Compare commits

..
47 Commits
Author SHA1 Message Date
nabarun e24d697c6d Upgrade package version to 0.1.5 (#29)
Part of https://www.notion.so/Gentx-like-attestation-mechanism-to-add-validators-at-genesis-time-19da6b22d47280ecbf1fe657c241ff59

Reviewed-on: LaconicNetwork/laconic-wallet-web#29
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2025-05-05 11:28:08 +00:00
nabarun f47850ebfa Fix cache headers for index html file (#28)
Part of <https://www.notion.so/Gentx-like-attestation-mechanism-to-add-validators-at-genesis-time-19da6b22d47280ecbf1fe657c241ff59>

- Use serve package for controlling separate cache headers for files

Reviewed-on: LaconicNetwork/laconic-wallet-web#28
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2025-05-02 08:23:22 +00:00
b2eafe59b3 Add hooks to add accounts and export private key from iframe (#26)
Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: AdityaSalunkhe21 <adityasalunkhe2204@gmail.com>
Reviewed-on: LaconicNetwork/laconic-wallet-web#26
Co-authored-by: ishavenikar <ishavenikar@noreply.git.vdb.to>
Co-committed-by: ishavenikar <ishavenikar@noreply.git.vdb.to>
2025-04-25 14:45:46 +00:00
713f8bc0bb Add iframe component for signing messages (#25)
Part of https://www.notion.so/Gentx-like-attestation-mechanism-to-add-validators-at-genesis-time-19da6b22d47280ecbf1fe657c241ff59

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Co-authored-by: AdityaSalunkhe21 <adityasalunkhe2204@gmail.com>
Reviewed-on: LaconicNetwork/laconic-wallet-web#25
Co-authored-by: ishavenikar <ishavenikar@noreply.git.vdb.to>
Co-committed-by: ishavenikar <ishavenikar@noreply.git.vdb.to>
2025-04-25 08:16:44 +00:00
6d5fcf798d Handle android webview messages for sign request (#24)
Part of https://www.notion.so/Integrate-eSIM-buy-flow-into-app-18aa6b22d47280d4a77cf1b27e2ba193

Co-authored-by: pranavjadhav007 <jadhavpranav89@gmail.com>
Co-authored-by: AdityaSalunkhe21 <adityasalunkhe2204@gmail.com>
Reviewed-on: LaconicNetwork/laconic-wallet-web#24
2025-04-15 14:25:11 +00:00
nabarun 3a0a321c6f Fix WALLET_ACCOUNTS_DATA iframe message data for Shopify app (#23)
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: cerc-io/laconic-wallet-web#23
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2025-03-25 11:36:18 +00:00
prathameshandShreerang Kale 59176ad7cb Use appropriate cosmos address prefix while signing messages (#22)
Part of https://www.notion.so/Revive-attestation-react-app-19da6b22d472809595c2fb9d2970849f

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#22
2025-02-25 10:38:59 +00:00
nabarunandIshaVenikar 0b4ceae6b2 Set default networks list on app start (#21)
Part of https://www.notion.so/Simplify-login-flow-in-deploy-laconic-com-190a6b22d47280a9924cc38f8cf4c891

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#21
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2025-02-12 09:17:56 +00:00
cbb28a6eb3 Add route to auto sign in for deploy app (#20)
Part of https://www.notion.so/Simplify-login-flow-in-deploy-laconic-com-190a6b22d47280a9924cc38f8cf4c891

- Auto sign SIWE message received if auto-sign-in is enabled
- Functionality to check account balance

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#20
2025-02-12 06:03:09 +00:00
nabarunandAdw8 9d2e710632 Change URL path to /wallet-embed (#19)
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#19
2024-11-12 12:28:44 +00:00
b527a9486d Add new component WalletEmbed to handle tx requests via iframe messaging (#18)
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Co-authored-by: Isha <ishavenikar7@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#18
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-11-12 09:46:16 +00:00
nabarunandShreerang Kale b94fd22c76 Add laconicd testnet-2 as default network (#17)
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#17
2024-10-28 12:13:13 +00:00
nabarun 657c39e5ed Add config for wallet connect verify code (#16)
Part of [Service provider auctions for web deployments](https://www.notion.so/Service-provider-auctions-for-web-deployments-104a6b22d47280dbad51d28aa3a91d75)

Reviewed-on: cerc-io/laconic-wallet-web#16
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-10-25 13:50:11 +00:00
zramsay e5c5d13c77 Merge pull request 'Style with Laconic colors and branding' (#15) from style/laconic-colors into main
Reviewed-on: cerc-io/laconic-wallet-web#15
2024-08-11 21:05:11 +00:00
zramsay edde3ab2b7 bump version 2024-08-11 17:00:47 -04:00
zramsay a20b5ad113 typo 2024-08-10 19:10:27 -04:00
zramsay 7ca019a83e fix build 2024-08-10 18:58:37 -04:00
cfbender 296bb3632b chore: rename style and add back enabled check 2024-08-10 17:49:55 -04:00
cfbender 360f3b76bc style: add back wallet connect button and style add session 2024-08-10 16:10:25 -04:00
Monkey 44e6670aab Merge remote-tracking branch 'origin/style/laconic-colors' into style/laconic-colors 2024-08-09 17:34:03 -04:00
Monkey 3aad056abe adjust header size and font size 2024-08-09 17:33:37 -04:00
cfbender 3d9b3408d9 style: fix layout padding 2024-08-09 17:33:00 -04:00
Monkey 053487da74 adjust site title and title spacing 2024-08-09 17:16:48 -04:00
cfbender 699cc5379e style: hd path 2024-08-09 16:13:24 -04:00
cfbender e05ce4659e style: import wallet dialog 2024-08-09 16:10:15 -04:00
cfbender ec3617ad42 style: sign message and use layout component 2024-08-09 16:06:49 -04:00
cfbender 393a42fceb feat: home link icon 2024-08-09 16:06:49 -04:00
cfbender 4ff1c10699 style: add network 2024-08-09 16:06:48 -04:00
cfbender 17810801dd style: gas price width 2024-08-09 16:06:17 -04:00
Monkey da3e4534d0 show private key dialog styling 2024-08-09 16:06:17 -04:00
cfbender a1747b8ba7 style: edit network and logo 2024-08-09 16:06:17 -04:00
Monkey c818bc0b9d Show Private Key confirmation dialog button styling 2024-08-09 16:06:17 -04:00
Monkey b2a043068a use mui/lab LoadingButton for CreateWallet 2024-08-09 16:06:16 -04:00
Monkey f67569a25e replace Dialog.tsx changes 2024-08-09 16:04:50 -04:00
Monkey 38d68a86d5 reset dialog styles 2024-08-09 16:04:50 -04:00
cfbender 7b72631206 style: container 2024-08-09 16:04:48 -04:00
cfbender 45e045d956 style: accordions on home screen 2024-08-09 16:03:18 -04:00
cfbender 181f9e8b7d style: home screen first pass 2024-08-09 16:03:18 -04:00
cfbender cca89775aa style: home screen first pass 2024-08-09 16:03:17 -04:00
Monkey afd09dc4c1 mnemonic dialog styles 2024-08-09 15:58:01 -04:00
cfbender e0632d1a50 style: initial dark mode 2024-08-09 15:57:29 -04:00
nabarunandShreerang Kale ba05a82406 Disable import wallet functionality with const flag (#14)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#14
2024-08-09 10:56:51 +00:00
nabarunandIshaVenikar 2bb92205ba Implement functionality to import wallet from mnemonic (#13)
Part of cerc-io/laconic-wallet-web#11

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#13
2024-08-09 10:24:38 +00:00
c26bddec1a Add support for staking module tx MsgCreateValidator (#12)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Co-authored-by: Prathamesh Musale <prathamesh.musale0@gmail.com>
Co-authored-by: Adw8 <adwaitgharpure@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#12
2024-08-09 09:12:32 +00:00
nabarunandIshaVenikar 6f174c6e0c Add loader at app start and configure eslint (#10)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#10
Co-authored-by: Nabarun <nabarun@deepstacksoft.com>
Co-committed-by: Nabarun <nabarun@deepstacksoft.com>
2024-08-08 10:39:02 +00:00
nabarunandIshaVenikar 2455dd982b Add button to copy mnemonic after creating new wallet (#9)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Co-authored-by: IshaVenikar <ishavenikar7@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#9
2024-08-07 11:39:10 +00:00
nabarunandShreerang Kale 9a112783d2 Send namespaces object with correct accounts sequence while pairing with dApps (#8)
Part of [laconicd testnet validator enrollment](https://www.notion.so/laconicd-testnet-validator-enrollment-6fc1d3cafcc64fef8c5ed3affa27c675)

Co-authored-by: Shreerang Kale <shreerangkale@gmail.com>
Reviewed-on: cerc-io/laconic-wallet-web#8
2024-08-01 10:17:11 +00:00
61 changed files with 2983 additions and 1051 deletions
+5 -1
View File
@@ -1,5 +1,9 @@
REACT_APP_WALLET_CONNECT_PROJECT_ID=
REACT_APP_DEFAULT_GAS_PRICE=0.025
# Reference: https://github.com/cosmos/cosmos-sdk/issues/16020
REACT_APP_GAS_ADJUSTMENT=2
REACT_APP_LACONICD_RPC_URL=https://laconicd.laconic.com
REACT_APP_LACONICD_RPC_URL=https://laconicd-sapo.laconic.com
# Example: https://example-url-1.com,https://example-url-2.com
REACT_APP_ALLOWED_URLS=
+1
View File
@@ -0,0 +1 @@
build
+17
View File
@@ -0,0 +1,17 @@
{
"parser": "@typescript-eslint/parser",
"plugins": ["react"],
"extends": [
"react-app",
"react-app/jest",
"plugin:react/recommended"
],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"indent": ["error", 2, { "SwitchCase": 1 }]
}
}
+1
View File
@@ -0,0 +1 @@
yarn lint
+43 -43
View File
@@ -2,52 +2,52 @@
const webpack = require('webpack')
module.exports = function override(config, env) {
config.module.rules.push({
test: /\.js$/,
exclude: /node_modules[/\\](?!react-native-vector-icons)/,
use: {
loader: "babel-loader",
options: {
// Disable reading babel configuration
babelrc: false,
configFile: false,
config.module.rules.push({
test: /\.js$/,
exclude: /node_modules[/\\](?!react-native-vector-icons)/,
use: {
loader: "babel-loader",
options: {
// Disable reading babel configuration
babelrc: false,
configFile: false,
// The configuration for compilation
presets: [
["@babel/preset-env", { useBuiltIns: "usage", "corejs": "3" }],
"@babel/preset-react",
"@babel/preset-flow",
"@babel/preset-typescript",
],
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-transform-modules-commonjs"
]
}
// The configuration for compilation
presets: [
["@babel/preset-env", { useBuiltIns: "usage", "corejs": "3" }],
"@babel/preset-react",
"@babel/preset-flow",
"@babel/preset-typescript",
],
plugins: [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
"@babel/plugin-transform-modules-commonjs"
]
}
});
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
})
)
config.module.rules.push({
test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/,
type: 'asset/resource'
})
config.resolve.fallback = {
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
url: false
}
});
config.resolve.alias['react-native$'] = require.resolve('react-native-web');
config.plugins.push(
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
})
)
return config;
config.module.rules.push({
test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/,
type: 'asset/resource'
})
config.resolve.fallback = {
crypto: require.resolve("crypto-browserify"),
stream: require.resolve("stream-browserify"),
http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
url: false
}
config.resolve.alias['react-native$'] = require.resolve('react-native-web');
return config;
};
+12 -1
View File
@@ -1,6 +1,6 @@
{
"name": "web-wallet",
"version": "0.1.0",
"version": "0.1.5",
"private": true,
"dependencies": {
"@cerc-io/registry-sdk": "^0.2.5",
@@ -13,6 +13,8 @@
"@ethersproject/shims": "^5.7.0",
"@hookform/resolvers": "^3.3.4",
"@json-rpc-tools/utils": "^1.7.6",
"@mui/icons-material": "^5.16.7",
"@mui/lab": "^5.0.0-alpha.173",
"@mui/material": "^5.16.4",
"@react-navigation/elements": "^1.3.30",
"@react-navigation/native": "^6.1.10",
@@ -49,6 +51,8 @@
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"lint": "eslint .",
"prepare": "husky",
"eject": "react-scripts eject"
},
"eslintConfig": {
@@ -75,7 +79,14 @@
"@types/node": "^16.7.13",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"crypto-browserify": "^3.12.0",
"eslint": "^8.3.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react": "^7.33.2",
"eslint-webpack-plugin": "^3.1.1",
"husky": "^9.0.11",
"react-app-rewired": "^2.2.1",
"stream-browserify": "^3.0.0"
}
+18
View File
@@ -0,0 +1,18 @@
const config = {
arrowParens: "always",
printWidth: 80,
semi: false,
singleQuote: true,
tabWidth: 2,
trailingComma: "es5",
importOrderSeparation: true,
importOrderSortSpecifiers: true,
plugins: ["@trivago/prettier-plugin-sort-imports"],
importOrder: [
"<THIRD_PARTY_MODULES>",
"^(pages|components|utils|icons|test|graphql)/(.*)$",
"^[./]",
],
};
export default config;
+10
View File
@@ -0,0 +1,10 @@
<svg width="115" height="20" viewBox="0 0 115 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.37388 10.5194C5.70149 8.19185 7.14225 4.97748 7.1416 1.42853C7.14246 0.94681 7.11586 0.470456 7.063 0L-0.000488281 0.000643078L-0.000273922 13.5723C-0.000917354 15.2174 0.62632 16.863 1.88091 18.1175C3.1356 19.3721 4.78235 20.0001 6.42772 19.9993L6.42729 19.9997L19.9995 20L19.999 12.9355C19.5296 12.8838 19.0532 12.857 18.5704 12.8569C15.0224 12.8574 11.8079 14.298 9.48026 16.6255C7.78654 18.2768 5.07093 18.2771 3.39812 16.6043C1.72638 14.9325 1.72562 12.2161 3.37388 10.5194ZM18.5344 1.46863C16.5837 -0.481929 13.4146 -0.48268 11.4633 1.46863C9.512 3.41984 9.51276 6.58895 11.4633 8.53941C13.415 10.491 16.5831 10.4907 18.5344 8.53941C20.4857 6.5882 20.4861 3.42016 18.5344 1.46863Z" fill="#FBFBFB"/>
<path d="M31.4741 18.5838H39.2552V16.3302H34.075V1.41351H31.4741V18.5838Z" fill="#FBFBFB"/>
<path d="M49.8108 1.41351H45.4976L40.9893 18.5838H43.6769L44.8039 14.2913H50.3744L51.5014 18.5838H54.3191L49.8108 1.41351ZM45.3458 12.145L47.6 3.2593H47.6866L49.8541 12.145H45.3458Z" fill="#FBFBFB"/>
<path d="M62.9292 8.06885H65.9636C65.9636 3.17534 64.3813 1.07196 60.6967 1.07196C56.8169 1.07196 55.1479 3.73341 55.1479 9.97909C55.1479 16.2462 56.8169 18.9291 60.6967 18.9291C64.3813 18.9291 65.9636 16.8901 65.9853 12.1468H62.9508C62.9292 15.8599 62.474 16.7828 60.6967 16.7828C58.6593 16.7828 58.1607 15.4307 58.1824 9.97909C58.1824 4.54896 58.6809 3.19678 60.6967 3.21823C62.474 3.21823 62.9292 4.18413 62.9292 8.06885Z" fill="#FBFBFB"/>
<path d="M73.7781 1.07209C77.7229 1.09364 79.4135 3.77643 79.4135 10.0007C79.4135 16.2249 77.7229 18.9078 73.7781 18.9292C69.8117 18.9507 68.1211 16.2678 68.1211 10.0007C68.1211 3.73354 69.8117 1.05064 73.7781 1.07209ZM71.1555 10.0007C71.1555 15.4308 71.6757 16.783 73.7781 16.783C75.8589 16.783 76.3791 15.4308 76.3791 10.0007C76.3791 4.54909 75.8589 3.19691 73.7781 3.21847C71.6757 3.23992 71.1555 4.59209 71.1555 10.0007Z" fill="#FBFBFB"/>
<path d="M85.0819 18.5624L82.481 18.5838V1.41351H87.0544L91.3243 15.4073H91.3676V1.41351H93.968V18.5838H89.677L85.1254 3.51689H85.0819V18.5624Z" fill="#FBFBFB"/>
<path d="M100.468 1.41351H97.8677V18.5838H100.468V1.41351Z" fill="#FBFBFB"/>
<path d="M111.139 8.06885H114.174C114.174 3.17534 112.591 1.07196 108.906 1.07196C105.028 1.07196 103.358 3.73341 103.358 9.97909C103.358 16.2462 105.028 18.9291 108.906 18.9291C112.591 18.9291 114.174 16.8901 114.195 12.1468H111.161C111.139 15.8599 110.684 16.7828 108.906 16.7828C106.869 16.7828 106.371 15.4307 106.393 9.97909C106.393 4.54896 106.891 3.19678 108.906 3.21823C110.684 3.21823 111.139 4.18413 111.139 8.06885Z" fill="#FBFBFB"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

+71 -21
View File
@@ -1,21 +1,24 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Laconic Wallet Web App"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Laconic Wallet Web App" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
rel="stylesheet" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
@@ -24,12 +27,58 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Laconic Wallet</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
<title>Laconic Wallet</title>
<style>
#app {
background-color: #0f0f0f;
}
body {
margin: 0;
padding: 0;
height: 100%;
background-color: #0f0f0f;
}
.loader-wrapper {
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
display: grid;
place-items: center;
}
.loader {
border: 16px solid #e3e3e3;
border-top: 16px solid #6750a4;
border-radius: 50%;
width: 140px;
height: 140px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<div class="loader-wrapper">
<div class="loader"></div>
</div>
</div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
@@ -39,5 +88,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</body>
</html>
+1 -1
View File
@@ -21,5 +21,5 @@
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
"background_color": "#0f0f0f"
}
+167 -70
View File
@@ -1,60 +1,73 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Button, Snackbar, Text } from 'react-native-paper';
import { TxBody, AuthInfo } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
import React, { useCallback, useEffect, useMemo, useState } from "react";
import { Button, Snackbar, Surface, Text } from "react-native-paper";
import { TxBody, AuthInfo } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { SignClientTypes } from '@walletconnect/types';
import { useNavigation } from '@react-navigation/native';
import { SignClientTypes } from "@walletconnect/types";
import { useNavigation } from "@react-navigation/native";
import { DirectSecp256k1Wallet } from "@cosmjs/proto-signing";
import { SigningStargateClient } from "@cosmjs/stargate";
import {
createStackNavigator,
StackNavigationProp,
} from '@react-navigation/stack';
import { getSdkError } from '@walletconnect/utils';
import { Web3WalletTypes } from '@walletconnect/web3wallet';
import { formatJsonRpcResult } from '@json-rpc-tools/utils';
createStackNavigator,
StackNavigationProp,
} from "@react-navigation/stack";
import { getSdkError } from "@walletconnect/utils";
import { Web3WalletTypes } from "@walletconnect/web3wallet";
import { formatJsonRpcResult } from "@json-rpc-tools/utils";
import PairingModal from './components/PairingModal';
import { useWalletConnect } from './context/WalletConnectContext';
import { useAccounts } from './context/AccountsContext';
import InvalidPath from './screens/InvalidPath';
import SignMessage from './screens/SignMessage';
import HomeScreen from './screens/HomeScreen';
import SignRequest from './screens/SignRequest';
import AddSession from './screens/AddSession';
import WalletConnect from './screens/WalletConnect';
import ApproveTransaction from './screens/ApproveTransaction';
import { StackParamsList } from './types';
import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data';
import { getSignParamsMessage } from './utils/wallet-connect/helpers';
import ApproveTransfer from './screens/ApproveTransfer';
import AddNetwork from './screens/AddNetwork';
import EditNetwork from './screens/EditNetwork';
import { COSMOS, EIP155 } from './utils/constants';
import { useNetworks } from './context/NetworksContext';
import { NETWORK_METHODS } from './utils/wallet-connect/common-data';
import { COSMOS_METHODS } from './utils/wallet-connect/COSMOSData';
import PairingModal from "./components/PairingModal";
import { useWalletConnect } from "./context/WalletConnectContext";
import { useAccounts } from "./context/AccountsContext";
import InvalidPath from "./screens/InvalidPath";
import SignMessage from "./screens/SignMessage";
import HomeScreen from "./screens/HomeScreen";
import SignRequest from "./screens/SignRequest";
import AddSession from "./screens/AddSession";
import WalletConnect from "./screens/WalletConnect";
import ApproveTransaction from "./screens/ApproveTransaction";
import { StackParamsList } from "./types";
import { EIP155_SIGNING_METHODS } from "./utils/wallet-connect/EIP155Data";
import { getSignParamsMessage } from "./utils/wallet-connect/helpers";
import ApproveTransfer from "./screens/ApproveTransfer";
import AddNetwork from "./screens/AddNetwork";
import EditNetwork from "./screens/EditNetwork";
import { COSMOS, EIP155 } from "./utils/constants";
import { useNetworks } from "./context/NetworksContext";
import { NETWORK_METHODS } from "./utils/wallet-connect/common-data";
import { COSMOS_METHODS } from "./utils/wallet-connect/COSMOSData";
import styles from "./styles/stylesheet";
import { Header } from "./components/Header";
import { WalletEmbed } from "./screens/WalletEmbed";
import { AutoSignIn } from "./screens/AutoSignIn";
import { checkSufficientFunds, getPathKey, sendMessage } from "./utils/misc";
import useAccountsData from "./hooks/useAccountsData";
import { useWebViewHandler } from "./hooks/useWebViewHandler";
import SignRequestEmbed from "./screens/SignRequestEmbed";
import useAddAccountEmbed from "./hooks/useAddAccountEmbed";
import useExportPKEmbed from "./hooks/useExportPrivateKeyEmbed";
const Stack = createStackNavigator<StackParamsList>();
const App = (): React.JSX.Element => {
const navigation =
useNavigation<StackNavigationProp<StackParamsList>>();
const navigation = useNavigation<StackNavigationProp<StackParamsList>>();
const { web3wallet, setActiveSessions } = useWalletConnect();
const { accounts, setCurrentIndex } = useAccounts();
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
const { getAccountsData } = useAccountsData();
const [modalVisible, setModalVisible] = useState(false);
const [toastVisible, setToastVisible] = useState(false);
const [currentProposal, setCurrentProposal] = useState<
SignClientTypes.EventArguments['session_proposal'] | undefined
SignClientTypes.EventArguments["session_proposal"] | undefined
>();
const onSessionProposal = useCallback(
async (proposal: SignClientTypes.EventArguments['session_proposal']) => {
async (proposal: SignClientTypes.EventArguments["session_proposal"]) => {
if (!accounts.length || !accounts.length) {
const { id } = proposal;
await web3wallet!.rejectSession({
id,
reason: getSdkError('UNSUPPORTED_ACCOUNTS'),
reason: getSdkError("UNSUPPORTED_ACCOUNTS"),
});
return;
}
@@ -74,10 +87,11 @@ const App = (): React.JSX.Element => {
switch (request.method) {
case NETWORK_METHODS.GET_NETWORKS:
const currentNetworkId = networksData.find(
networkData => networkData.networkId === selectedNetwork!.networkId,
(networkData) =>
networkData.networkId === selectedNetwork!.networkId,
)?.networkId;
const networkNamesData = networksData.map(networkData => {
const networkNamesData = networksData.map((networkData) => {
return {
id: networkData.networkId,
name: networkData.networkName,
@@ -98,13 +112,13 @@ const App = (): React.JSX.Element => {
case NETWORK_METHODS.CHANGE_NETWORK:
const networkNameData = request.params[0];
const network = networksData.find(
networkData => networkData.networkId === networkNameData.id,
(networkData) => networkData.networkId === networkNameData.id,
);
setCurrentIndex(0);
setSelectedNetwork(network);
const response = formatJsonRpcResult(id, {
response: 'true',
response: "true",
});
await web3wallet!.respondSessionRequest({
@@ -114,7 +128,7 @@ const App = (): React.JSX.Element => {
break;
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
navigation.navigate('ApproveTransfer', {
navigation.navigate("ApproveTransfer", {
transaction: request.params[0],
requestEvent,
requestSessionData,
@@ -122,7 +136,7 @@ const App = (): React.JSX.Element => {
break;
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
navigation.navigate('SignRequest', {
navigation.navigate("SignRequest", {
namespace: EIP155,
address: request.params[1],
message: getSignParamsMessage(request.params),
@@ -136,19 +150,19 @@ const App = (): React.JSX.Element => {
txbody: TxBody.toJSON(
TxBody.decode(
Uint8Array.from(
Buffer.from(request.params.signDoc.bodyBytes, 'hex'),
Buffer.from(request.params.signDoc.bodyBytes, "hex"),
),
),
),
authInfo: AuthInfo.toJSON(
AuthInfo.decode(
Uint8Array.from(
Buffer.from(request.params.signDoc.authInfoBytes, 'hex'),
Buffer.from(request.params.signDoc.authInfoBytes, "hex"),
),
),
),
};
navigation.navigate('SignRequest', {
navigation.navigate("SignRequest", {
namespace: COSMOS,
address: request.params.signerAddress,
message: JSON.stringify(message, undefined, 2),
@@ -158,7 +172,7 @@ const App = (): React.JSX.Element => {
break;
case COSMOS_METHODS.COSMOS_SIGN_AMINO:
navigation.navigate('SignRequest', {
navigation.navigate("SignRequest", {
namespace: COSMOS,
address: request.params.signerAddress,
message: request.params.signDoc.memo,
@@ -168,7 +182,7 @@ const App = (): React.JSX.Element => {
break;
case COSMOS_METHODS.COSMOS_SEND_TOKENS:
navigation.navigate('ApproveTransfer', {
navigation.navigate("ApproveTransfer", {
transaction: request.params[0],
requestEvent,
requestSessionData,
@@ -177,7 +191,7 @@ const App = (): React.JSX.Element => {
case COSMOS_METHODS.COSMOS_SEND_TRANSACTION:
const { transactionMessage, signer } = request.params;
navigation.navigate('ApproveTransaction', {
navigation.navigate("ApproveTransaction", {
transactionMessage,
signer,
requestEvent,
@@ -186,7 +200,7 @@ const App = (): React.JSX.Element => {
break;
default:
throw new Error('Invalid method');
throw new Error("Invalid method");
}
},
[
@@ -195,7 +209,7 @@ const App = (): React.JSX.Element => {
setSelectedNetwork,
setCurrentIndex,
selectedNetwork,
web3wallet
web3wallet,
],
);
@@ -205,18 +219,76 @@ const App = (): React.JSX.Element => {
}, [setActiveSessions, web3wallet]);
useEffect(() => {
web3wallet?.on('session_proposal', onSessionProposal);
web3wallet?.on('session_request', onSessionRequest);
web3wallet?.on('session_delete', onSessionDelete);
web3wallet?.on("session_proposal", onSessionProposal);
web3wallet?.on("session_request", onSessionRequest);
web3wallet?.on("session_delete", onSessionDelete);
return () => {
web3wallet?.off('session_proposal', onSessionProposal);
web3wallet?.off('session_request', onSessionRequest);
web3wallet?.off('session_delete', onSessionDelete);
web3wallet?.off("session_proposal", onSessionProposal);
web3wallet?.off("session_request", onSessionRequest);
web3wallet?.off("session_delete", onSessionDelete);
};
});
useEffect(() => {
const handleCheckBalance = async (event: MessageEvent) => {
if (event.data.type !== 'CHECK_BALANCE') return;
const { chainId, amount } = event.data;
const network = networksData.find(net => net.chainId === chainId);
if (!network) {
console.error('Network not found');
throw new Error('Requested network not supported.');
}
if (network.namespace !== COSMOS) {
throw new Error('Unsupported network');
}
const accounts = await getAccountsData(chainId);
const account = accounts[0];
if (!account) {
throw new Error(`No accounts in network ${chainId}`);
}
const cosmosPrivKey = (
await getPathKey(`${network.namespace}:${chainId}`, account.index)
).privKey;
const sender = await DirectSecp256k1Wallet.fromKey(
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
network.addressPrefix
);
const client = await SigningStargateClient.connectWithSigner(network.rpcUrl!, sender);
const balance = await client.getBalance(
account.address,
network.nativeDenom!.toLowerCase()
);
const areFundsSufficient = checkSufficientFunds(amount, balance.amount);
sendMessage(event.source as Window, 'IS_SUFFICIENT', areFundsSufficient, event.origin);
};
window.addEventListener('message', handleCheckBalance);
return () => {
window.removeEventListener('message', handleCheckBalance);
};
}, [networksData, getAccountsData]);
const showWalletConnect = useMemo(() => accounts.length > 0, [accounts]);
useWebViewHandler();
useAddAccountEmbed();
useExportPKEmbed();
return (
<>
<Surface style={styles.appSurface}>
<Stack.Navigator
screenOptions={{
headerBackTitleVisible: true,
@@ -227,7 +299,9 @@ const App = (): React.JSX.Element => {
component={HomeScreen}
options={{
// eslint-disable-next-line react/no-unstable-nested-components
headerTitle: () => <Text variant="titleLarge">Laconic Wallet</Text>,
header: () => (
<Header title="Wallet" showWalletConnect={showWalletConnect} />
),
}}
/>
<Stack.Screen
@@ -235,7 +309,7 @@ const App = (): React.JSX.Element => {
component={SignMessage}
options={{
// eslint-disable-next-line react/no-unstable-nested-components
headerTitle: () => <Text variant="titleLarge">Sign Message</Text>,
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
@@ -243,7 +317,7 @@ const App = (): React.JSX.Element => {
component={SignRequest}
options={{
// eslint-disable-next-line react/no-unstable-nested-components
headerTitle: () => <Text variant="titleLarge">Sign Request</Text>,
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
@@ -254,11 +328,11 @@ const App = (): React.JSX.Element => {
headerTitle: () => <Text variant="titleLarge">Bad Request</Text>,
}}
/>
<Stack.Screen
<Stack.Screen
name="AddSession"
component={AddSession}
options={{
title: 'New session',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
@@ -271,8 +345,9 @@ const App = (): React.JSX.Element => {
headerRight: () => (
<Button
onPress={() => {
navigation.navigate('AddSession');
}}>
navigation.navigate("AddSession");
}}
>
{<Text>PAIR</Text>}
</Button>
),
@@ -282,28 +357,49 @@ const App = (): React.JSX.Element => {
name="ApproveTransfer"
component={ApproveTransfer}
options={{
title: 'Approve transfer',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
name="AddNetwork"
component={AddNetwork}
options={{
title: 'Add Network',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
name="EditNetwork"
component={EditNetwork}
options={{
title: 'Edit Network',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
name="ApproveTransaction"
component={ApproveTransaction}
options={{
title: 'Approve Transaction',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
name="wallet-embed"
component={WalletEmbed}
options={{
header: () => <></>,
}}
/>
<Stack.Screen
name="auto-sign-in"
component={AutoSignIn}
options={{
header: () => <></>,
}}
/>
<Stack.Screen
name="sign-request-embed"
component={SignRequestEmbed}
options={{
header: () => <Header title="Wallet" />,
}}
/>
</Stack.Navigator>
@@ -317,10 +413,11 @@ const App = (): React.JSX.Element => {
<Snackbar
visible={toastVisible}
onDismiss={() => setToastVisible(false)}
duration={3000}>
duration={3000}
>
Session approved
</Snackbar>
</>
</Surface>
);
};
+25 -19
View File
@@ -1,9 +1,7 @@
import React from 'react';
import { View } from 'react-native';
import { Text } from 'react-native-paper';
import React from "react";
import { Account } from '../types';
import styles from '../styles/stylesheet';
import { Account } from "../types";
import { Box, Stack, Typography } from "@mui/material";
interface AccountDetailsProps {
account: Account | undefined;
@@ -11,20 +9,28 @@ interface AccountDetailsProps {
const AccountDetails: React.FC<AccountDetailsProps> = ({ account }) => {
return (
<View style={styles.accountContainer}>
<Text variant="bodyLarge" selectable={true}>
<Text style={styles.highlight}>Address: </Text>
{account?.address}
</Text>
<Text variant="bodyLarge" selectable={true}>
<Text style={styles.highlight}>Public Key: </Text>
{account?.pubKey}
</Text>
<Text variant="bodyLarge">
<Text style={styles.highlight}>HD Path: </Text>
{account?.hdPath}
</Text>
</View>
<Box sx={{ marginY: 4 }}>
<Stack spacing={1}>
<Stack flexDirection="row">
<Typography color="secondary" sx={{ mr: 1 }}>
Address:
</Typography>
<Typography color="text.primary">{account?.address}</Typography>
</Stack>
</Stack>
<Stack flexDirection="row">
<Typography color="secondary" sx={{ mr: 1 }}>
Public Key:
</Typography>
<Typography color="text.primary">{account?.pubKey}</Typography>
</Stack>
<Stack flexDirection="row">
<Typography color="secondary" sx={{ mr: 1 }}>
HD Path:
</Typography>
<Typography color="text.primary">{account?.hdPath}</Typography>
</Stack>
</Box>
);
};
+85 -100
View File
@@ -1,22 +1,30 @@
import React, { useEffect, useState } from 'react';
import { TouchableOpacity, View } from 'react-native';
import { Button, List, Text, useTheme } from 'react-native-paper';
import React, { useEffect, useState } from "react";
import { TouchableOpacity, View } from "react-native";
import { List } from "react-native-paper";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { StackParamsList, Account } from '../types';
import { addAccount } from '../utils/accounts';
import styles from '../styles/stylesheet';
import HDPathDialog from './HDPathDialog';
import AccountDetails from './AccountDetails';
import { useAccounts } from '../context/AccountsContext';
import { useWalletConnect } from '../context/WalletConnectContext';
import { useNetworks } from '../context/NetworksContext';
import ConfirmDialog from './ConfirmDialog';
import { getNamespaces } from '../utils/wallet-connect/helpers';
import ShowPKDialog from './ShowPKDialog';
import { setInternetCredentials } from '../utils/key-store';
import { StackParamsList, Account } from "../types";
import { addAccount } from "../utils/accounts";
import HDPathDialog from "./HDPathDialog";
import AccountDetails from "./AccountDetails";
import { useAccounts } from "../context/AccountsContext";
import { useWalletConnect } from "../context/WalletConnectContext";
import { useNetworks } from "../context/NetworksContext";
import ConfirmDialog from "./ConfirmDialog";
import { getNamespaces } from "../utils/wallet-connect/helpers";
import ShowPKDialog from "./ShowPKDialog";
import { setInternetCredentials } from "../utils/key-store";
import {
Accordion,
AccordionSummary,
Button,
Link,
Stack,
} from "@mui/material";
import { LoadingButton } from "@mui/lab";
const Accounts = () => {
const navigation =
@@ -27,16 +35,14 @@ const Accounts = () => {
const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } =
useNetworks();
const {web3wallet} = useWalletConnect();
const { web3wallet } = useWalletConnect();
const [expanded, setExpanded] = useState(false);
const [isAccountCreating, setIsAccountCreating] = useState(false);
const [hdDialog, setHdDialog] = useState(false);
const [pathCode, setPathCode] = useState('');
const [pathCode, setPathCode] = useState("");
const [deleteNetworkDialog, setDeleteNetworkDialog] =
useState<boolean>(false);
const theme = useTheme();
const handlePress = () => setExpanded(!expanded);
const hideDeleteNetworkDialog = () => setDeleteNetworkDialog(false);
@@ -60,7 +66,6 @@ const Accounts = () => {
networksData,
selectedNetwork!,
accounts,
currentIndex,
);
if (!updatedNamespaces) {
@@ -89,7 +94,7 @@ const Accounts = () => {
};
const renderAccountItems = () =>
accounts.map(account => (
accounts.map((account) => (
<List.Item
key={account.index}
title={`Account ${account.index + 1}`}
@@ -102,12 +107,12 @@ const Accounts = () => {
const handleRemove = async () => {
const updatedNetworks = networksData.filter(
networkData => selectedNetwork!.networkId !== networkData.networkId,
(networkData) => selectedNetwork!.networkId !== networkData.networkId,
);
await setInternetCredentials(
'networks',
'_',
"networks",
"_",
JSON.stringify(updatedNetworks),
);
@@ -126,94 +131,74 @@ const Accounts = () => {
updateAccounts={updateAccounts}
pathCode={pathCode}
/>
<List.Accordion
title={`Account ${currentIndex + 1}`}
expanded={expanded}
onPress={handlePress}>
<Accordion expanded={expanded} onClick={handlePress}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>{`Account ${currentIndex + 1}`}</AccordionSummary>
{renderAccountItems()}
</List.Accordion>
</Accordion>
<View style={styles.addAccountButton}>
<Button
mode="contained"
onPress={addAccountHandler}
<Stack direction="row" spacing={3} sx={{ mt: 4 }}>
<LoadingButton
variant="contained"
onClick={addAccountHandler}
loading={isAccountCreating}
disabled={isAccountCreating}>
{isAccountCreating ? 'Adding' : 'Add Account'}
</Button>
</View>
disabled={isAccountCreating}
>
{isAccountCreating ? "Adding" : "Add Account"}
</LoadingButton>
<View style={styles.addAccountButton}>
<Button
mode="contained"
onPress={() => {
variant="contained"
onClick={() => {
setHdDialog(true);
setPathCode(`m/44'/${selectedNetwork!.coinType}'/`);
}}>
}}
>
Add Account from HD path
</Button>
</View>
</Stack>
<AccountDetails account={accounts[currentIndex]} />
<View style={styles.linkContainer}>
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
navigation.navigate('SignMessage', {
selectedNamespace: selectedNetwork!.namespace,
selectedChainId: selectedNetwork!.chainId,
accountInfo: accounts[currentIndex],
});
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Sign Message
</Text>
</TouchableOpacity>
</View>
<Stack direction="row" spacing={4}>
<TouchableOpacity
onPress={() => {
navigation.navigate("SignMessage", {
selectedNamespace: selectedNetwork!.namespace,
selectedChainId: selectedNetwork!.chainId,
accountInfo: accounts[currentIndex],
});
}}
>
<Link>Sign Message</Link>
</TouchableOpacity>
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
navigation.navigate('AddNetwork');
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Add Network
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
onPress={() => {
navigation.navigate("AddNetwork");
}}
>
<Link>Add Network</Link>
</TouchableOpacity>
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
navigation.navigate('EditNetwork', {
selectedNetwork: selectedNetwork!,
});
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Edit Network
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
onPress={() => {
navigation.navigate("EditNetwork", {
selectedNetwork: selectedNetwork!,
});
}}
>
<Link>Edit Network</Link>
</TouchableOpacity>
{!selectedNetwork!.isDefault && (
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
setDeleteNetworkDialog(true);
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Delete Network
</Text>
</TouchableOpacity>
</View>
<TouchableOpacity
onPress={() => {
setDeleteNetworkDialog(true);
}}
>
<Link>Delete Network</Link>
</TouchableOpacity>
)}
<ConfirmDialog
title="Delete Network"
@@ -222,7 +207,7 @@ const Accounts = () => {
onConfirm={handleRemove}
/>
<ShowPKDialog />
</View>
</Stack>
</View>
</View>
);
+7 -6
View File
@@ -1,6 +1,7 @@
import React from 'react';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography } from '@mui/material';
import { ResetDialogProps } from '../types';
import styles from "../styles/stylesheet";
const ConfirmDialog = ({
title,
@@ -10,15 +11,15 @@ const ConfirmDialog = ({
}: ResetDialogProps) => {
return (
<Dialog open={visible} onClose={hideDialog}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Typography variant="body1">Are you sure?</Typography>
<DialogTitle style={{...styles.resetDialogTitle}} >{title}</DialogTitle>
<DialogContent style={{...styles.resetDialogContent}}>
<Typography>Are you sure?</Typography>
</DialogContent>
<DialogActions>
<Button color="error" onClick={onConfirm}>
<DialogActions style={{...styles.resetDialogActionRow}}>
<Button style={{...styles.buttonRed, ...styles.button}} color="error" onClick={onConfirm}>
Yes
</Button>
<Button onClick={hideDialog}>No</Button>
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideDialog}>No</Button>
</DialogActions>
</Dialog>
);
+21
View File
@@ -0,0 +1,21 @@
import { Box, BoxProps } from "@mui/material";
import React, { PropsWithChildren } from "react";
export const Container: React.FC<
PropsWithChildren<{ boxProps?: BoxProps }>
> = ({ children, boxProps = {} }) => (
<Box
{...boxProps}
sx={{
width: "100%",
maxWidth: "752px",
marginX: "auto",
backgroundColor: "background.paper",
padding: 3,
borderRadius: 2,
...boxProps.sx,
}}
>
{children}
</Box>
);
+11 -11
View File
@@ -1,9 +1,8 @@
import { View } from 'react-native';
import React from 'react';
import { Button } from 'react-native-paper';
import React from "react";
import { View } from "react-native";
import { LoadingButton } from "@mui/lab";
import { CreateWalletProps } from '../types';
import styles from '../styles/stylesheet';
import { CreateWalletProps } from "../types";
const CreateWallet = ({
isWalletCreating,
@@ -11,14 +10,15 @@ const CreateWallet = ({
}: CreateWalletProps) => {
return (
<View>
<View style={styles.createWalletContainer}>
<Button
mode="contained"
<View>
<LoadingButton
variant="contained"
loading={isWalletCreating}
disabled={isWalletCreating}
onPress={createWalletHandler}>
{isWalletCreating ? 'Creating' : 'Create Wallet'}
</Button>
onClick={createWalletHandler}
>
{isWalletCreating ? "Creating" : "CREATE WALLET"}
</LoadingButton>
</View>
</View>
);
+30 -14
View File
@@ -1,28 +1,44 @@
import React from 'react';
import { Dialog, DialogActions, DialogContent, DialogTitle, Button, Typography } from '@mui/material';
import React from "react";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
Typography,
} from "@mui/material";
import styles from '../styles/stylesheet';
import GridView from './Grid';
import { CustomDialogProps } from '../types';
import styles from "../styles/stylesheet";
import GridView from "./Grid";
import { CustomDialogProps } from "../types";
const DialogComponent = ({
visible,
hideDialog,
contentText,
}: CustomDialogProps) => {
const words = contentText.split(" ");
const DialogComponent = ({ visible, hideDialog, contentText }: CustomDialogProps) => {
const words = contentText.split(' ');
const copyMnemonic = () => {
navigator.clipboard.writeText(contentText);
};
return (
<Dialog open={visible} onClose={hideDialog}>
<DialogTitle>Mnemonic</DialogTitle>
<DialogContent>
<Typography variant="h6" component="div" style={{ color: 'rgba(0, 0, 0, 0.87)' }}>
Your mnemonic provides full access to your wallet and funds. Make sure to note it down.
<DialogTitle style={{...styles.mnemonicTitle}}>Mnemonic</DialogTitle>
<DialogContent style={{...styles.mnemonicContainer}}>
<Typography component="div">
Your mnemonic provides full access to your wallet and funds.<br/>
Make sure to note it down.
</Typography>
<Typography variant="h6" component="div" style={{ ...styles.dialogWarning }}>
<Typography component="div" style={{ ...styles.mnemonicDialogWarning }}>
Do not share your mnemonic with anyone
</Typography>
<GridView words={words} />
</DialogContent>
<DialogActions>
<Button onClick={hideDialog}>Done</Button>
<DialogActions style={{...styles.mnemonicButtonRow}}>
<Button style={{...styles.mnemonicButton}} onClick={copyMnemonic}>Copy</Button>
<Button style={{...styles.mnemonicButton}} onClick={hideDialog}>Done</Button>
</DialogActions>
</Dialog>
);
+1 -1
View File
@@ -7,7 +7,7 @@ import { GridViewProps } from '../types';
const GridView = ({ words }: GridViewProps) => {
return (
<View style={styles.gridContainer}>
<View style={styles.mnemonicGridContainer}>
{words.map((word, index) => (
<View key={index} style={styles.gridItem}>
<Text>{index + 1}. </Text>
+25 -23
View File
@@ -1,11 +1,12 @@
import React, { useState } from 'react';
import { ScrollView, View, Text } from 'react-native';
import { Button, TextInput } from 'react-native-paper';
import React, { useState } from "react";
import { ScrollView, View, Text } from "react-native";
import { TextInput } from "react-native-paper";
import { addAccountFromHDPath } from '../utils/accounts';
import { Account, NetworksDataState, PathState } from '../types';
import styles from '../styles/stylesheet';
import { useAccounts } from '../context/AccountsContext';
import { addAccountFromHDPath } from "../utils/accounts";
import { Account, NetworksDataState, PathState } from "../types";
import styles from "../styles/stylesheet";
import { useAccounts } from "../context/AccountsContext";
import { LoadingButton } from "@mui/lab";
const HDPath = ({
pathCode,
@@ -21,19 +22,19 @@ const HDPath = ({
const { setCurrentIndex } = useAccounts();
const [isAccountCreating, setIsAccountCreating] = useState(false);
const [path, setPath] = useState<PathState>({
firstNumber: '',
secondNumber: '',
thirdNumber: '',
firstNumber: "",
secondNumber: "",
thirdNumber: "",
});
const handleChange = (key: keyof PathState, value: string) => {
if (key === 'secondNumber' && value !== '' && !['0', '1'].includes(value)) {
if (key === "secondNumber" && value !== "" && !["0", "1"].includes(value)) {
return;
}
setPath({
...path,
[key]: value.replace(/[^0-9]/g, ''),
[key]: value.replace(/[^0-9]/g, ""),
});
};
@@ -50,7 +51,7 @@ const HDPath = ({
hideDialog();
}
} catch (error) {
console.error('Error creating account:', error);
console.error("Error creating account:", error);
} finally {
setIsAccountCreating(false);
}
@@ -63,15 +64,15 @@ const HDPath = ({
<TextInput
keyboardType="numeric"
mode="outlined"
onChangeText={text => handleChange('firstNumber', text)}
onChangeText={(text) => handleChange("firstNumber", text)}
value={path.firstNumber}
style={styles.HDtextInput}
/>
<Text style={styles.HDtext}>'/</Text>
<Text style={styles.HDtext}>{"'/"}</Text>
<TextInput
keyboardType="numeric"
mode="outlined"
onChangeText={text => handleChange('secondNumber', text)}
onChangeText={(text) => handleChange("secondNumber", text)}
value={path.secondNumber}
style={styles.HDtextInput}
/>
@@ -79,19 +80,20 @@ const HDPath = ({
<TextInput
keyboardType="numeric"
mode="outlined"
onChangeText={text => handleChange('thirdNumber', text)}
onChangeText={(text) => handleChange("thirdNumber", text)}
value={path.thirdNumber}
style={styles.HDtextInput}
/>
</View>
<View style={styles.HDbuttonContainer}>
<Button
mode="contained"
onPress={createFromHDPathHandler}
<LoadingButton
variant="contained"
onClick={createFromHDPathHandler}
loading={isAccountCreating}
disabled={isAccountCreating}>
{isAccountCreating ? 'Adding' : 'Add Account'}
</Button>
disabled={isAccountCreating}
>
{isAccountCreating ? "Adding" : "Add Account"}
</LoadingButton>
</View>
</ScrollView>
);
+113
View File
@@ -0,0 +1,113 @@
import {
Button,
Divider,
Link,
Stack,
SvgIcon,
Typography,
} from "@mui/material";
import React from "react";
import { useNavigation } from "@react-navigation/native";
import { Image } from "react-native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { StackParamsList } from "../types";
import styles from "../styles/stylesheet";
const WCLogo = () => {
return (
<Image
style={styles.walletConnectLogo}
source={require("../assets/WalletConnect-Icon-Blueberry.png")}
/>
);
};
export const Header: React.FC<{
title: string;
showWalletConnect?: boolean;
}> = ({ title, showWalletConnect }) => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
return (
<Stack
direction="row"
sx={{
backgroundColor: "background.paper",
pl: 2,
alignItems: "center",
py: 1,
justifyContent: "space-between",
}}
spacing={1}
>
<Stack direction="row">
<Button
component={Link}
onClick={() => {
navigation.navigate("Home");
}}
>
<SvgIcon sx={{ height: 20, width: 100 }}>
<svg
width="115"
height="20"
viewBox="0 0 115 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.37388 10.5194C5.70149 8.19185 7.14225 4.97748 7.1416 1.42853C7.14246 0.94681 7.11586 0.470456 7.063 0L-0.000488281 0.000643078L-0.000273922 13.5723C-0.000917354 15.2174 0.62632 16.863 1.88091 18.1175C3.1356 19.3721 4.78235 20.0001 6.42772 19.9993L6.42729 19.9997L19.9995 20L19.999 12.9355C19.5296 12.8838 19.0532 12.857 18.5704 12.8569C15.0224 12.8574 11.8079 14.298 9.48026 16.6255C7.78654 18.2768 5.07093 18.2771 3.39812 16.6043C1.72638 14.9325 1.72562 12.2161 3.37388 10.5194ZM18.5344 1.46863C16.5837 -0.481929 13.4146 -0.48268 11.4633 1.46863C9.512 3.41984 9.51276 6.58895 11.4633 8.53941C13.415 10.491 16.5831 10.4907 18.5344 8.53941C20.4857 6.5882 20.4861 3.42016 18.5344 1.46863Z"
fill="#FBFBFB"
/>
<path
d="M31.4741 18.5838H39.2552V16.3302H34.075V1.41351H31.4741V18.5838Z"
fill="#FBFBFB"
/>
<path
d="M49.8108 1.41351H45.4976L40.9893 18.5838H43.6769L44.8039 14.2913H50.3744L51.5014 18.5838H54.3191L49.8108 1.41351ZM45.3458 12.145L47.6 3.2593H47.6866L49.8541 12.145H45.3458Z"
fill="#FBFBFB"
/>
<path
d="M62.9292 8.06885H65.9636C65.9636 3.17534 64.3813 1.07196 60.6967 1.07196C56.8169 1.07196 55.1479 3.73341 55.1479 9.97909C55.1479 16.2462 56.8169 18.9291 60.6967 18.9291C64.3813 18.9291 65.9636 16.8901 65.9853 12.1468H62.9508C62.9292 15.8599 62.474 16.7828 60.6967 16.7828C58.6593 16.7828 58.1607 15.4307 58.1824 9.97909C58.1824 4.54896 58.6809 3.19678 60.6967 3.21823C62.474 3.21823 62.9292 4.18413 62.9292 8.06885Z"
fill="#FBFBFB"
/>
<path
d="M73.7781 1.07209C77.7229 1.09364 79.4135 3.77643 79.4135 10.0007C79.4135 16.2249 77.7229 18.9078 73.7781 18.9292C69.8117 18.9507 68.1211 16.2678 68.1211 10.0007C68.1211 3.73354 69.8117 1.05064 73.7781 1.07209ZM71.1555 10.0007C71.1555 15.4308 71.6757 16.783 73.7781 16.783C75.8589 16.783 76.3791 15.4308 76.3791 10.0007C76.3791 4.54909 75.8589 3.19691 73.7781 3.21847C71.6757 3.23992 71.1555 4.59209 71.1555 10.0007Z"
fill="#FBFBFB"
/>
<path
d="M85.0819 18.5624L82.481 18.5838V1.41351H87.0544L91.3243 15.4073H91.3676V1.41351H93.968V18.5838H89.677L85.1254 3.51689H85.0819V18.5624Z"
fill="#FBFBFB"
/>
<path
d="M100.468 1.41351H97.8677V18.5838H100.468V1.41351Z"
fill="#FBFBFB"
/>
<path
d="M111.139 8.06885H114.174C114.174 3.17534 112.591 1.07196 108.906 1.07196C105.028 1.07196 103.358 3.73341 103.358 9.97909C103.358 16.2462 105.028 18.9291 108.906 18.9291C112.591 18.9291 114.174 16.8901 114.195 12.1468H111.161C111.139 15.8599 110.684 16.7828 108.906 16.7828C106.869 16.7828 106.371 15.4307 106.393 9.97909C106.393 4.54896 106.891 3.19678 108.906 3.21823C110.684 3.21823 111.139 4.18413 111.139 8.06885Z"
fill="#FBFBFB"
/>
</svg>
</SvgIcon>
</Button>
<Divider
flexItem
orientation="vertical"
color="#FBFBFB"
sx={{ height: "20px", width: "1px", alignSelf: "center" }}
/>
<Typography fontSize="1.25rem" sx={{ paddingLeft: 1 }}>
{title}
</Typography>
</Stack>
{showWalletConnect && (
<Button onClick={() => navigation.navigate("WalletConnect")}>
{<WCLogo />}
</Button>
)}
</Stack>
);
};
+84
View File
@@ -0,0 +1,84 @@
import React, { useEffect, useState } from "react";
import styles from "../styles/stylesheet";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
TextField,
Grid,
Button,
Typography,
} from "@mui/material";
const ImportWalletDialog = ({
visible,
hideDialog,
importWalletHandler,
}: {
visible: boolean;
hideDialog: () => void;
importWalletHandler: (recoveryPhrase: string) => Promise<void>;
}) => {
const [words, setWords] = useState(Array(12).fill(""));
const handleWordChange = (index: number, value: string) => {
const newWords = [...words];
newWords[index] = value;
setWords(newWords);
};
const handlePaste = (event: React.ClipboardEvent<HTMLDivElement>) => {
const pastedText = event.clipboardData.getData("Text");
const splitWords = pastedText.trim().split(/\s+/);
if (splitWords.length === 12) {
setWords(splitWords);
event.preventDefault();
}
};
useEffect(() => {
setWords(Array(12).fill(""));
}, [visible]);
return (
<Dialog open={visible} onClose={hideDialog}>
<DialogTitle style={styles.mnemonicTitle}>
Import your wallet from your mnemonic
</DialogTitle>
<DialogContent style={styles.mnemonicContainer}>
<Typography component="div">
(You can paste your entire mnemonic into the first textbox)
</Typography>
<Grid container spacing={2}>
{words.map((word, index) => (
<Grid item xs={6} sm={4} key={index}>
<TextField
value={word}
onChange={(e) => handleWordChange(index, e.target.value)}
onPaste={index === 0 ? handlePaste : undefined}
placeholder={`Word ${index + 1}`}
fullWidth
margin="normal"
/>
</Grid>
))}
</Grid>
</DialogContent>
<DialogActions style={styles.mnemonicButtonRow}>
<Button
onClick={() => importWalletHandler(words.join(" "))}
style={styles.mnemonicButton}
>
Import Wallet
</Button>
<Button onClick={hideDialog} style={styles.mnemonicButton}>
Cancel
</Button>
</DialogActions>
</Dialog>
);
};
export default ImportWalletDialog;
+34
View File
@@ -0,0 +1,34 @@
import { Button, Typography } from "@mui/material";
import React, { PropsWithChildren } from "react";
import { Container } from "./Container";
import { ArrowBack } from "@mui/icons-material";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useNavigation } from "@react-navigation/native";
import { StackParamsList } from "../types";
export const Layout: React.FC<PropsWithChildren<{ title: string }>> = ({
children,
title,
}) => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
return (
<Container
boxProps={{ sx: { backgroundColor: "inherit", padding: 0, mt: 3 } }}
>
<Button
startIcon={<ArrowBack />}
color="info"
sx={{ mb: 4 }}
onClick={() => navigation.navigate("Home")}
>
Home
</Button>
<Typography variant="h4" sx={{ mb: 4 }}>
{title}
</Typography>
<Container>{children}</Container>
</Container>
);
};
+51
View File
@@ -0,0 +1,51 @@
import React from "react";
import {
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Button,
Typography,
} from "@mui/material";
import styles from "../styles/stylesheet";
import GridView from "./Grid";
import { CustomDialogProps } from "../types";
const MnemonicDialog = ({
visible,
hideDialog,
contentText,
}: CustomDialogProps) => {
const words = contentText.split(" ");
const copyMnemonic = () => {
navigator.clipboard.writeText(contentText);
};
return (
<Dialog open={visible} onClose={hideDialog}>
<DialogTitle style={{ ...styles.mnemonicTitle }}>Mnemonic</DialogTitle>
<DialogContent style={{ ...styles.mnemonicContainer }}>
<Typography component="div">
Your mnemonic provides full access to your wallet and funds. <br />
Make sure to note it down.
</Typography>
<Typography component="div" style={styles.mnemonicDialogWarning}>
Do not share your mnemonic with anyone
</Typography>
<GridView words={words} />
</DialogContent>
<DialogActions style={{ ...styles.mnemonicButtonRow }}>
<Button style={{ ...styles.mnemonicButton }} onClick={copyMnemonic}>
Copy
</Button>
<Button style={{ ...styles.mnemonicButton }} onClick={hideDialog}>
Done
</Button>
</DialogActions>
</Dialog>
);
};
export { MnemonicDialog };
+15 -12
View File
@@ -1,10 +1,12 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { List } from 'react-native-paper';
import React, { useState } from "react";
import { View } from "react-native";
import { List } from "react-native-paper";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { NetworkDropdownProps, NetworksDataState } from '../types';
import styles from '../styles/stylesheet';
import { useNetworks } from '../context/NetworksContext';
import { NetworkDropdownProps, NetworksDataState } from "../types";
import styles from "../styles/stylesheet";
import { useNetworks } from "../context/NetworksContext";
import { Accordion, AccordionSummary } from "@mui/material";
const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
@@ -19,18 +21,19 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
return (
<View style={styles.networkDropdown}>
<List.Accordion
title={selectedNetwork!.networkName}
expanded={expanded}
onPress={() => setExpanded(!expanded)}>
{networksData.map(networkData => (
<Accordion expanded={expanded} onClick={() => setExpanded(!expanded)}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
{selectedNetwork!.networkName}
</AccordionSummary>
{networksData.map((networkData) => (
<List.Item
key={networkData.networkId}
title={networkData.networkName}
onPress={() => handleNetworkPress(networkData)}
/>
))}
</List.Accordion>
</Accordion>
</View>
);
};
+1 -3
View File
@@ -19,7 +19,7 @@ const PairingModal = ({
setModalVisible,
setToastVisible,
}: PairingModalProps) => {
const { accounts, currentIndex } = useAccounts();
const { accounts } = useAccounts();
const { selectedNetwork, networksData } = useNetworks();
const [isLoading, setIsLoading] = useState(false);
const [chainError, setChainError] = useState('');
@@ -101,7 +101,6 @@ const PairingModal = ({
networksData,
selectedNetwork!,
accounts,
currentIndex,
);
setSupportedNamespaces(nameSpaces);
} catch (err) {
@@ -127,7 +126,6 @@ const PairingModal = ({
networksData,
selectedNetwork,
accounts,
currentIndex,
setCurrentProposal,
setModalVisible,
web3wallet
+21 -32
View File
@@ -1,16 +1,15 @@
import React, { useState } from 'react';
import { TouchableOpacity, View } from 'react-native';
import { Button, Typography } from '@mui/material';
import Dialog from '@mui/material/Dialog';
import DialogTitle from '@mui/material/DialogTitle';
import DialogContent from '@mui/material/DialogContent';
import DialogActions from '@mui/material/DialogActions';
import React, { useState } from "react";
import { TouchableOpacity, View } from "react-native";
import { Button, Link, Typography } from "@mui/material";
import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent";
import DialogActions from "@mui/material/DialogActions";
import styles from '../styles/stylesheet';
import { getPathKey } from '../utils/misc';
import { useNetworks } from '../context/NetworksContext';
import { useAccounts } from '../context/AccountsContext';
import { Text, useTheme } from 'react-native-paper';
import styles from "../styles/stylesheet";
import { getPathKey } from "../utils/misc";
import { useNetworks } from "../context/NetworksContext";
import { useAccounts } from "../context/AccountsContext";
const ShowPKDialog = () => {
const { currentIndex } = useAccounts();
@@ -19,8 +18,6 @@ const ShowPKDialog = () => {
const [privateKey, setPrivateKey] = useState<string>();
const [showPKDialog, setShowPKDialog] = useState<boolean>(false);
const theme = useTheme();
const handleShowPrivateKey = async () => {
const pathKey = await getPathKey(
`${selectedNetwork!.namespace}:${selectedNetwork!.chainId}`,
@@ -41,12 +38,9 @@ const ShowPKDialog = () => {
<TouchableOpacity
onPress={() => {
setShowPKDialog(true);
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Show Private Key
</Text>
}}
>
<Link>Show Private Key</Link>
</TouchableOpacity>
</View>
<View>
@@ -66,8 +60,8 @@ const ShowPKDialog = () => {
variant="body1"
style={styles.dataBoxData}
sx={{
wordWrap: 'break-word',
whiteSpace: 'initial',
wordWrap: "break-word",
whiteSpace: "initial"
}}
>
{privateKey}
@@ -76,24 +70,19 @@ const ShowPKDialog = () => {
)}
<View>
<Typography variant="body1" style={styles.dialogWarning}>
<Typography component="span">
Warning:
</Typography>
Never disclose this key. Anyone with your private keys can
steal any assets held in your account.
Warning: Never disclose this key. Anyone with your private keys can steal
any assets held in your account.
</Typography>
</View>
</DialogContent>
<DialogActions>
{!privateKey ? (
<>
<Button onClick={handleShowPrivateKey} color="error">
Yes
</Button>
<Button onClick={hideShowPKDialog}>No</Button>
<Button style={{...styles.buttonRed, ...styles.button}} onClick={handleShowPrivateKey}>Yes</Button>
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideShowPKDialog}>No</Button>
</>
) : (
<Button onClick={hideShowPKDialog}>Ok</Button>
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideShowPKDialog}>Ok</Button>
)}
</DialogActions>
</Dialog>
+7 -7
View File
@@ -4,15 +4,15 @@ import { Account } from '../types';
const AccountsContext = createContext<{
accounts: Account[];
setAccounts: (account: Account[]) => void;
setAccounts: React.Dispatch<React.SetStateAction<Account[]>>;
currentIndex: number;
setCurrentIndex: (index: number) => void;
}>({
accounts: [],
setAccounts: () => {},
currentIndex: 0,
setCurrentIndex: () => {},
});
}>({
accounts: [],
setAccounts: () => {},
currentIndex: 0,
setCurrentIndex: () => {},
});
const useAccounts = () => {
const accountsContext = useContext(AccountsContext);
+32 -21
View File
@@ -1,8 +1,9 @@
import React, { createContext, useContext, useEffect, useState } from 'react';
import { NetworksDataState } from '../types';
import { retrieveNetworksData, storeNetworkData } from '../utils/accounts';
import { retrieveNetworksData } from '../utils/accounts';
import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants';
import { setInternetCredentials } from '../utils/key-store';
const NetworksContext = createContext<{
networksData: NetworksDataState[];
@@ -13,42 +14,52 @@ const NetworksContext = createContext<{
setSelectedNetwork: React.Dispatch<
React.SetStateAction<NetworksDataState | undefined>
>;
}>({
networksData: [],
setNetworksData: () => {},
networkType: '',
setNetworkType: () => {},
selectedNetwork: {} as NetworksDataState,
setSelectedNetwork: () => {},
});
}>({
networksData: [],
setNetworksData: () => {},
networkType: '',
setNetworkType: () => {},
selectedNetwork: {} as NetworksDataState,
setSelectedNetwork: () => {},
});
const useNetworks = () => {
const networksContext = useContext(NetworksContext);
return networksContext;
};
const DEFAULT_NETWORKS_DATA = DEFAULT_NETWORKS.map((defaultNetwork, index) => (
{
...defaultNetwork,
networkId: index.toString()
})
);
const NetworksProvider = ({ children }: { children: React.ReactNode }) => {
const [networksData, setNetworksData] = useState<NetworksDataState[]>([]);
const [networksData, setNetworksData] = useState<NetworksDataState[]>(DEFAULT_NETWORKS_DATA);
const [networkType, setNetworkType] = useState<string>(EIP155);
const [selectedNetwork, setSelectedNetwork] = useState<NetworksDataState>();
useEffect(() => {
const fetchData = async () => {
const retrievedNetworks = await retrieveNetworksData();
let retrievedNetworks = await retrieveNetworksData();
if (retrievedNetworks.length === 0) {
for (const defaultNetwork of DEFAULT_NETWORKS) {
await storeNetworkData(defaultNetwork);
}
setInternetCredentials(
'networks',
'_',
JSON.stringify(DEFAULT_NETWORKS_DATA),
);
retrievedNetworks = DEFAULT_NETWORKS_DATA;
}
const retrievedNewNetworks = await retrieveNetworksData();
setNetworksData(retrievedNewNetworks);
setSelectedNetwork(retrievedNewNetworks[0]);
setNetworksData(retrievedNetworks);
setSelectedNetwork(retrievedNetworks[0]);
};
if (networksData.length === 0) {
fetchData();
}
}, [networksData]);
fetchData();
}, []);
useEffect(() => {
setSelectedNetwork(prevSelectedNetwork => {
+24
View File
@@ -0,0 +1,24 @@
// Extends the Window interface for Android WebView communication
declare global {
interface Window {
// Android bridge callbacks for signature and accounts related events
Android?: {
// Called when signature is successfully generated
onSignatureComplete?: (signature: string) => void;
// Called when signature generation fails
onSignatureError?: (error: string) => void;
// Called when signature process is cancelled
onSignatureCancelled?: () => void;
// Called when accounts are ready for use
onAccountsReady?: () => void;
};
// Handles incoming signature requests from Android
receiveSignRequestFromAndroid?: (message: string) => void;
}
}
export {};
+23
View File
@@ -0,0 +1,23 @@
import { useCallback } from "react";
import { retrieveAccounts } from "../utils/accounts";
import { useNetworks } from "../context/NetworksContext";
const useAccountsData = () => {
const { networksData } = useNetworks();
const getAccountsData = useCallback(async (chainId: string) => {
const targetNetwork = networksData.find(network => network.chainId === chainId);
if (!targetNetwork) {
return [];
}
const accounts = await retrieveAccounts(targetNetwork);
return accounts || [];
}, [networksData]);
return { getAccountsData };
};
export default useAccountsData;
+58
View File
@@ -0,0 +1,58 @@
import { useEffect, useCallback } from 'react';
import { useNetworks } from '../context/NetworksContext';
import { sendMessage } from '../utils/misc';
import useAccountsData from '../hooks/useAccountsData';
import { addAccount } from '../utils/accounts';
import { useAccounts } from '../context/AccountsContext';
import { Account, NetworksDataState } from '../types';
const REACT_APP_ALLOWED_URLS = process.env.REACT_APP_ALLOWED_URLS;
const useAddAccountEmbed = () => {
const { networksData } = useNetworks();
const { setAccounts, setCurrentIndex } = useAccounts();
const { getAccountsData } = useAccountsData();
const addAccountHandler = useCallback(async (network: NetworksDataState) => {
const newAccount = await addAccount(network);
if (newAccount) {
setAccounts(prev => [...prev, newAccount]);
setCurrentIndex(newAccount.index);
}
}, [setAccounts, setCurrentIndex]);
useEffect(() => {
const handleAddAccount = async (event: MessageEvent) => {
if (event.data.type !== 'ADD_ACCOUNT') return;
if (!REACT_APP_ALLOWED_URLS) {
console.log('Unauthorized app origin:', event.origin);
return;
}
const allowedUrls = REACT_APP_ALLOWED_URLS.split(',').map(url => url.trim());
if (!allowedUrls.includes(event.origin)) {
console.log('Unauthorized app.');
return;
}
const network = networksData.find(network => network.chainId === event.data.chainId);
await addAccountHandler(network!);
const updatedAccounts = await getAccountsData(event.data.chainId);
const addresses = updatedAccounts.map((account: Account) => account.address);
sendMessage(event.source as Window, 'ADD_ACCOUNT_RESPONSE', addresses, event.origin);
};
window.addEventListener('message', handleAddAccount);
return () => {
window.removeEventListener('message', handleAddAccount);
};
}, [networksData, getAccountsData, addAccountHandler]);
};
export default useAddAccountEmbed;
+42
View File
@@ -0,0 +1,42 @@
import { useEffect } from 'react';
import { useAccounts } from '../context/AccountsContext';
import { getPathKey, sendMessage } from '../utils/misc';
const useExportPKEmbed = () => {
const { accounts } = useAccounts();
useEffect(() => {
const handleMessage = async (event: MessageEvent) => {
const { type, chainId, address } = event.data;
if (type !== 'REQUEST_ACCOUNT_PK') return;
try {
const selectedAccount = accounts.find(account => account.address === address);
if (!selectedAccount) {
throw new Error("Account not found")
}
const pathKey = await getPathKey(chainId, selectedAccount.index);
const privateKey = pathKey.privKey;
sendMessage(
event.source as Window,
'ACCOUNT_PK_DATA',
{ privateKey },
event.origin,
);
} catch (error) {
console.error('Error fetching private key:', error);
}
};
window.addEventListener('message', handleMessage);
return () => {
window.removeEventListener('message', handleMessage);
};
}, [accounts]);
};
export default useExportPKEmbed;
+90
View File
@@ -0,0 +1,90 @@
import { useEffect, useCallback } from "react";
import { createWallet } from "../utils/accounts";
import { sendMessage } from "../utils/misc";
import useAccountsData from "./useAccountsData";
import { useNetworks } from "../context/NetworksContext";
import { useAccounts } from "../context/AccountsContext";
const REACT_APP_ALLOWED_URLS = process.env.REACT_APP_ALLOWED_URLS;
const useGetOrCreateAccounts = () => {
const { networksData } = useNetworks();
const { getAccountsData } = useAccountsData();
const { setAccounts } = useAccounts();
// Wrap the function in useCallback to prevent recreation on each render
const getOrCreateAccountsForChain = useCallback(async (chainId: string) => {
let accountsData = await getAccountsData(chainId);
if (accountsData.length === 0) {
console.log("Accounts not found, creating wallet...");
await createWallet(networksData);
accountsData = await getAccountsData(chainId);
}
// Update the AccountsContext with the new accounts
setAccounts(accountsData);
return accountsData;
}, [networksData, getAccountsData, setAccounts]);
useEffect(() => {
const handleCreateAccounts = async (event: MessageEvent) => {
if (event.data.type !== 'REQUEST_CREATE_OR_GET_ACCOUNTS') return;
if (!REACT_APP_ALLOWED_URLS) {
console.log('Allowed URLs are not set');
return;
}
const allowedUrls = REACT_APP_ALLOWED_URLS.split(',').map(url => url.trim());
if (!allowedUrls.includes(event.origin)) {
console.log('Unauthorized app.');
return;
}
const accountsData = await getOrCreateAccountsForChain(event.data.chainId);
const accountsAddressList = accountsData.map(account => account.address);
console.log('Sending WALLET_ACCOUNTS_DATA accounts:', accountsAddressList);
sendMessage(
event.source as Window, 'WALLET_ACCOUNTS_DATA',
accountsAddressList,
event.origin
);
};
const autoCreateAccounts = async () => {
const defaultChainId = networksData[0]?.chainId;
if (!defaultChainId) {
console.log('useGetOrCreateAccounts: No default chainId found');
return;
}
const accounts = await getOrCreateAccountsForChain(defaultChainId);
// Only notify Android when we actually have accounts
if (accounts.length > 0 && window.Android?.onAccountsReady) {
window.Android.onAccountsReady();
} else {
console.log('No accounts created or Android bridge not available');
}
};
window.addEventListener('message', handleCreateAccounts);
const isAndroidWebView = !!(window.Android);
if (isAndroidWebView) {
autoCreateAccounts();
}
return () => {
window.removeEventListener('message', handleCreateAccounts);
};
}, [networksData, getAccountsData, getOrCreateAccountsForChain]);
};
export default useGetOrCreateAccounts;
+81
View File
@@ -0,0 +1,81 @@
import { useEffect, useCallback } from 'react';
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useAccounts } from '../context/AccountsContext';
import { useNetworks } from '../context/NetworksContext';
import { StackParamsList } from '../types';
import useGetOrCreateAccounts from './useGetOrCreateAccounts';
export const useWebViewHandler = () => {
// Navigation and context hooks
const navigation = useNavigation<NativeStackNavigationProp<StackParamsList>>();
const { selectedNetwork } = useNetworks();
const { accounts, currentIndex } = useAccounts();
// Initialize accounts
useGetOrCreateAccounts();
// Core navigation handler
const navigateToSignRequest = useCallback((message: string) => {
try {
// Validation checks
if (!selectedNetwork?.namespace || !selectedNetwork?.chainId) {
window.Android?.onSignatureError?.('Invalid network configuration');
return;
}
if (!accounts?.length) {
window.Android?.onSignatureError?.('No accounts available');
return;
}
const currentAccount = accounts[currentIndex];
if (!currentAccount) {
window.Android?.onSignatureError?.('Current account not found');
return;
}
// Create the path and validate with regex
const path = `/sign/${selectedNetwork.namespace}/${selectedNetwork.chainId}/${currentAccount.address}/${encodeURIComponent(message)}`;
const pathRegex = /^\/sign\/(eip155|cosmos)\/(.+)\/(.+)\/(.+)$/;
const match = path.match(pathRegex);
if (!match) {
window.Android?.onSignatureError?.('Invalid signing path');
return;
}
const [, pathNamespace, pathChainId, pathAddress, pathMessage] = match;
// Reset navigation stack and navigate to sign request
navigation.reset({
index: 0,
routes: [
{
name: 'SignRequest',
path,
params: {
namespace: pathNamespace,
chainId: pathChainId,
address: pathAddress,
message: decodeURIComponent(pathMessage),
accountInfo: currentAccount,
},
},
],
});
} catch (error) {
window.Android?.onSignatureError?.(`Navigation error: ${error}`);
}
}, [selectedNetwork, accounts, currentIndex, navigation]);
useEffect(() => {
// Assign the function to the window object
window.receiveSignRequestFromAndroid = navigateToSignRequest;
return () => {
window.receiveSignRequestFromAndroid = undefined;
};
}, [navigateToSignRequest]); // Only the function reference as dependency
};
+120 -38
View File
@@ -1,59 +1,141 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import { PaperProvider, MD3LightTheme as DefaultTheme, } from 'react-native-paper';
import { NavigationContainer } from '@react-navigation/native';
import { Platform } from 'react-native';
import { Buffer } from 'buffer';
import React from "react";
import ReactDOM from "react-dom/client";
import {
PaperProvider,
MD3DarkTheme as DefaultTheme,
} from "react-native-paper";
import { NavigationContainer, DarkTheme } from "@react-navigation/native";
import { Platform } from "react-native";
import { Buffer } from "buffer";
import './index.css';
import App from './App';
import { AccountsProvider } from './context/AccountsContext';
import { NetworksProvider } from './context/NetworksContext';
import reportWebVitals from './reportWebVitals';
import { WalletConnectProvider } from './context/WalletConnectContext';
import "./index.css";
import App from "./App";
import { AccountsProvider } from "./context/AccountsContext";
import { NetworksProvider } from "./context/NetworksContext";
import reportWebVitals from "./reportWebVitals";
import { WalletConnectProvider } from "./context/WalletConnectContext";
import { createTheme, ThemeProvider } from "@mui/material";
globalThis.Buffer = Buffer;
const linking = {
prefixes: ['https://wallet.laconic.com']
prefixes: ["https://wallet.laconic.com"],
};
const theme = {
...DefaultTheme,
dark: false,
dark: true,
};
const navigationTheme: typeof DarkTheme = {
...DarkTheme,
colors: {
...DarkTheme.colors,
primary: "#0000F4",
background: "#0F0F0F",
card: "#18181A",
},
};
const muiTheme = createTheme({
components: {
MuiAccordion: {
defaultProps: {
sx: {
border: "1px solid #48474F",
borderBottomRightRadius: 3,
borderBottomLeftRadius: 3,
},
},
},
MuiButton: {
defaultProps: {
color: "primary",
sx: {
fontFamily: `DM Mono, monospace`,
fontWeight: 400,
},
},
},
MuiLink: {
defaultProps: {
color: "text.primary",
fontSize: "14px",
},
},
MuiTypography: {
defaultProps: {
color: "text.primary",
fontWeight: 400,
},
},
MuiPaper: {
defaultProps: {
sx: {
backgroundImage: "none",
},
},
},
},
palette: {
mode: "dark",
primary: {
main: "#0000F4",
},
secondary: {
main: "#A2A2FF",
},
error: {
main: "#B20710",
},
background: {
default: "#0F0F0F",
paper: "#18181A",
},
text: {
primary: "#FBFBFB",
},
info: {
main: "#FBFBFB",
},
},
});
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
document.getElementById("root") as HTMLElement,
);
root.render(
<PaperProvider theme={theme}>
<NetworksProvider>
<AccountsProvider>
<WalletConnectProvider>
<NavigationContainer
linking={linking}
documentTitle={{
formatter: () =>
`Laconic Wallet`,
}}
>
<React.Fragment>
{Platform.OS === 'web' ? (
<style type="text/css">{`
<div id="app">
<PaperProvider theme={theme}>
<NetworksProvider>
<AccountsProvider>
<WalletConnectProvider>
<NavigationContainer
linking={linking}
documentTitle={{
formatter: () => `Laconic | Wallet`,
}}
theme={navigationTheme}
>
<React.Fragment>
{Platform.OS === "web" ? (
<style type="text/css">{`
@font-face {
font-family: 'MaterialCommunityIcons';
src: url(${require('react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf')}) format('truetype');
src: url(${require("react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf")}) format('truetype');
}
`}</style>
) : null}
<App />
</React.Fragment>
</NavigationContainer>
</WalletConnectProvider>
</AccountsProvider>
</NetworksProvider>
</PaperProvider>
) : null}
<ThemeProvider theme={muiTheme}>
<App />
</ThemeProvider>
</React.Fragment>
</NavigationContainer>
</WalletConnectProvider>
</AccountsProvider>
</NetworksProvider>
</PaperProvider>
</div>,
);
// If you want to start measuring performance in your app, pass a function
+231 -201
View File
@@ -1,21 +1,20 @@
import React, { useCallback, useEffect, useState } from 'react';
import { View } from 'react-native';
import { useForm, Controller, useWatch, FieldErrors } from 'react-hook-form';
import { TextInput, Button, HelperText } from 'react-native-paper';
import React, { useCallback, useEffect, useState } from "react";
import { useForm, Controller, useWatch, FieldErrors } from "react-hook-form";
import { TextInput, HelperText } from "react-native-paper";
import { HDNode } from 'ethers/lib/utils';
import { chains } from 'chain-registry';
import { useDebouncedCallback } from 'use-debounce';
import { z } from 'zod';
import { HDNode } from "ethers/lib/utils";
import { chains } from "chain-registry";
import { useDebouncedCallback } from "use-debounce";
import { z } from "zod";
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useNavigation } from '@react-navigation/native';
import { zodResolver } from '@hookform/resolvers/zod';
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { useNavigation } from "@react-navigation/native";
import { zodResolver } from "@hookform/resolvers/zod";
import { StackParamsList } from '../types';
import { SelectNetworkType } from '../components/SelectNetworkType';
import { storeNetworkData } from '../utils/accounts';
import { useNetworks } from '../context/NetworksContext';
import { StackParamsList } from "../types";
import { SelectNetworkType } from "../components/SelectNetworkType";
import { storeNetworkData } from "../utils/accounts";
import { useNetworks } from "../context/NetworksContext";
import {
COSMOS,
EIP155,
@@ -23,14 +22,16 @@ import {
EMPTY_FIELD_ERROR,
INVALID_URL_ERROR,
IS_NUMBER_REGEX,
} from '../utils/constants';
import { getCosmosAccounts } from '../utils/accounts';
import ETH_CHAINS from '../assets/ethereum-chains.json';
} from "../utils/constants";
import { getCosmosAccounts } from "../utils/accounts";
import ETH_CHAINS from "../assets/ethereum-chains.json";
import {
getInternetCredentials,
setInternetCredentials,
} from '../utils/key-store';
import styles from '../styles/stylesheet';
} from "../utils/key-store";
import { Divider, Grid } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { Layout } from "../components/Layout";
const ethNetworkDataSchema = z.object({
chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
@@ -39,7 +40,7 @@ const ethNetworkDataSchema = z.object({
blockExplorerUrl: z
.string()
.url({ message: INVALID_URL_ERROR })
.or(z.literal('')),
.or(z.literal("")),
coinType: z
.string()
.nonempty({ message: EMPTY_FIELD_ERROR })
@@ -54,7 +55,7 @@ const cosmosNetworkDataSchema = z.object({
blockExplorerUrl: z
.string()
.url({ message: INVALID_URL_ERROR })
.or(z.literal('')),
.or(z.literal("")),
coinType: z
.string()
.nonempty({ message: EMPTY_FIELD_ERROR })
@@ -85,13 +86,13 @@ const AddNetwork = () => {
setValue,
reset,
} = useForm<z.infer<typeof networksFormDataSchema>>({
mode: 'onChange',
mode: "onChange",
resolver: zodResolver(networksFormDataSchema),
});
const watchChainId = useWatch({
control,
name: 'chainId',
name: "chainId",
});
const updateNetworkType = (newNetworkType: string) => {
@@ -101,16 +102,16 @@ const AddNetwork = () => {
const fetchChainDetails = useDebouncedCallback((chainId: string) => {
if (namespace === EIP155) {
const ethChainDetails = ETH_CHAINS.find(
chain => chain.chainId === Number(chainId),
(chain) => chain.chainId === Number(chainId),
);
if (!ethChainDetails) {
return;
}
setValue('networkName', ethChainDetails.name);
setValue('rpcUrl', ethChainDetails.rpc[0]);
setValue('blockExplorerUrl', ethChainDetails.explorers?.[0].url || '');
setValue('coinType', String(ethChainDetails.slip44 ?? '60'));
setValue('currencySymbol', ethChainDetails.nativeCurrency.symbol);
setValue("networkName", ethChainDetails.name);
setValue("rpcUrl", ethChainDetails.rpc[0]);
setValue("blockExplorerUrl", ethChainDetails.explorers?.[0].url || "");
setValue("coinType", String(ethChainDetails.slip44 ?? "60"));
setValue("currencySymbol", ethChainDetails.nativeCurrency.symbol);
return;
}
const cosmosChainDetails = chains.find(
@@ -119,14 +120,14 @@ const AddNetwork = () => {
if (!cosmosChainDetails) {
return;
}
setValue('networkName', cosmosChainDetails.pretty_name);
setValue('rpcUrl', cosmosChainDetails.apis?.rpc?.[0]?.address || '');
setValue('blockExplorerUrl', cosmosChainDetails.explorers?.[0].url || '');
setValue('addressPrefix', cosmosChainDetails.bech32_prefix);
setValue('coinType', String(cosmosChainDetails.slip44 ?? '118'));
setValue('nativeDenom', cosmosChainDetails.fees?.fee_tokens[0].denom || '');
setValue("networkName", cosmosChainDetails.pretty_name);
setValue("rpcUrl", cosmosChainDetails.apis?.rpc?.[0]?.address || "");
setValue("blockExplorerUrl", cosmosChainDetails.explorers?.[0].url || "");
setValue("addressPrefix", cosmosChainDetails.bech32_prefix);
setValue("coinType", String(cosmosChainDetails.slip44 ?? "118"));
setValue("nativeDenom", cosmosChainDetails.fees?.fee_tokens[0].denom || "");
setValue(
'gasPrice',
"gasPrice",
String(
cosmosChainDetails.fees?.fee_tokens[0].average_gas_price ||
String(process.env.DEFAULT_GAS_PRICE),
@@ -142,11 +143,11 @@ const AddNetwork = () => {
isDefault: false,
};
const mnemonicServer = await getInternetCredentials('mnemonicServer');
const mnemonicServer = await getInternetCredentials("mnemonicServer");
const mnemonic = mnemonicServer;
if (!mnemonic) {
throw new Error('Mnemonic not found');
throw new Error("Mnemonic not found");
}
const hdNode = HDNode.fromMnemonic(mnemonic);
@@ -172,7 +173,7 @@ const AddNetwork = () => {
break;
default:
throw new Error('Unsupported namespace');
throw new Error("Unsupported namespace");
}
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
@@ -183,22 +184,22 @@ const AddNetwork = () => {
await Promise.all([
setInternetCredentials(
`accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`,
'_',
"_",
accountInfo,
),
setInternetCredentials(
`addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`,
'_',
'1',
"_",
"1",
),
setInternetCredentials(
`accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`,
'_',
'0',
"_",
"0",
),
]);
navigation.navigate('Home');
navigation.navigate("Home");
},
[navigation, namespace, setNetworksData],
);
@@ -212,208 +213,237 @@ const AddNetwork = () => {
}, [namespace, reset]);
return (
<View style={styles.appContainer}>
<Layout title="Add Network">
<SelectNetworkType updateNetworkType={updateNetworkType} />
<Divider flexItem sx={{ my: 4 }} />
<Controller
control={control}
name="chainId"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Chain ID"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.chainId?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
defaultValue=""
name="networkName"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.networkName?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
name="rpcUrl"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
defaultValue=""
name="blockExplorerUrl"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">
{errors.blockExplorerUrl?.message}
</HelperText>
</>
)}
/>
<Controller
control={control}
name="coinType"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Coin Type"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">{errors.coinType?.message}</HelperText>
</>
)}
/>
{namespace === EIP155 ? (
<Controller
control={control}
name="currencySymbol"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Currency Symbol"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">
{
(errors as FieldErrors<z.infer<typeof ethNetworkDataSchema>>)
.currencySymbol?.message
}
</HelperText>
</>
)}
/>
) : (
<>
<Grid container spacing={2} sx={{ px: 1 }}>
<Grid item xs={6}>
<Controller
control={control}
name="nativeDenom"
name="chainId"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Native Denom"
label="Chain ID"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.chainId?.message}</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
defaultValue=""
name="networkName"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).nativeDenom?.message
}
{errors.networkName?.message}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="addressPrefix"
name="rpcUrl"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Address Prefix"
label="New RPC URL"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
value={value}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
defaultValue=""
name="blockExplorerUrl"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).addressPrefix?.message
}
{errors.blockExplorerUrl?.message}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={namespace === EIP155 ? 12 : 6}>
<Controller
control={control}
name="gasPrice"
name="coinType"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Gas Price"
label="Coin Type"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).gasPrice?.message
}
</HelperText>
<HelperText type="error">{errors.coinType?.message}</HelperText>
</>
)}
/>
</>
)}
<Button
mode="contained"
</Grid>
{namespace === EIP155 ? (
<Grid item xs={12}>
<Controller
control={control}
name="currencySymbol"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Currency Symbol"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof ethNetworkDataSchema>
>
).currencySymbol?.message
}
</HelperText>
</>
)}
/>
</Grid>
) : (
<>
<Grid item xs={6}>
<Controller
control={control}
name="nativeDenom"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Native Denom"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).nativeDenom?.message
}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="addressPrefix"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Address Prefix"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).addressPrefix?.message
}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="gasPrice"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Gas Price"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).gasPrice?.message
}
</HelperText>
</>
)}
/>
</Grid>
</>
)}
</Grid>
<LoadingButton
variant="contained"
loading={isSubmitting}
disabled={isSubmitting}
style={styles.networksButton}
onPress={handleSubmit(submit)}>
{isSubmitting ? 'Adding' : 'Submit'}
</Button>
</View>
onClick={handleSubmit(submit)}
sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }}
>
{isSubmitting ? "Adding" : "Submit"}
</LoadingButton>
</Layout>
);
};
+19 -17
View File
@@ -1,22 +1,24 @@
import React, { useCallback, useState } from 'react';
import { View } from 'react-native';
import { Button, Text, TextInput } from 'react-native-paper';
import React, { useCallback, useState } from "react";
import { View } from "react-native";
import { Text, TextInput } from "react-native-paper";
import { useNavigation } from '@react-navigation/native';
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useNavigation } from "@react-navigation/native";
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
import { Box, Button } from "@mui/material";
import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils';
import styles from '../styles/stylesheet';
import { StackParamsList } from '../types';
import { useWalletConnect } from '../context/WalletConnectContext';
import { web3WalletPair } from "../utils/wallet-connect/WalletConnectUtils";
import styles from "../styles/stylesheet";
import { StackParamsList } from "../types";
import { useWalletConnect } from "../context/WalletConnectContext";
import { Layout } from "../components/Layout";
const AddSession = () => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const [currentWCURI, setCurrentWCURI] = useState<string>('');
const [currentWCURI, setCurrentWCURI] = useState<string>("");
const {web3wallet} = useWalletConnect();
const { web3wallet } = useWalletConnect();
const pair = useCallback(async () => {
if (!web3wallet) {
@@ -24,12 +26,12 @@ const AddSession = () => {
}
const pairing = await web3WalletPair(web3wallet, { uri: currentWCURI });
navigation.navigate('WalletConnect');
navigation.navigate("WalletConnect");
return pairing;
}, [currentWCURI, navigation, web3wallet]);
return (
<View style={styles.appContainer}>
<Layout title="Add Session">
<View style={styles.inputContainer}>
<Text variant="titleMedium">Enter WalletConnect URI</Text>
<TextInput
@@ -41,13 +43,13 @@ const AddSession = () => {
style={styles.walletConnectUriText}
/>
<View style={styles.signButton}>
<Button mode="contained" onPress={pair}>
<Box sx={{ mt: 2 }}>
<Button variant="contained" onClick={pair}>
Pair Session
</Button>
</View>
</Box>
</View>
</View>
</Layout>
);
};
export default AddSession;
+27 -6
View File
@@ -1,13 +1,14 @@
import React, { useCallback, useEffect, useState } from 'react';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Image, ScrollView, View } from 'react-native';
import { Button, Text, TextInput } from 'react-native-paper';
import { MsgCreateValidator } from 'cosmjs-types/cosmos/staking/v1beta1/tx';
import {
NativeStackNavigationProp,
NativeStackScreenProps,
} from '@react-navigation/native-stack';
import { useNavigation } from '@react-navigation/native';
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
import { DirectSecp256k1Wallet, EncodeObject } from '@cosmjs/proto-signing';
import { LaconicClient } from '@cerc-io/registry-sdk';
import { GasPrice, calculateFee } from '@cosmjs/stargate';
import { formatJsonRpcError } from '@json-rpc-tools/utils';
@@ -40,7 +41,6 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
const requestName = requestSession.peer.metadata.name;
const requestIcon = requestSession.peer.metadata.icons[0];
const requestURL = requestSession.peer.metadata.url;
const transactionMessage = route.params.transactionMessage;
const signer = route.params.signer;
const requestEvent = route.params.requestEvent;
const chainId = requestEvent.params.chainId;
@@ -67,6 +67,20 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
const { web3wallet } = useWalletConnect();
const transactionMessage = useMemo((): EncodeObject => {
const inputTxMsg = route.params.transactionMessage;
// If it's a MsgCreateValidator, decode the tx msg value using MsgCreateValidator type
if (inputTxMsg.typeUrl.includes('MsgCreateValidator')) {
return {
typeUrl: inputTxMsg.typeUrl,
value: MsgCreateValidator.fromJSON(inputTxMsg.value)
}
}
return inputTxMsg;
}, [route.params.transactionMessage]);
useEffect(() => {
if (namespace !== COSMOS) {
return;
@@ -138,7 +152,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
return;
}
const gasEstimation = await cosmosStargateClient!.simulate(
transactionMessage.value.participant!,
signer,
[transactionMessage],
MEMO,
);
@@ -160,7 +174,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
}
};
getCosmosGas();
}, [cosmosStargateClient, transactionMessage, requestEventId, topic, web3wallet]);
}, [cosmosStargateClient, transactionMessage, requestEventId, topic, web3wallet, signer]);
useEffect(() => {
const gasPrice = GasPrice.fromString(
@@ -239,6 +253,13 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
navigation.navigate('Home');
};
const replacer = (key: string, value: any): any => {
if (value instanceof Uint8Array) {
return Buffer.from(value).toString('hex');
}
return value;
};
return (
<>
<ScrollView contentContainerStyle={styles.approveTransaction}>
@@ -264,7 +285,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
</Text>
<View style={styles.messageBody}>
<Text variant="bodyLarge">
{JSON.stringify(transactionMessage, null, 2)}
{JSON.stringify(transactionMessage, replacer, 2)}
</Text>
</View>
<>
+58
View File
@@ -0,0 +1,58 @@
import React, { useEffect } from 'react';
import { useNetworks } from '../context/NetworksContext';
import { signMessage } from '../utils/sign-message';
import { EIP155 } from '../utils/constants';
import { sendMessage } from '../utils/misc';
import useAccountsData from '../hooks/useAccountsData';
import useGetOrCreateAccounts from '../hooks/useGetOrCreateAccounts';
const REACT_APP_ALLOWED_URLS = process.env.REACT_APP_ALLOWED_URLS;
export const AutoSignIn = () => {
const { networksData } = useNetworks();
const { getAccountsData } = useAccountsData();
useEffect(() => {
const handleSignIn = async (event: MessageEvent) => {
if (event.data.type !== 'AUTO_SIGN_IN') return;
if (!REACT_APP_ALLOWED_URLS) {
console.log('Allowed URLs are not set');
return;
}
const allowedUrls = REACT_APP_ALLOWED_URLS.split(',').map(url => url.trim());
if (!allowedUrls.includes(event.origin)) {
console.log('Unauthorized app.');
return;
}
const accountsData = await getAccountsData(event.data.chainId);
if (!accountsData.length) {
return
}
const signature = await signMessage({ message: event.data.message, accountId: accountsData[0].index, chainId: event.data.chainId, namespace: EIP155 })
sendMessage(event.source as Window, 'SIGN_IN_RESPONSE', { message: event.data.message, signature }, event.origin);
};
window.addEventListener('message', handleSignIn);
return () => {
window.removeEventListener('message', handleSignIn);
};
}, [networksData, getAccountsData]);
// Custom hook for adding listener to get accounts data
useGetOrCreateAccounts();
return (
<>
</>
)
};
+127 -113
View File
@@ -1,27 +1,28 @@
import React, { useCallback } from 'react';
import { View } from 'react-native';
import { useForm, Controller, FieldErrors } from 'react-hook-form';
import { TextInput, Button, HelperText, Text } from 'react-native-paper';
import { z } from 'zod';
import React, { useCallback } from "react";
import { useForm, Controller, FieldErrors } from "react-hook-form";
import { TextInput, HelperText } from "react-native-paper";
import { z } from "zod";
import { zodResolver } from '@hookform/resolvers/zod';
import { zodResolver } from "@hookform/resolvers/zod";
import {
NativeStackNavigationProp,
NativeStackScreenProps,
} from '@react-navigation/native-stack';
import { useNavigation } from '@react-navigation/native';
} from "@react-navigation/native-stack";
import { useNavigation } from "@react-navigation/native";
import { setInternetCredentials } from '../utils/key-store';
import { StackParamsList } from '../types';
import styles from '../styles/stylesheet';
import { retrieveNetworksData } from '../utils/accounts';
import { useNetworks } from '../context/NetworksContext';
import { setInternetCredentials } from "../utils/key-store";
import { StackParamsList } from "../types";
import { retrieveNetworksData } from "../utils/accounts";
import { useNetworks } from "../context/NetworksContext";
import {
COSMOS,
EIP155,
EMPTY_FIELD_ERROR,
INVALID_URL_ERROR,
} from '../utils/constants';
} from "../utils/constants";
import { Divider, Grid, Typography } from "@mui/material";
import { LoadingButton } from "@mui/lab";
import { Layout } from "../components/Layout";
const ethNetworksFormSchema = z.object({
// Adding type field for resolving typescript error
@@ -31,7 +32,7 @@ const ethNetworksFormSchema = z.object({
blockExplorerUrl: z
.string()
.url({ message: INVALID_URL_ERROR })
.or(z.literal('')),
.or(z.literal("")),
});
const cosmosNetworksFormDataSchema = z.object({
@@ -41,14 +42,14 @@ const cosmosNetworksFormDataSchema = z.object({
blockExplorerUrl: z
.string()
.url({ message: INVALID_URL_ERROR })
.or(z.literal('')),
.or(z.literal("")),
gasPrice: z
.string()
.nonempty({ message: EMPTY_FIELD_ERROR })
.regex(/^\d+(\.\d+)?$/),
});
type EditNetworkProps = NativeStackScreenProps<StackParamsList, 'EditNetwork'>;
type EditNetworkProps = NativeStackScreenProps<StackParamsList, "EditNetwork">;
const EditNetwork = ({ route }: EditNetworkProps) => {
const { setNetworksData } = useNetworks();
@@ -67,7 +68,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
formState: { errors, isSubmitting },
handleSubmit,
} = useForm<z.infer<typeof networksFormDataSchema>>({
mode: 'onChange',
mode: "onChange",
resolver: zodResolver(networksFormDataSchema),
});
@@ -77,121 +78,134 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
const { type, ...dataWithoutType } = data;
const newNetworkData = { ...networkData, ...dataWithoutType };
const index = retrievedNetworksData.findIndex(
network => network.networkId === networkData.networkId,
(network) => network.networkId === networkData.networkId,
);
retrievedNetworksData.splice(index, 1, newNetworkData);
await setInternetCredentials(
'networks',
'_',
"networks",
"_",
JSON.stringify(retrievedNetworksData),
);
setNetworksData(retrievedNetworksData);
navigation.navigate('Home');
navigation.navigate("Home");
},
[networkData, navigation, setNetworksData],
);
const isCosmos = networkData.namespace === COSMOS;
return (
<View style={styles.appContainer}>
<View>
<Text style={styles.subHeading}>
Edit {networkData?.networkName} details
</Text>
</View>
<Controller
control={control}
defaultValue={networkData.networkName}
name="networkName"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.networkName?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
name="rpcUrl"
defaultValue={networkData.rpcUrl}
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
<Layout title="Edit Network">
<Typography fontSize="1.5rem" fontWeight={500}>
Edit {networkData?.networkName} details
</Typography>
<Divider flexItem sx={{ my: 4 }} />
<Grid container spacing={2}>
<Grid item xs={6}>
<Controller
control={control}
defaultValue={networkData.networkName}
name="networkName"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{errors.networkName?.message}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="rpcUrl"
defaultValue={networkData.rpcUrl}
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
</Grid>
<Controller
control={control}
defaultValue={networkData.blockExplorerUrl}
name="blockExplorerUrl"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
<Grid item xs={isCosmos ? 6 : 12}>
<Controller
control={control}
defaultValue={networkData.blockExplorerUrl}
name="blockExplorerUrl"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{errors.blockExplorerUrl?.message}
</HelperText>
</>
)}
/>
</Grid>
{isCosmos && (
<Grid item xs={6}>
<Controller
control={control}
name="gasPrice"
defaultValue={networkData.gasPrice}
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Gas Price"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworksFormDataSchema>
>
).gasPrice?.message
}
</HelperText>
</>
)}
/>
<HelperText type="error">
{errors.blockExplorerUrl?.message}
</HelperText>
</>
</Grid>
)}
/>
{networkData.namespace === COSMOS && (
<Controller
control={control}
name="gasPrice"
defaultValue={networkData.gasPrice}
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Gas Price"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworksFormDataSchema>
>
).gasPrice?.message
}
</HelperText>
</>
)}
/>
)}
<Button
mode="contained"
</Grid>
<LoadingButton
variant="contained"
loading={isSubmitting}
disabled={isSubmitting}
style={styles.networksButton}
onPress={handleSubmit(submit)}>
{isSubmitting ? 'Adding' : 'Submit'}
</Button>
</View>
onClick={handleSubmit(submit)}
sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }}
>
{isSubmitting ? "Adding" : "Submit"}
</LoadingButton>
</Layout>
);
};
+107 -87
View File
@@ -1,66 +1,43 @@
import React, { useCallback, useEffect, useState } from 'react';
import { View, ActivityIndicator, Image } from 'react-native';
import { Button, Text } from 'react-native-paper';
import React, { useCallback, useEffect, useState } from "react";
import { View, ActivityIndicator } from "react-native";
import { Text } from "react-native-paper";
import { Button, Divider, Portal, Snackbar } from "@mui/material";
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useNavigation } from '@react-navigation/native';
import { getSdkError } from '@walletconnect/utils';
import { getSdkError } from "@walletconnect/utils";
import { createWallet, resetWallet, retrieveAccounts } from '../utils/accounts';
import { DialogComponent } from '../components/Dialog';
import { NetworkDropdown } from '../components/NetworkDropdown';
import Accounts from '../components/Accounts';
import CreateWallet from '../components/CreateWallet';
import ConfirmDialog from '../components/ConfirmDialog';
import styles from '../styles/stylesheet';
import { useAccounts } from '../context/AccountsContext';
import { useWalletConnect } from '../context/WalletConnectContext';
import { NetworksDataState, StackParamsList } from '../types';
import { useNetworks } from '../context/NetworksContext';
const WCLogo = () => {
return (
<Image
style={styles.walletConnectLogo}
source={require('../assets/WalletConnect-Icon-Blueberry.png')}
/>
);
};
import { createWallet, resetWallet, retrieveAccounts } from "../utils/accounts";
import { NetworkDropdown } from "../components/NetworkDropdown";
import Accounts from "../components/Accounts";
import CreateWallet from "../components/CreateWallet";
import ConfirmDialog from "../components/ConfirmDialog";
import styles from "../styles/stylesheet";
import { useAccounts } from "../context/AccountsContext";
import { useWalletConnect } from "../context/WalletConnectContext";
import { NetworksDataState } from "../types";
import { useNetworks } from "../context/NetworksContext";
import ImportWalletDialog from "../components/ImportWalletDialog";
import { MnemonicDialog } from "../components/MnemonicDialog";
import { Container } from "../components/Container";
import { IS_IMPORT_WALLET_ENABLED } from "../utils/constants";
const HomeScreen = () => {
const { accounts, setAccounts, setCurrentIndex } = useAccounts();
const { setAccounts, setCurrentIndex } = useAccounts();
const { networksData, selectedNetwork, setSelectedNetwork, setNetworksData } =
useNetworks();
const { setActiveSessions, web3wallet } = useWalletConnect();
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
useEffect(() => {
if (accounts.length > 0) {
navigation.setOptions({
// eslint-disable-next-line react/no-unstable-nested-components
headerRight: () => (
<Button onPress={() => navigation.navigate('WalletConnect')}>
{<WCLogo />}
</Button>
),
});
} else {
navigation.setOptions({
headerRight: undefined,
});
}
}, [navigation, accounts]);
const [isWalletCreated, setIsWalletCreated] = useState<boolean>(false);
const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false);
const [walletDialog, setWalletDialog] = useState<boolean>(false);
const [importWalletDialog, setImportWalletDialog] = useState<boolean>(false);
const [mnemonicDialog, setMnemonicDialog] = useState<boolean>(false);
const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false);
const [toastVisible, setToastVisible] = useState(false);
const [invalidMnemonicError, setInvalidMnemonicError] = useState("");
const [isAccountsFetched, setIsAccountsFetched] = useState<boolean>(true);
const [phrase, setPhrase] = useState('');
const [phrase, setPhrase] = useState("");
const hideWalletDialog = () => setWalletDialog(false);
const hideMnemonicDialog = () => setMnemonicDialog(false);
const hideResetDialog = () => setResetWalletDialog(false);
const fetchAccounts = useCallback(async () => {
@@ -83,12 +60,27 @@ const HomeScreen = () => {
const mnemonic = await createWallet(networksData);
if (mnemonic) {
fetchAccounts();
setWalletDialog(true);
setMnemonicDialog(true);
setPhrase(mnemonic);
setSelectedNetwork(networksData[0]);
}
};
const importWalletHandler = async (recoveryPhrase: string) => {
try {
const mnemonic = await createWallet(networksData, recoveryPhrase);
if (mnemonic) {
fetchAccounts();
setPhrase(mnemonic);
setSelectedNetwork(networksData[0]);
setImportWalletDialog(false);
}
} catch (error: any) {
setInvalidMnemonicError((error.message as string).toUpperCase());
setToastVisible(true);
}
};
const confirmResetWallet = useCallback(async () => {
setIsWalletCreated(false);
setIsWalletCreating(false);
@@ -99,10 +91,10 @@ const HomeScreen = () => {
await resetWallet();
const sessions = web3wallet!.getActiveSessions();
Object.keys(sessions).forEach(async sessionId => {
Object.keys(sessions).forEach(async (sessionId) => {
await web3wallet!.disconnectSession({
topic: sessionId,
reason: getSdkError('USER_DISCONNECTED'),
reason: getSdkError("USER_DISCONNECTED"),
});
});
setActiveSessions({});
@@ -114,7 +106,7 @@ const HomeScreen = () => {
setCurrentIndex,
setNetworksData,
setSelectedNetwork,
web3wallet
web3wallet,
]);
const updateNetwork = (networkData: NetworksDataState) => {
@@ -128,46 +120,74 @@ const HomeScreen = () => {
return (
<View style={styles.appContainer}>
{!isAccountsFetched ? (
<View style={styles.spinnerContainer}>
<Text style={styles.LoadingText}>Loading...</Text>
<ActivityIndicator size="large" color="#0000ff" />
</View>
) : isWalletCreated && selectedNetwork ? (
<>
<NetworkDropdown updateNetwork={updateNetwork} />
<View style={styles.accountComponent}>
<Accounts />
<Container>
{!isAccountsFetched ? (
<View style={styles.spinnerContainer}>
<Text style={styles.LoadingText}>Loading...</Text>
<ActivityIndicator size="large" color="#0000ff" />
</View>
<View style={styles.resetContainer}>
) : isWalletCreated && selectedNetwork ? (
<>
<NetworkDropdown updateNetwork={updateNetwork} />
<View style={styles.accountComponent}>
<Accounts />
</View>
<Divider flexItem sx={{ my: 4 }} />
<Button
style={styles.resetButton}
mode="contained"
buttonColor="#B82B0D"
onPress={() => {
variant="contained"
onClick={() => {
setResetWalletDialog(true);
}}>
}}
color="error"
>
Reset Wallet
</Button>
</View>
</>
) : (
<CreateWallet
isWalletCreating={isWalletCreating}
createWalletHandler={createWalletHandler}
</>
) : (
<>
<CreateWallet
isWalletCreating={isWalletCreating}
createWalletHandler={createWalletHandler}
/>
{IS_IMPORT_WALLET_ENABLED && (
<View style={styles.createWalletContainer}>
<Button
variant="contained"
onClick={() => setImportWalletDialog(true)}
>
Import Wallet
</Button>
</View>
)}
</>
)}
<ImportWalletDialog
visible={importWalletDialog}
hideDialog={() => setImportWalletDialog(false)}
importWalletHandler={importWalletHandler}
/>
)}
<DialogComponent
visible={walletDialog}
hideDialog={hideWalletDialog}
contentText={phrase}
/>
<ConfirmDialog
title="Reset wallet"
visible={resetWalletDialog}
hideDialog={hideResetDialog}
onConfirm={confirmResetWallet}
/>
<MnemonicDialog
visible={mnemonicDialog}
hideDialog={hideMnemonicDialog}
contentText={phrase}
/>
<ConfirmDialog
title="Reset wallet"
visible={resetWalletDialog}
hideDialog={hideResetDialog}
onConfirm={confirmResetWallet}
/>
</Container>
<Portal>
<Snackbar
open={toastVisible}
autoHideDuration={3000}
message={invalidMnemonicError}
onClose={() => setToastVisible(false)}
anchorOrigin={{ horizontal: "center", vertical: "bottom" }}
ContentProps={{ style: { backgroundColor: "red", color: "white" } }}
/>
</Portal>
</View>
);
};
+30 -31
View File
@@ -1,22 +1,22 @@
import React, { useState } from 'react';
import { View } from 'react-native';
import { Button, Text, TextInput } from 'react-native-paper';
import React, { useState } from "react";
import { Text, TextInput } from "react-native-paper";
import { Button, Divider, Stack } from "@mui/material";
import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { NativeStackScreenProps } from "@react-navigation/native-stack";
import { StackParamsList } from '../types';
import styles from '../styles/stylesheet';
import { signMessage } from '../utils/sign-message';
import AccountDetails from '../components/AccountDetails';
import { StackParamsList } from "../types";
import { signMessage } from "../utils/sign-message";
import AccountDetails from "../components/AccountDetails";
import { Layout } from "../components/Layout";
type SignProps = NativeStackScreenProps<StackParamsList, 'SignMessage'>;
type SignProps = NativeStackScreenProps<StackParamsList, "SignMessage">;
const SignMessage = ({ route }: SignProps) => {
const namespace = route.params.selectedNamespace;
const chainId = route.params.selectedChainId;
const account = route.params.accountInfo;
const [message, setMessage] = useState<string>('');
const [message, setMessage] = useState<string>("");
const signMessageHandler = async () => {
const signedMessage = await signMessage({
@@ -29,31 +29,30 @@ const SignMessage = ({ route }: SignProps) => {
};
return (
<View style={styles.signPage}>
<View style={styles.accountInfo}>
<View>
<Text variant="titleMedium">
{account && `Account ${account.index + 1}`}
</Text>
</View>
<View style={styles.accountContainer}>
<AccountDetails account={account} />
</View>
</View>
<Layout title="Sign Message">
<Text variant="titleMedium">
{account && `Account ${account.index + 1}`}
</Text>
<AccountDetails account={account} />
<TextInput
mode="outlined"
placeholder="Enter your message"
onChangeText={text => setMessage(text)}
value={message}
/>
<Stack spacing={4}>
<Divider flexItem />
<TextInput
mode="outlined"
placeholder="Enter your message"
onChangeText={(text) => setMessage(text)}
value={message}
/>
<View style={styles.signButton}>
<Button mode="contained" onPress={signMessageHandler}>
<Button
variant="contained"
onClick={signMessageHandler}
sx={{ width: "200px", px: 4, py: 1, mt: 2 }}
>
Sign
</Button>
</View>
</View>
</Stack>
</Layout>
);
};
+12 -2
View File
@@ -202,7 +202,13 @@ const SignRequest = ({ route }: SignRequestProps) => {
chainId,
accountId: account.index,
});
alert(`Signature ${signedMessage}`);
// Send the result back to Android and close dialog
if (window.Android?.onSignatureComplete) {
window.Android.onSignatureComplete(signedMessage || "");
} else {
alert(`Signature: ${signedMessage}`);
}
}
};
@@ -230,7 +236,11 @@ const SignRequest = ({ route }: SignRequestProps) => {
}
setIsRejecting(false);
navigation.navigate('Home');
if (window.Android?.onSignatureCancelled) {
window.Android.onSignatureCancelled();
} else {
navigation.navigate('Home');
}
};
useEffect(() => {
+189
View File
@@ -0,0 +1,189 @@
import React, { useCallback, useEffect, useState } from 'react';
import { ScrollView, View } from 'react-native';
import { ActivityIndicator, Button, Text, Appbar } from 'react-native-paper';
import { useNavigation } from '@react-navigation/native';
import {
NativeStackNavigationProp,
NativeStackScreenProps,
} from '@react-navigation/native-stack';
import { getHeaderTitle } from '@react-navigation/elements';
import { Account, StackParamsList } from '../types';
import AccountDetails from '../components/AccountDetails';
import styles from '../styles/stylesheet';
import { getCosmosAccounts, retrieveSingleAccount } from '../utils/accounts';
import { getMnemonic, getPathKey, sendMessage } from '../utils/misc';
import { COSMOS } from '../utils/constants';
const REACT_APP_ALLOWED_URLS = process.env.REACT_APP_ALLOWED_URLS;
type SignRequestProps = NativeStackScreenProps<StackParamsList, 'sign-request-embed'>;
const SignRequestEmbed = ({ route }: SignRequestProps) => {
const [displayAccount, setDisplayAccount] = useState<Account>();
const [message, setMessage] = useState<string>('');
const [chainId, setChainId] = useState<string>('');
const [signDoc, setSignDoc] = useState<any>(null);
const [signerAddress, setSignerAddress] = useState<string>('');
const [origin, setOrigin] = useState<string>('');
const [sourceWindow, setSourceWindow] = useState<Window | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isApproving, setIsApproving] = useState(false);
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
const signMessageHandler = async () => {
if (!signDoc || !signerAddress || !sourceWindow) return;
setIsApproving(true);
try {
const requestAccount = await retrieveSingleAccount(COSMOS, chainId, signerAddress);
const path = (await getPathKey(`${COSMOS}:${chainId}`, requestAccount!.index)).path;
const mnemonic = await getMnemonic();
const cosmosAccount = await getCosmosAccounts(mnemonic, path, 'zenith');
const cosmosAminoSignature = await cosmosAccount.cosmosWallet.signAmino(
signerAddress,
signDoc,
);
const signature = cosmosAminoSignature.signature.signature;
sendMessage(
sourceWindow,
'ZENITH_SIGNED_MESSAGE',
{ signature },
origin,
);
navigation.navigate('Home');
} catch (err) {
console.error('Signing failed:', err);
sendMessage(
sourceWindow!,
'ZENITH_SIGNED_MESSAGE',
{ error: err },
origin,
);
} finally {
setIsApproving(false);
}
};
const rejectRequestHandler = useCallback(async () => {
if (sourceWindow && origin) {
sendMessage(
sourceWindow,
'ZENITH_SIGNED_MESSAGE',
{ error: 'User rejected the request' },
origin,
);
}
}, [sourceWindow, origin]);
useEffect(() => {
const handleCosmosSignMessage = async (event: MessageEvent) => {
if (event.data.type !== 'SIGN_ZENITH_MESSAGE') return;
if (!REACT_APP_ALLOWED_URLS) {
console.log('Allowed URLs are not set');
return;
}
const allowedUrls = REACT_APP_ALLOWED_URLS.split(',').map(url => url.trim());
if (!allowedUrls.includes(event.origin)) {
console.log('Unauthorized app.');
return;
}
try {
const { signerAddress, signDoc } = event.data.params;
setSignerAddress(signerAddress);
setSignDoc(signDoc);
setMessage(signDoc.memo || '');
setOrigin(event.origin);
setSourceWindow(event.source as Window);
setChainId(event.data.chainId);
const requestAccount = await retrieveSingleAccount(
COSMOS,
event.data.chainId,
signerAddress,
);
setDisplayAccount(requestAccount);
setIsLoading(false);
} catch (err) {
console.error('Error preparing sign request:', err);
setIsLoading(false);
}
};
window.addEventListener('message', handleCosmosSignMessage);
return () => window.removeEventListener('message', handleCosmosSignMessage);
}, []);
useEffect(() => {
navigation.setOptions({
// eslint-disable-next-line react/no-unstable-nested-components
header: ({ options, back }) => {
const title = getHeaderTitle(options, 'Sign Message');
return (
<Appbar.Header>
{back && (
<Appbar.BackAction
onPress={async () => {
await rejectRequestHandler();
navigation.navigate('Home');
}}
/>
)}
<Appbar.Content title={title} />
</Appbar.Header>
);
},
});
}, [navigation, rejectRequestHandler]);
return (
<>
{isLoading ? (
<View style={styles.spinnerContainer}>
<ActivityIndicator size="large" color="#0000ff" />
</View>
) : (
<>
<ScrollView contentContainerStyle={styles.appContainer}>
<AccountDetails account={displayAccount} />
<View style={styles.requestMessage}>
<Text variant="bodyLarge">{message}</Text>
</View>
</ScrollView>
<View style={styles.buttonContainer}>
<Button
mode="contained"
onPress={signMessageHandler}
loading={isApproving}
disabled={isApproving}>
Yes
</Button>
<Button
mode="contained"
onPress={rejectRequestHandler}
buttonColor="#B82B0D">
No
</Button>
</View>
</>
)}
</>
);
};
export default SignRequestEmbed;
+14 -14
View File
@@ -1,11 +1,12 @@
import React, { useEffect } from 'react';
import { Image, TouchableOpacity, View } from 'react-native';
import { List, Text } from 'react-native-paper';
import React, { useEffect } from "react";
import { Image, TouchableOpacity, View } from "react-native";
import { List, Text } from "react-native-paper";
import { getSdkError } from '@walletconnect/utils';
import { getSdkError } from "@walletconnect/utils";
import { useWalletConnect } from '../context/WalletConnectContext';
import styles from '../styles/stylesheet';
import { useWalletConnect } from "../context/WalletConnectContext";
import styles from "../styles/stylesheet";
import { Layout } from "../components/Layout";
export default function WalletConnect() {
const { web3wallet, activeSessions, setActiveSessions } = useWalletConnect();
@@ -13,7 +14,7 @@ export default function WalletConnect() {
const disconnect = async (sessionId: string) => {
await web3wallet!.disconnectSession({
topic: sessionId,
reason: getSdkError('USER_DISCONNECTED'),
reason: getSdkError("USER_DISCONNECTED"),
});
const sessions = web3wallet?.getActiveSessions() || {};
setActiveSessions(sessions);
@@ -26,12 +27,10 @@ export default function WalletConnect() {
}, [web3wallet, setActiveSessions]);
return (
<View>
<Layout title="Active Sessions">
{Object.keys(activeSessions).length > 0 ? (
<>
<View style={styles.sessionsContainer}>
<Text variant="titleMedium">Active Sessions</Text>
</View>
<View style={styles.sessionsContainer} />
<List.Section>
{Object.entries(activeSessions).map(([sessionId, session]) => (
<List.Item
@@ -44,7 +43,7 @@ export default function WalletConnect() {
// eslint-disable-next-line react/no-unstable-nested-components
left={() => (
<>
{session.peer.metadata.icons[0].endsWith('.svg') ? (
{session.peer.metadata.icons[0].endsWith(".svg") ? (
<View style={styles.dappLogo}>
<Text>SvgURI peerMetaDataIcon</Text>
</View>
@@ -60,7 +59,8 @@ export default function WalletConnect() {
right={() => (
<TouchableOpacity
onPress={() => disconnect(sessionId)}
style={styles.disconnectSession}>
style={styles.disconnectSession}
>
<List.Icon icon="close" />
</TouchableOpacity>
)}
@@ -73,6 +73,6 @@ export default function WalletConnect() {
<Text>You have no active sessions</Text>
</View>
)}
</View>
</Layout>
);
}
+317
View File
@@ -0,0 +1,317 @@
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { ScrollView, View } from 'react-native';
import {
ActivityIndicator,
Button,
Text,
TextInput,
} from 'react-native-paper';
import { BigNumber } from 'ethers';
import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing';
import {
calculateFee,
GasPrice,
SigningStargateClient,
} from '@cosmjs/stargate';
import { retrieveSingleAccount } from '../utils/accounts';
import AccountDetails from '../components/AccountDetails';
import styles from '../styles/stylesheet';
import DataBox from '../components/DataBox';
import { checkSufficientFunds, getPathKey, sendMessage } from '../utils/misc';
import { useNetworks } from '../context/NetworksContext';
import TxErrorDialog from '../components/TxErrorDialog';
import { MEMO } from '../screens/ApproveTransfer';
import { Account, NetworksDataState } from '../types';
import useGetOrCreateAccounts from '../hooks/useGetOrCreateAccounts';
import useAccountsData from '../hooks/useAccountsData';
type TransactionDetails = {
chainId: string;
fromAddress: string;
toAddress: string;
amount: string;
account: Account
balance: string;
requestedNetwork: NetworksDataState
};
export const WalletEmbed = () => {
const [isTxRequested, setIsTxRequested] = useState<boolean>(false);
const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null);
const [fees, setFees] = useState<string>('');
const [gasLimit, setGasLimit] = useState<string>('');
const [isTxLoading, setIsTxLoading] = useState(false);
const [txError, setTxError] = useState<string | null>(null);
const txEventRef = useRef<MessageEvent | null>(null);
const { networksData } = useNetworks();
const { getAccountsData } = useAccountsData();
useEffect(() => {
const handleGetAccounts = async (event: MessageEvent) => {
if (event.data.type !== 'REQUEST_WALLET_ACCOUNTS') return;
const accountsData = await getAccountsData(event.data.chainId);
if (accountsData.length === 0) {
sendMessage(event.source as Window, 'ERROR', 'Wallet accounts not found', event.origin);
return;
}
sendMessage(
event.source as Window,
'WALLET_ACCOUNTS_DATA',
accountsData.map(account => account.address),
event.origin
);
};
window.addEventListener('message', handleGetAccounts);
return () => {
window.removeEventListener('message', handleGetAccounts);
};
}, [getAccountsData]);
// Custom hook for adding listener to get accounts data
useGetOrCreateAccounts();
const handleTxRequested = useCallback(
async (event: MessageEvent) => {
try {
if (event.data.type !== 'REQUEST_TX') return;
txEventRef.current = event;
const { chainId, fromAddress, toAddress, amount } = event.data;
const network = networksData.find(net => net.chainId === chainId);
if (!network) {
console.error('Network not found');
throw new Error('Requested network not supported.');
}
const account = await retrieveSingleAccount(network.namespace, network.chainId, fromAddress);
if (!account) {
throw new Error('Account not found for the requested address.');
}
const cosmosPrivKey = (
await getPathKey(`${network.namespace}:${chainId}`, account.index)
).privKey;
const sender = await DirectSecp256k1Wallet.fromKey(
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
network.addressPrefix
);
const client = await SigningStargateClient.connectWithSigner(network.rpcUrl!, sender);
const balance = await client.getBalance(
account.address,
network.nativeDenom!.toLowerCase()
);
const sendMsg = {
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
value: {
fromAddress: fromAddress,
toAddress: toAddress,
amount: [
{
amount: String(amount),
denom: network.nativeDenom!,
},
],
},
};
setTransactionDetails({
chainId,
fromAddress,
toAddress,
amount,
account,
balance: balance.amount,
requestedNetwork: network,
});
if (!checkSufficientFunds(amount, balance.amount)) {
console.log("Insufficient funds detected. Throwing error.");
throw new Error('Insufficient funds');
}
const gasEstimation = await client.simulate(fromAddress, [sendMsg], MEMO);
const gasLimit = String(
Math.round(gasEstimation * Number(process.env.REACT_APP_GAS_ADJUSTMENT))
);
setGasLimit(gasLimit);
const gasPrice = GasPrice.fromString(`${network.gasPrice}${network.nativeDenom}`);
const cosmosFees = calculateFee(Number(gasLimit), gasPrice);
setFees(cosmosFees.amount[0].amount);
setIsTxRequested(true);
} catch (error) {
if (!(error instanceof Error)) {
throw error;
}
setTxError(error.message);
}
}, [networksData]);
useEffect(() => {
window.addEventListener('message', handleTxRequested);
return () => window.removeEventListener('message', handleTxRequested);
}, [handleTxRequested]);
const acceptRequestHandler = async () => {
try {
setIsTxLoading(true);
if (!transactionDetails) {
throw new Error('Tx details not set');
}
const balanceBigNum = BigNumber.from(transactionDetails.balance);
const amountBigNum = BigNumber.from(String(transactionDetails.amount));
if (amountBigNum.gte(balanceBigNum)) {
throw new Error('Insufficient funds');
}
const cosmosPrivKey = (
await getPathKey(`${transactionDetails.requestedNetwork.namespace}:${transactionDetails.chainId}`, transactionDetails.account.index)
).privKey;
const sender = await DirectSecp256k1Wallet.fromKey(
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
transactionDetails.requestedNetwork.addressPrefix
);
const client = await SigningStargateClient.connectWithSigner(
transactionDetails.requestedNetwork.rpcUrl!,
sender
);
const fee = calculateFee(
Number(gasLimit),
GasPrice.fromString(`${transactionDetails.requestedNetwork.gasPrice}${transactionDetails.requestedNetwork.nativeDenom}`)
);
const txResult = await client.sendTokens(
transactionDetails.fromAddress,
transactionDetails.toAddress,
[{ amount: String(transactionDetails.amount), denom: transactionDetails.requestedNetwork.nativeDenom! }],
fee
);
const event = txEventRef.current;
if (event?.source) {
sendMessage(event.source as Window, 'TRANSACTION_RESPONSE', txResult.transactionHash, event.origin);
} else {
console.error('No event source available to send message');
}
} catch (error) {
if (!(error instanceof Error)) {
throw error;
}
setTxError(error.message);
} finally {
setIsTxLoading(false);
}
};
const rejectRequestHandler = () => {
const event = txEventRef.current;
setIsTxRequested(false);
setTransactionDetails(null);
if (event?.source) {
sendMessage(event.source as Window, 'TRANSACTION_RESPONSE', null, event.origin);
} else {
console.error('No event source available to send message');
}
};
return (
<>
{isTxRequested && transactionDetails ? (
<>
<ScrollView contentContainerStyle={styles.appContainer}>
<View style={styles.dataBoxContainer}>
<Text style={styles.dataBoxLabel}>From</Text>
<View style={styles.dataBox}>
<AccountDetails account={transactionDetails.account} />
</View>
</View>
<DataBox
label={`Balance (${transactionDetails.requestedNetwork.nativeDenom})`}
data={
transactionDetails.balance === '' ||
transactionDetails.balance === undefined
? 'Loading balance...'
: `${transactionDetails.balance}`
}
/>
<View style={styles.approveTransfer}>
<DataBox label="To" data={transactionDetails.toAddress} />
<DataBox
label={`Amount (${transactionDetails.requestedNetwork.nativeDenom})`}
data={transactionDetails.amount}
/>
<TextInput
mode="outlined"
label="Fee"
value={fees}
onChangeText={setFees}
style={styles.transactionFeesInput}
/>
<TextInput
mode="outlined"
label="Gas Limit"
value={gasLimit}
onChangeText={value =>
/^\d+$/.test(value) ? setGasLimit(value) : null
}
/>
</View>
</ScrollView>
<View style={styles.buttonContainer}>
<Button
mode="contained"
onPress={acceptRequestHandler}
loading={isTxLoading}
disabled={!transactionDetails.balance || !fees || isTxLoading}
>
{isTxLoading ? 'Processing' : 'Yes'}
</Button>
<Button
mode="contained"
onPress={rejectRequestHandler}
buttonColor="#B82B0D"
disabled={isTxLoading}
>
No
</Button>
</View>
</>
) : (
<View style={styles.spinnerContainer}>
<View style={{ marginTop: 50 }}></View>
<ActivityIndicator size="large" color="#0000ff" />
</View>
)}
<TxErrorDialog
error={txError!}
visible={!!txError}
hideDialog={() => {
setTxError(null)
if (window.parent) {
sendMessage(window.parent, 'TRANSACTION_RESPONSE', null, '*');
sendMessage(window.parent, 'closeIframe', null, '*');
}
}}
/>
</>
);
};
+129 -76
View File
@@ -1,22 +1,21 @@
import { StyleSheet } from 'react-native';
import { StyleSheet } from "react-native";
const styles = StyleSheet.create({
createWalletContainer: {
marginTop: 20,
width: 150,
alignSelf: 'center',
marginBottom: 40
alignSelf: "center",
marginBottom: 30,
},
signLink: {
alignItems: 'flex-end',
alignItems: "flex-end",
marginTop: 24,
},
hyperlink: {
fontWeight: '500',
textDecorationLine: 'underline',
fontWeight: "500",
textDecorationLine: "underline",
},
highlight: {
fontWeight: '700',
fontWeight: "700",
},
accountContainer: {
padding: 8,
@@ -24,28 +23,32 @@ const styles = StyleSheet.create({
},
addAccountButton: {
marginTop: 24,
alignSelf: 'center',
alignSelf: "center",
},
accountComponent: {
flex: 4,
},
appSurface: {
backgroundColor: "#0f0f0f",
},
appContainer: {
flexGrow: 1,
marginTop: 24,
paddingHorizontal: 24,
backgroundColor: "#0f0f0f",
},
resetContainer: {
flex: 1,
justifyContent: 'center',
justifyContent: "center",
},
resetButton: {
alignSelf: 'center',
alignSelf: "center",
},
signButton: {
marginTop: 20,
marginBottom: 20,
width: 150,
alignSelf: 'center',
alignSelf: "center",
},
signPage: {
paddingHorizontal: 24,
@@ -71,33 +74,81 @@ const styles = StyleSheet.create({
borderRadius: 10,
},
dialogWarning: {
color: 'red',
color: "#FFA3A8",
},
resetDialogTitle: {
width: 500,
backgroundColor: "#18181A",
},
resetDialogContent: {
backgroundColor: "#18181A",
},
resetDialogActionRow: {
backgroundColor: "#18181A",
},
button: {
color: "#fff",
margin: 10,
},
buttonRed: {
backgroundColor: "#B20710",
},
buttonBlue: {
backgroundColor: "#0000F4",
},
mnemonicTitle: {
backgroundColor: "#18181A",
},
mnemonicContainer: {
backgroundColor: "#18181A",
},
mnemonicDialogWarning: {
color: "#FFA3A8",
marginTop: 10,
},
mnemonicButtonRow: {
paddingRight: 40,
backgroundColor: "#18181A",
},
mnemonicButton: {
backgroundColor: "#0000F4",
color: "white",
padding: 2,
marginBottom: 20,
},
mnemonicGridContainer: {
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "center",
marginTop: 20,
paddingBottom: 30,
borderBottomWidth: 1,
borderBottomColor: "#29292E",
},
gridContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'center',
flexDirection: "row",
flexWrap: "wrap",
justifyContent: "center",
},
gridItem: {
width: '25%',
margin: 8,
padding: 6,
borderWidth: 1,
borderColor: '#ccc',
borderRadius: 8,
alignItems: 'center',
justifyContent: 'flex-start',
width: "30%",
margin: 4,
padding: 4,
borderRadius: 4,
alignItems: "center",
justifyContent: "flex-start",
backgroundColor: "#29292E",
},
HDcontainer: {
marginTop: 24,
paddingHorizontal: 8,
},
HDrowContainer: {
flexDirection: 'row',
alignItems: 'center',
flexDirection: "row",
alignItems: "center",
},
HDtext: {
color: 'black',
color: "#FBFBFB",
fontSize: 18,
margin: 4,
},
@@ -107,15 +158,15 @@ const styles = StyleSheet.create({
HDbuttonContainer: {
marginTop: 20,
width: 200,
alignSelf: 'center',
alignSelf: "flex-start",
},
spinnerContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
justifyContent: "center",
alignItems: "center",
},
LoadingText: {
color: 'black',
color: "black",
fontSize: 18,
padding: 10,
},
@@ -123,9 +174,9 @@ const styles = StyleSheet.create({
borderWidth: 1,
borderRadius: 5,
marginTop: 50,
height: 'auto',
alignItems: 'center',
justifyContent: 'center',
height: "auto",
alignItems: "center",
justifyContent: "center",
padding: 10,
},
requestDirectMessage: {
@@ -134,51 +185,51 @@ const styles = StyleSheet.create({
marginTop: 20,
marginBottom: 50,
height: 500,
alignItems: 'center',
justifyContent: 'center',
alignItems: "center",
justifyContent: "center",
padding: 8,
},
approveTransfer: {
height: '40%',
height: "40%",
marginBottom: 30,
},
buttonContainer: {
flexDirection: 'row',
flexDirection: "row",
marginLeft: 20,
marginTop: 10,
marginBottom: 10,
justifyContent: 'space-evenly',
justifyContent: "space-evenly",
},
badRequestContainer: {
alignItems: 'center',
justifyContent: 'center',
alignItems: "center",
justifyContent: "center",
padding: 20,
},
invalidMessageText: {
color: 'black',
color: "black",
fontSize: 16,
textAlign: 'center',
textAlign: "center",
marginBottom: 20,
},
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
alignItems: "center",
justifyContent: "center",
marginBottom: 10,
paddingHorizontal: 20,
},
modalContentContainer: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
display: "flex",
justifyContent: "center",
alignItems: "center",
borderRadius: 34,
borderBottomStartRadius: 0,
borderBottomEndRadius: 0,
borderWidth: 1,
width: '100%',
height: '50%',
position: 'absolute',
backgroundColor: 'white',
width: "100%",
height: "50%",
position: "absolute",
backgroundColor: "#0f0f0f",
bottom: 0,
},
modalOuterContainer: { flex: 1 },
@@ -187,32 +238,32 @@ const styles = StyleSheet.create({
height: 50,
borderRadius: 8,
marginVertical: 16,
overflow: 'hidden',
overflow: "hidden",
},
space: {
width: 50,
},
flexRow: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
marginTop: 20,
paddingHorizontal: 16,
marginBottom: 10,
},
marginVertical8: {
marginVertical: 8,
textAlign: 'center',
textAlign: "center",
},
subHeading: {
textAlign: 'center',
textAlign: "center",
marginBottom: 10,
marginTop: 10,
fontSize: 20
fontSize: 20,
},
centerText: {
textAlign: 'center',
textAlign: "center",
},
messageBody: {
borderWidth: 1,
@@ -225,46 +276,48 @@ const styles = StyleSheet.create({
marginTop: 20,
},
dappDetails: {
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
},
dataBoxContainer: {
marginBottom: 10,
backgroundColor: "#29292E",
border: "none",
},
dataBoxLabel: {
fontSize: 18,
fontWeight: 'bold',
fontWeight: "bold",
marginBottom: 3,
color: 'black',
color: "white",
},
dataBox: {
borderWidth: 1,
borderColor: '#ccc',
borderColor: "#ccc",
padding: 10,
borderRadius: 5,
},
dataBoxData: {
fontSize: 16,
color: 'black',
color: "white",
},
transactionText: {
padding: 8,
fontSize: 18,
fontWeight: 'bold',
color: 'black',
fontWeight: "bold",
color: "black",
},
balancePadding: {
padding: 8,
},
noActiveSessions: {
display: 'flex',
alignItems: 'center',
display: "flex",
alignItems: "center",
marginTop: 20,
marginBottom: 20,
},
disconnectSession: {
display: 'flex',
justifyContent: 'center',
display: "flex",
justifyContent: "center",
},
sessionItem: {
paddingLeft: 12,
@@ -281,7 +334,7 @@ const styles = StyleSheet.create({
margin: 0,
},
selectNetworkText: {
fontWeight: 'bold',
fontWeight: "bold",
marginVertical: 10,
},
transactionFeesInput: { marginBottom: 10 },
@@ -292,7 +345,7 @@ const styles = StyleSheet.create({
paddingVertical: 5,
},
transactionLabel: {
fontWeight: '700',
fontWeight: "700",
padding: 8,
},
linkContainer: {
@@ -301,7 +354,7 @@ const styles = StyleSheet.create({
networksButton: {
marginTop: 12,
marginBottom: 20,
}
},
});
export default styles;
+5
View File
@@ -13,8 +13,10 @@ export type StackParamsList = {
};
SignRequest: {
namespace: string;
chainId?: string;
address: string;
message: string;
accountInfo?: Account;
requestEvent?: Web3WalletTypes.SessionRequest;
requestSessionData?: SessionTypes.Struct;
};
@@ -36,6 +38,9 @@ export type StackParamsList = {
requestEvent: Web3WalletTypes.SessionRequest;
requestSessionData: SessionTypes.Struct;
};
"wallet-embed": undefined;
"auto-sign-in": undefined;
"sign-request-embed": undefined;
};
export type Account = {
+21 -12
View File
@@ -8,10 +8,10 @@ import { utils } from 'ethers';
import { HDNode } from 'ethers/lib/utils';
import {
setInternetCredentials,
resetInternetCredentials,
getInternetCredentials,
} from './key-store';
setInternetCredentials,
resetInternetCredentials,
getInternetCredentials,
} from './key-store';
import { Secp256k1HdWallet } from '@cosmjs/amino';
import { AccountData } from '@cosmjs/proto-signing';
import { stringToPath } from '@cosmjs/crypto';
@@ -27,12 +27,23 @@ import { COSMOS, EIP155 } from './constants';
const createWallet = async (
networksData: NetworksDataState[],
recoveryPhrase?: string,
): Promise<string> => {
const mnemonic = utils.entropyToMnemonic(utils.randomBytes(16));
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
const mnemonic = recoveryPhrase ? recoveryPhrase : utils.entropyToMnemonic(utils.randomBytes(16));
const hdNode = HDNode.fromMnemonic(mnemonic);
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
await createWalletFromMnemonic(networksData, hdNode, mnemonic);
return mnemonic;
};
const createWalletFromMnemonic = async (
networksData: NetworksDataState[],
hdNode: HDNode,
mnemonic: string
): Promise<void> => {
for (const network of networksData) {
const hdPath = `m/44'/${network.coinType}'/0'/0/0`;
const node = hdNode.derivePath(hdPath);
@@ -53,7 +64,7 @@ const createWallet = async (
throw new Error('Unsupported namespace');
}
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
await Promise.all([
setInternetCredentials(
@@ -73,8 +84,6 @@ const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
),
]);
}
return mnemonic;
};
const addAccount = async (
@@ -197,9 +206,9 @@ const retrieveAccounts = async (
return;
}
const loadedAccounts = await retrieveAccountsForNetwork(
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
accountIndices,
)
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
accountIndices,
)
return loadedAccounts;
};
+32 -6
View File
@@ -1,22 +1,46 @@
import { DENOM } from '@cerc-io/registry-sdk'
import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData';
import { EIP155_CHAINS } from './wallet-connect/EIP155Data';
import { NetworksFormData } from '../types';
export const EIP155 = 'eip155';
export const COSMOS = 'cosmos';
export const DEFAULT_NETWORKS = [
export const DEFAULT_NETWORKS: NetworksFormData[] = [
{
chainId: 'laconic-testnet-2',
networkName: 'laconicd testnet-2',
namespace: COSMOS,
rpcUrl: process.env.REACT_APP_LACONICD_RPC_URL!,
blockExplorerUrl: '',
nativeDenom: 'alnt',
addressPrefix: 'laconic',
coinType: '118',
gasPrice: '0.001',
isDefault: true,
},
{
chainId: 'zenith-testnet',
networkName: 'zenithd testnet',
namespace: COSMOS,
rpcUrl: 'https://zenith-node-rpc.com',
blockExplorerUrl: '',
nativeDenom: 'znt',
addressPrefix: 'zenith',
coinType: '118',
gasPrice: '0.01',
isDefault: true,
},
{
chainId: 'laconic_9000-1',
networkName: 'laconicd',
namespace: COSMOS,
rpcUrl: process.env.REACT_APP_LACONICD_RPC_URL!,
rpcUrl: "https://laconicd.laconic.com",
blockExplorerUrl: '',
nativeDenom: DENOM,
nativeDenom: 'alnt',
addressPrefix: 'laconic',
coinType: '118',
gasPrice: '1',
isDefault: true,
isDefault: false,
},
{
chainId: '1',
@@ -48,3 +72,5 @@ export const EMPTY_FIELD_ERROR = 'Field cannot be empty';
export const INVALID_URL_ERROR = 'Invalid URL';
export const IS_NUMBER_REGEX = /^\d+$/;
export const IS_IMPORT_WALLET_ENABLED = false;
+6 -6
View File
@@ -1,17 +1,17 @@
const setInternetCredentials = (name:string, username:string, password:string) => {
localStorage.setItem(name, password);
localStorage.setItem(name, password);
};
const getInternetCredentials = (name:string) : string | null => {
return localStorage.getItem(name);
return localStorage.getItem(name);
};
const resetInternetCredentials = (name:string) => {
localStorage.removeItem(name);
localStorage.removeItem(name);
};
export {
setInternetCredentials,
getInternetCredentials,
resetInternetCredentials
setInternetCredentials,
getInternetCredentials,
resetInternetCredentials
}
+22 -4
View File
@@ -1,7 +1,11 @@
/* Importing this library provides react native with a secure random source.
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
import 'react-native-get-random-values';
import { BigNumber } from 'ethers';
import { AccountData } from '@cosmjs/amino';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { stringToPath } from '@cosmjs/crypto';
import '@ethersproject/shims';
import {
@@ -9,10 +13,6 @@ import {
resetInternetCredentials,
setInternetCredentials,
} from './key-store';
import { AccountData } from '@cosmjs/amino';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { stringToPath } from '@cosmjs/crypto';
import { EIP155 } from './constants';
import { NetworksDataState } from '../types';
@@ -149,10 +149,28 @@ const resetKeyServers = async (namespace: string) => {
});
};
const sendMessage = (
source: Window | null,
type: string,
data: any,
origin: string
): void => {
source?.postMessage({ type, data }, origin);
};
const checkSufficientFunds = (amount: string, balance: string) => {
const amountBigNum = BigNumber.from(String(amount));
const balanceBigNum = BigNumber.from(balance);
return balanceBigNum.gte(amountBigNum);
};
export {
getMnemonic,
getPathKey,
updateAccountIndices,
getHDPath,
resetKeyServers,
sendMessage,
checkSufficientFunds,
};
+6 -2
View File
@@ -3,6 +3,7 @@ For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#c
import 'react-native-get-random-values';
import '@ethersproject/shims';
import { fromBech32 } from '@cosmjs/encoding';
import { Wallet } from 'ethers';
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
@@ -24,7 +25,7 @@ const signMessage = async ({
case EIP155:
return await signEthMessage(message, accountId, chainId);
case COSMOS:
return await signCosmosMessage(message, path.path);
return await signCosmosMessage(message, path.path, path.address);
default:
throw new Error('Invalid wallet type');
}
@@ -51,10 +52,13 @@ const signEthMessage = async (
const signCosmosMessage = async (
message: string,
path: string,
cosmosAddress: string,
): Promise<string | undefined> => {
try {
const mnemonic = await getMnemonic();
const cosmosAccount = await getCosmosAccounts(mnemonic, path);
const addressPrefix = fromBech32(cosmosAddress).prefix
const cosmosAccount = await getCosmosAccounts(mnemonic, path, addressPrefix);
const address = cosmosAccount.data.address;
const cosmosSignature = await cosmosAccount.cosmosWallet.signAmino(
address,
+4 -28
View File
@@ -40,7 +40,6 @@ export const getNamespaces = async (
networksData: NetworksDataState[],
selectedNetwork: NetworksDataState,
accounts: Account[],
currentIndex: number,
) => {
const namespaceChainId = `${selectedNetwork.namespace}:${selectedNetwork.chainId}`;
@@ -101,23 +100,6 @@ export const getNamespaces = async (
const requiredAddressesArray = await Promise.all(requiredAddressesPromise);
const requiredAddresses = requiredAddressesArray.flat();
let sortedAccounts = requiredAddresses;
// If selected network is included in chains requested from dApp,
// Put selected account as first account
if (walletConnectChains.includes(namespaceChainId)) {
const currentAddresses = requiredAddresses.filter(address =>
address.includes(namespaceChainId),
);
sortedAccounts = [
currentAddresses[currentIndex],
...currentAddresses.filter((address, index) => index !== currentIndex),
...requiredAddresses.filter(
address => !currentAddresses.includes(address),
),
];
}
// construct namespace object
const newNamespaces = {
eip155: {
@@ -133,7 +115,7 @@ export const getNamespaces = async (
...(optionalNamespaces.eip155?.events ?? []),
...(requiredNamespaces.eip155?.events ?? []),
],
accounts: sortedAccounts.filter(account => account.includes(EIP155)),
accounts: requiredAddresses.filter(account => account.includes(EIP155)),
},
cosmos: {
chains: walletConnectChains.filter(chain => chain.includes(COSMOS)),
@@ -147,18 +129,12 @@ export const getNamespaces = async (
...(optionalNamespaces.cosmos?.events ?? []),
...(requiredNamespaces.cosmos?.events ?? []),
],
accounts: sortedAccounts.filter(account => account.includes(COSMOS)),
accounts: requiredAddresses.filter(account => account.includes(COSMOS)),
},
};
return newNamespaces;
} else {
// Set selected account as the first account in supported namespaces
const sortedAccounts = [
accounts[currentIndex],
...accounts.filter((account, index) => index !== currentIndex),
];
switch (selectedNetwork.namespace) {
case EIP155:
return {
@@ -175,7 +151,7 @@ export const getNamespaces = async (
...(optionalNamespaces.eip155?.events ?? []),
...(requiredNamespaces.eip155?.events ?? []),
],
accounts: sortedAccounts.map(ethAccount => {
accounts: accounts.map(ethAccount => {
return `${namespaceChainId}:${ethAccount.address}`;
}),
},
@@ -200,7 +176,7 @@ export const getNamespaces = async (
...(optionalNamespaces.cosmos?.events ?? []),
...(requiredNamespaces.cosmos?.events ?? []),
],
accounts: sortedAccounts.map(cosmosAccount => {
accounts: accounts.map(cosmosAccount => {
return `${namespaceChainId}:${cosmosAccount.address}`;
}),
},
@@ -7,8 +7,9 @@ import { getSdkError } from '@walletconnect/utils';
import {
SigningStargateClient,
StdFee,
MsgSendEncodeObject,
MsgSendEncodeObject
} from '@cosmjs/stargate';
import { fromBech32 } from '@cosmjs/encoding';
import { EncodeObject } from '@cosmjs/proto-signing';
import { LaconicClient } from '@cerc-io/registry-sdk';
import { Buffer } from 'buffer';
@@ -19,6 +20,7 @@ import { Account } from '../../types';
import { getMnemonic, getPathKey } from '../misc';
import { getCosmosAccounts } from '../accounts';
import { COSMOS_METHODS } from './COSMOSData';
import { COSMOS } from '../constants';
interface EthSendTransaction {
type: 'eth_sendTransaction';
@@ -80,7 +82,13 @@ export async function approveWalletConnectRequest(
const path = (await getPathKey(`${namespace}:${chainId}`, account.index))
.path;
const mnemonic = await getMnemonic();
const cosmosAccount = await getCosmosAccounts(mnemonic, path);
let addressPrefix: string | undefined
if (namespace === COSMOS) {
addressPrefix = fromBech32(account.address).prefix
}
const cosmosAccount = await getCosmosAccounts(mnemonic, path, addressPrefix);
const address = account.address;
switch (request.method) {
@@ -97,18 +105,18 @@ export async function approveWalletConnectRequest(
const updatedTransaction =
options.maxFeePerGas && options.maxPriorityFeePerGas
? {
...sendTransaction,
gasLimit: options.ethGasLimit,
maxFeePerGas: options.maxFeePerGas,
maxPriorityFeePerGas: options.maxPriorityFeePerGas,
type: 2,
}
...sendTransaction,
gasLimit: options.ethGasLimit,
maxFeePerGas: options.maxFeePerGas,
maxPriorityFeePerGas: options.maxPriorityFeePerGas,
type: 2,
}
: {
...sendTransaction,
gasLimit: options.ethGasLimit,
gasPrice: options.ethGasPrice,
type: 0,
};
...sendTransaction,
gasLimit: options.ethGasLimit,
gasPrice: options.ethGasPrice,
type: 0,
};
const connectedWallet = wallet.connect(options.provider);
@@ -6,12 +6,15 @@ services:
environment:
CERC_SCRIPT_DEBUG: ${CERC_SCRIPT_DEBUG}
WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}
WALLET_CONNECT_VERIFY_CODE: ${WALLET_CONNECT_VERIFY_CODE}
CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE:-0.025}
CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT:-2}
CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL:-https://laconicd.laconic.com}
CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}
command: ["bash", "/scripts/run.sh"]
volumes:
- ../config/app/run.sh:/scripts/run.sh
- ../config/app/serve.json:/app/serve.json
ports:
- "80"
healthcheck:
+12 -1
View File
@@ -10,12 +10,23 @@ echo "WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}"
echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}"
echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}"
echo "CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL}"
echo "CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}"
# Build with required env
REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_ID \
REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE \
REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT \
REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL \
REACT_APP_ALLOWED_URLS=$CERC_ALLOWED_URLS \
yarn build
http-server --proxy http://localhost:80? -p 80 /app/build
# Define the directory and file path
FILE_PATH="/app/build/.well-known/walletconnect.txt"
# Create the directory if it doesn't exist
mkdir -p "$(dirname "$FILE_PATH")"
# Write verification code to the file
echo "$WALLET_CONNECT_VERIFY_CODE" > "$FILE_PATH"
# Serve build dir with explicit config
serve -s -l 80 -c /app/serve.json /app/build
@@ -0,0 +1,22 @@
{
"headers": [
{
"source": "/index.html",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache, must-revalidate"
}
]
},
{
"source": "/static/**/*.{js,css,png,jpg,jpeg,gif,svg,ico}",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}
@@ -33,7 +33,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
RUN mkdir -p /scripts
# Install simple web server for now (use nginx perhaps later)
RUN yarn global add http-server
RUN yarn global add serve
WORKDIR /app
COPY . .
@@ -49,8 +49,14 @@ Instructions for running the `laconic-wallet-web` using [laconic-so](https://git
# WalletConnect project ID, same should be used in the laconic-wallet
WALLET_CONNECT_ID=
# Allowed urls is a comma separated list of allowed urls
CERC_ALLOWED_URLS=
# Optional
# WalletConnect code for hostname verification
WALLET_CONNECT_VERIFY_CODE=
# Default gas price for txs (default: 0.025)
CERC_DEFAULT_GAS_PRICE=
+215 -7
View File
@@ -1746,14 +1746,14 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
"@eslint-community/eslint-utils@^4.2.0":
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
version "4.4.0"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
dependencies:
eslint-visitor-keys "^3.3.0"
"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1":
"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1":
version "4.11.0"
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
@@ -2125,6 +2125,33 @@
"@ethersproject/properties" "^5.7.0"
"@ethersproject/strings" "^5.7.0"
"@floating-ui/core@^1.6.0":
version "1.6.7"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12"
integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==
dependencies:
"@floating-ui/utils" "^0.2.7"
"@floating-ui/dom@^1.0.0":
version "1.6.10"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f"
integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==
dependencies:
"@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.7"
"@floating-ui/react-dom@^2.0.8":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0"
integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==
dependencies:
"@floating-ui/dom" "^1.0.0"
"@floating-ui/utils@^0.2.7":
version "0.2.7"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e"
integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
version "9.3.0"
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
@@ -2557,11 +2584,44 @@
tweetnacl "^1.0.3"
tweetnacl-util "^0.15.1"
"@mui/base@5.0.0-beta.40":
version "5.0.0-beta.40"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2"
integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==
dependencies:
"@babel/runtime" "^7.23.9"
"@floating-ui/react-dom" "^2.0.8"
"@mui/types" "^7.2.14"
"@mui/utils" "^5.15.14"
"@popperjs/core" "^2.11.8"
clsx "^2.1.0"
prop-types "^15.8.1"
"@mui/core-downloads-tracker@^5.16.4":
version "5.16.4"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.4.tgz#a34de72acd7e81fdbcc7eeb07786205e90dda148"
integrity sha512-rNdHXhclwjEZnK+//3SR43YRx0VtjdHnUFhMSGYmAMJve+KiwEja/41EYh8V3pZKqF2geKyfcFUenTfDTYUR4w==
"@mui/icons-material@^5.16.7":
version "5.16.7"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.16.7.tgz#e27f901af792065efc9f3d75d74a66af7529a10a"
integrity sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/lab@^5.0.0-alpha.173":
version "5.0.0-alpha.173"
resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.173.tgz#a0f9696d93a765b48d69a7da5aaca0affa510ae8"
integrity sha512-Gt5zopIWwxDgGy/MXcp6GueD84xFFugFai4hYiXY0zowJpTVnIrTQCQXV004Q7rejJ7aaCntX9hpPJqCrioshA==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/base" "5.0.0-beta.40"
"@mui/system" "^5.16.5"
"@mui/types" "^7.2.15"
"@mui/utils" "^5.16.5"
clsx "^2.1.0"
prop-types "^15.8.1"
"@mui/material@^5.16.4":
version "5.16.4"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.4.tgz#992d630637d9d38620e4937fb11d0a97965fdabf"
@@ -2589,6 +2649,15 @@
"@mui/utils" "^5.16.4"
prop-types "^15.8.1"
"@mui/private-theming@^5.16.6":
version "5.16.6"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.16.6.tgz#547671e7ae3f86b68d1289a0b90af04dfcc1c8c9"
integrity sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/utils" "^5.16.6"
prop-types "^15.8.1"
"@mui/styled-engine@^5.16.4":
version "5.16.4"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.4.tgz#a7a8c9079c307bab91ccd65ed5dd1496ddf2a3ab"
@@ -2599,6 +2668,16 @@
csstype "^3.1.3"
prop-types "^15.8.1"
"@mui/styled-engine@^5.16.6":
version "5.16.6"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.6.tgz#60110c106dd482dfdb7e2aa94fd6490a0a3f8852"
integrity sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g==
dependencies:
"@babel/runtime" "^7.23.9"
"@emotion/cache" "^11.11.0"
csstype "^3.1.3"
prop-types "^15.8.1"
"@mui/system@^5.16.4":
version "5.16.4"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.4.tgz#c03f971ed273f0ad06c69c949c05e866ad211407"
@@ -2613,11 +2692,37 @@
csstype "^3.1.3"
prop-types "^15.8.1"
"@mui/types@^7.2.15":
"@mui/system@^5.16.5":
version "5.16.7"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.7.tgz#4583ca5bf3b38942e02c15a1e622ba869ac51393"
integrity sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/private-theming" "^5.16.6"
"@mui/styled-engine" "^5.16.6"
"@mui/types" "^7.2.15"
"@mui/utils" "^5.16.6"
clsx "^2.1.0"
csstype "^3.1.3"
prop-types "^15.8.1"
"@mui/types@^7.2.14", "@mui/types@^7.2.15":
version "7.2.15"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.15.tgz#dadd232fe9a70be0d526630675dff3b110f30b53"
integrity sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==
"@mui/utils@^5.15.14", "@mui/utils@^5.16.5", "@mui/utils@^5.16.6":
version "5.16.6"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.6.tgz#905875bbc58d3dcc24531c3314a6807aba22a711"
integrity sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==
dependencies:
"@babel/runtime" "^7.23.9"
"@mui/types" "^7.2.15"
"@types/prop-types" "^15.7.12"
clsx "^2.1.1"
prop-types "^15.8.1"
react-is "^18.3.1"
"@mui/utils@^5.16.4":
version "5.16.4"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.4.tgz#8e50e27a630e3d8eeb3e9d3bc31cbb0e4956f5fd"
@@ -3834,7 +3939,7 @@
expect "^29.0.0"
pretty-format "^29.0.0"
"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
"@types/json-schema@*", "@types/json-schema@^7.0.12", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
@@ -3973,7 +4078,7 @@
dependencies:
"@types/node" "*"
"@types/semver@^7.3.12":
"@types/semver@^7.3.12", "@types/semver@^7.5.0":
version "7.5.8"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
@@ -4075,6 +4180,23 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/eslint-plugin@^6.13.2":
version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3"
integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==
dependencies:
"@eslint-community/regexpp" "^4.5.1"
"@typescript-eslint/scope-manager" "6.21.0"
"@typescript-eslint/type-utils" "6.21.0"
"@typescript-eslint/utils" "6.21.0"
"@typescript-eslint/visitor-keys" "6.21.0"
debug "^4.3.4"
graphemer "^1.4.0"
ignore "^5.2.4"
natural-compare "^1.4.0"
semver "^7.5.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/experimental-utils@^5.0.0":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741"
@@ -4092,6 +4214,17 @@
"@typescript-eslint/typescript-estree" "5.62.0"
debug "^4.3.4"
"@typescript-eslint/parser@^6.13.2":
version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b"
integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==
dependencies:
"@typescript-eslint/scope-manager" "6.21.0"
"@typescript-eslint/types" "6.21.0"
"@typescript-eslint/typescript-estree" "6.21.0"
"@typescript-eslint/visitor-keys" "6.21.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@5.62.0":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c"
@@ -4100,6 +4233,14 @@
"@typescript-eslint/types" "5.62.0"
"@typescript-eslint/visitor-keys" "5.62.0"
"@typescript-eslint/scope-manager@6.21.0":
version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1"
integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==
dependencies:
"@typescript-eslint/types" "6.21.0"
"@typescript-eslint/visitor-keys" "6.21.0"
"@typescript-eslint/type-utils@5.62.0":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a"
@@ -4110,11 +4251,26 @@
debug "^4.3.4"
tsutils "^3.21.0"
"@typescript-eslint/type-utils@6.21.0":
version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e"
integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==
dependencies:
"@typescript-eslint/typescript-estree" "6.21.0"
"@typescript-eslint/utils" "6.21.0"
debug "^4.3.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/types@5.62.0":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
"@typescript-eslint/types@6.21.0":
version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d"
integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==
"@typescript-eslint/typescript-estree@5.62.0":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
@@ -4128,6 +4284,20 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/typescript-estree@6.21.0":
version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46"
integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==
dependencies:
"@typescript-eslint/types" "6.21.0"
"@typescript-eslint/visitor-keys" "6.21.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
minimatch "9.0.3"
semver "^7.5.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.58.0":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
@@ -4142,6 +4312,19 @@
eslint-scope "^5.1.1"
semver "^7.3.7"
"@typescript-eslint/utils@6.21.0":
version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134"
integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==
dependencies:
"@eslint-community/eslint-utils" "^4.4.0"
"@types/json-schema" "^7.0.12"
"@types/semver" "^7.5.0"
"@typescript-eslint/scope-manager" "6.21.0"
"@typescript-eslint/types" "6.21.0"
"@typescript-eslint/typescript-estree" "6.21.0"
semver "^7.5.4"
"@typescript-eslint/visitor-keys@5.62.0":
version "5.62.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
@@ -4150,6 +4333,14 @@
"@typescript-eslint/types" "5.62.0"
eslint-visitor-keys "^3.3.0"
"@typescript-eslint/visitor-keys@6.21.0":
version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47"
integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==
dependencies:
"@typescript-eslint/types" "6.21.0"
eslint-visitor-keys "^3.4.1"
"@ungap/structured-clone@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
@@ -7101,7 +7292,7 @@ eslint-plugin-react-hooks@^4.3.0:
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596"
integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==
eslint-plugin-react@^7.27.1:
eslint-plugin-react@^7.27.1, eslint-plugin-react@^7.33.2:
version "7.35.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41"
integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==
@@ -8323,6 +8514,11 @@ human-signals@^5.0.0:
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
husky@^9.0.11:
version "9.1.4"
resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.4.tgz#926fd19c18d345add5eab0a42b2b6d9a80259b34"
integrity sha512-bho94YyReb4JV7LYWRWxZ/xr6TtOTt8cMfmQ39MQYJ7f/YE268s3GdghGwi+y4zAeqewE5zYLvuhV0M0ijsDEA==
hyphenate-style-name@^1.0.3:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
@@ -8369,7 +8565,7 @@ ieee754@^1.1.13, ieee754@^1.2.1:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
ignore@^5.2.0:
ignore@^5.2.0, ignore@^5.2.4:
version "5.3.1"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
@@ -10374,6 +10570,13 @@ minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
minimatch@9.0.3:
version "9.0.3"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825"
integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==
dependencies:
brace-expansion "^2.0.1"
minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -13646,6 +13849,11 @@ tryer@^1.0.1:
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
ts-api-utils@^1.0.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1"
integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==
ts-interface-checker@^0.1.9:
version "0.1.13"
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"