Compare commits
52 Commits
iv-fix-pai
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
d0623be1c3 | ||
fe3d2411a2 | |||
f5b92af4f9 | |||
6c27fd212a | |||
e1514e98af | |||
36208870ab | |||
e24d697c6d | |||
f47850ebfa | |||
|
b2eafe59b3 | ||
|
713f8bc0bb | ||
6d5fcf798d | |||
3a0a321c6f | |||
59176ad7cb | |||
0b4ceae6b2 | |||
cbb28a6eb3 | |||
9d2e710632 | |||
b527a9486d | |||
b94fd22c76 | |||
657c39e5ed | |||
e5c5d13c77 | |||
|
edde3ab2b7 | ||
|
a20b5ad113 | ||
|
7ca019a83e | ||
296bb3632b | |||
360f3b76bc | |||
|
44e6670aab | ||
|
3aad056abe | ||
3d9b3408d9 | |||
|
053487da74 | ||
699cc5379e | |||
e05ce4659e | |||
ec3617ad42 | |||
393a42fceb | |||
4ff1c10699 | |||
17810801dd | |||
|
da3e4534d0 | ||
a1747b8ba7 | |||
|
c818bc0b9d | ||
|
b2a043068a | ||
|
f67569a25e | ||
|
38d68a86d5 | ||
7b72631206 | |||
45e045d956 | |||
181f9e8b7d | |||
cca89775aa | |||
|
afd09dc4c1 | ||
e0632d1a50 | |||
ba05a82406 | |||
2bb92205ba | |||
c26bddec1a | |||
6f174c6e0c | |||
2455dd982b |
@ -1,5 +1,9 @@
|
|||||||
REACT_APP_WALLET_CONNECT_PROJECT_ID=
|
REACT_APP_WALLET_CONNECT_PROJECT_ID=
|
||||||
|
|
||||||
REACT_APP_DEFAULT_GAS_PRICE=0.025
|
REACT_APP_DEFAULT_GAS_PRICE=0.025
|
||||||
# Reference: https://github.com/cosmos/cosmos-sdk/issues/16020
|
# Reference: https://github.com/cosmos/cosmos-sdk/issues/16020
|
||||||
REACT_APP_GAS_ADJUSTMENT=2
|
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
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
build
|
17
.eslintrc.json
Normal file
17
.eslintrc.json
Normal 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
.husky/pre-commit
Normal file
1
.husky/pre-commit
Normal file
@ -0,0 +1 @@
|
|||||||
|
yarn lint
|
@ -2,52 +2,56 @@
|
|||||||
const webpack = require('webpack')
|
const webpack = require('webpack')
|
||||||
|
|
||||||
module.exports = function override(config, env) {
|
module.exports = function override(config, env) {
|
||||||
config.module.rules.push({
|
config.module.rules.push({
|
||||||
test: /\.js$/,
|
test: /\.js$/,
|
||||||
exclude: /node_modules[/\\](?!react-native-vector-icons)/,
|
exclude: /node_modules[/\\](?!react-native-vector-icons)/,
|
||||||
use: {
|
use: {
|
||||||
loader: "babel-loader",
|
loader: "babel-loader",
|
||||||
options: {
|
options: {
|
||||||
// Disable reading babel configuration
|
// Disable reading babel configuration
|
||||||
babelrc: false,
|
babelrc: false,
|
||||||
configFile: false,
|
configFile: false,
|
||||||
|
|
||||||
// The configuration for compilation
|
// The configuration for compilation
|
||||||
presets: [
|
presets: [
|
||||||
["@babel/preset-env", { useBuiltIns: "usage", "corejs": "3" }],
|
["@babel/preset-env", { useBuiltIns: "usage", "corejs": "3" }],
|
||||||
"@babel/preset-react",
|
"@babel/preset-react",
|
||||||
"@babel/preset-flow",
|
"@babel/preset-flow",
|
||||||
"@babel/preset-typescript",
|
"@babel/preset-typescript",
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
"@babel/plugin-proposal-class-properties",
|
"@babel/plugin-proposal-class-properties",
|
||||||
"@babel/plugin-proposal-object-rest-spread",
|
"@babel/plugin-proposal-object-rest-spread",
|
||||||
"@babel/plugin-transform-modules-commonjs"
|
"@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"],
|
||||||
|
}),
|
||||||
|
require("@import-meta-env/unplugin").webpack({
|
||||||
|
example: ".env.example",
|
||||||
|
env: ".env",
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
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;
|
||||||
};
|
};
|
||||||
|
24
package.json
24
package.json
@ -1,11 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "web-wallet",
|
"name": "web-wallet",
|
||||||
"version": "0.1.0",
|
"version": "0.1.7",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cerc-io/registry-sdk": "^0.2.5",
|
"@cerc-io/registry-sdk": "^0.2.5",
|
||||||
"@cosmjs/amino": "^0.32.3",
|
"@cosmjs/amino": "^0.32.3",
|
||||||
"@cosmjs/crypto": "^0.32.3",
|
"@cosmjs/crypto": "^0.32.3",
|
||||||
|
"@cosmjs/encoding": "^0.33.1",
|
||||||
"@cosmjs/proto-signing": "^0.32.3",
|
"@cosmjs/proto-signing": "^0.32.3",
|
||||||
"@cosmjs/stargate": "^0.32.3",
|
"@cosmjs/stargate": "^0.32.3",
|
||||||
"@emotion/react": "^11.13.0",
|
"@emotion/react": "^11.13.0",
|
||||||
@ -13,6 +14,8 @@
|
|||||||
"@ethersproject/shims": "^5.7.0",
|
"@ethersproject/shims": "^5.7.0",
|
||||||
"@hookform/resolvers": "^3.3.4",
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@json-rpc-tools/utils": "^1.7.6",
|
"@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",
|
"@mui/material": "^5.16.4",
|
||||||
"@react-navigation/elements": "^1.3.30",
|
"@react-navigation/elements": "^1.3.30",
|
||||||
"@react-navigation/native": "^6.1.10",
|
"@react-navigation/native": "^6.1.10",
|
||||||
@ -26,6 +29,7 @@
|
|||||||
"cosmjs-types": "^0.9.0",
|
"cosmjs-types": "^0.9.0",
|
||||||
"ethers": "5.7.2",
|
"ethers": "5.7.2",
|
||||||
"https-browserify": "^1.0.0",
|
"https-browserify": "^1.0.0",
|
||||||
|
"json-bigint": "^1.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
@ -48,7 +52,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-app-rewired start",
|
"start": "react-app-rewired start",
|
||||||
"build": "react-app-rewired build",
|
"build": "react-app-rewired build",
|
||||||
|
"set-env": "import-meta-env -x .env.example -p build/index.html",
|
||||||
"test": "react-app-rewired test",
|
"test": "react-app-rewired test",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"prepare": "husky",
|
||||||
"eject": "react-scripts eject"
|
"eject": "react-scripts eject"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
@ -71,12 +78,25 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
|
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
|
||||||
|
"@import-meta-env/cli": "^0.7.3",
|
||||||
|
"@import-meta-env/typescript": "^0.4.0",
|
||||||
|
"@import-meta-env/unplugin": "^0.6.2",
|
||||||
|
"@types/json-bigint": "^1.0.4",
|
||||||
"@types/lodash": "^4.17.7",
|
"@types/lodash": "^4.17.7",
|
||||||
"@types/node": "^16.7.13",
|
"@types/node": "^16.7.13",
|
||||||
"@types/react": "^18.0.0",
|
"@types/react": "^18.0.0",
|
||||||
"@types/react-dom": "^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",
|
"crypto-browserify": "^3.12.0",
|
||||||
|
"dotenv": "^16.5.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",
|
"react-app-rewired": "^2.2.1",
|
||||||
"stream-browserify": "^3.0.0"
|
"stream-browserify": "^3.0.0"
|
||||||
}
|
},
|
||||||
|
"packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
|
||||||
}
|
}
|
||||||
|
18
prettier.config.js
Normal file
18
prettier.config.js
Normal 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
public/Logo.svg
Normal file
10
public/Logo.svg
Normal 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 |
@ -1,21 +1,24 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
<head>
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta
|
<meta name="theme-color" content="#000000" />
|
||||||
name="description"
|
<meta name="description" content="Laconic Wallet Web App" />
|
||||||
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="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<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
|
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/
|
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.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
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.
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
@ -24,12 +27,61 @@
|
|||||||
work correctly both with client-side routing and a non-root public URL.
|
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`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>Laconic Wallet</title>
|
<title>Laconic Wallet</title>
|
||||||
</head>
|
<script>
|
||||||
<body>
|
globalThis.import_meta_env = JSON.parse('"import_meta_env_placeholder"');
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
</script>
|
||||||
<div id="root"></div>
|
<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.
|
This HTML file is a template.
|
||||||
If you open it directly in the browser, you will see an empty page.
|
If you open it directly in the browser, you will see an empty page.
|
||||||
|
|
||||||
@ -39,5 +91,6 @@
|
|||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
-->
|
-->
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
@ -21,5 +21,5 @@
|
|||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"background_color": "#ffffff"
|
"background_color": "#0f0f0f"
|
||||||
}
|
}
|
||||||
|
247
src/App.tsx
247
src/App.tsx
@ -1,60 +1,75 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||||
import { Button, Snackbar, Text } from 'react-native-paper';
|
import { Button, Snackbar, Surface, Text } from "react-native-paper";
|
||||||
import { TxBody, AuthInfo } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
|
import { TxBody, AuthInfo } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
||||||
|
|
||||||
import { SignClientTypes } from '@walletconnect/types';
|
import { SignClientTypes } from "@walletconnect/types";
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from "@react-navigation/native";
|
||||||
|
import { DirectSecp256k1Wallet } from "@cosmjs/proto-signing";
|
||||||
|
import { SigningStargateClient } from "@cosmjs/stargate";
|
||||||
import {
|
import {
|
||||||
createStackNavigator,
|
createStackNavigator,
|
||||||
StackNavigationProp,
|
StackNavigationProp,
|
||||||
} from '@react-navigation/stack';
|
} from "@react-navigation/stack";
|
||||||
import { getSdkError } from '@walletconnect/utils';
|
import { getSdkError } from "@walletconnect/utils";
|
||||||
import { Web3WalletTypes } from '@walletconnect/web3wallet';
|
import { Web3WalletTypes } from "@walletconnect/web3wallet";
|
||||||
import { formatJsonRpcResult } from '@json-rpc-tools/utils';
|
import { formatJsonRpcResult } from "@json-rpc-tools/utils";
|
||||||
|
|
||||||
import PairingModal from './components/PairingModal';
|
import PairingModal from "./components/PairingModal";
|
||||||
import { useWalletConnect } from './context/WalletConnectContext';
|
import { useWalletConnect } from "./context/WalletConnectContext";
|
||||||
import { useAccounts } from './context/AccountsContext';
|
import { useAccounts } from "./context/AccountsContext";
|
||||||
import InvalidPath from './screens/InvalidPath';
|
import InvalidPath from "./screens/InvalidPath";
|
||||||
import SignMessage from './screens/SignMessage';
|
import SignMessage from "./screens/SignMessage";
|
||||||
import HomeScreen from './screens/HomeScreen';
|
import HomeScreen from "./screens/HomeScreen";
|
||||||
import SignRequest from './screens/SignRequest';
|
import SignRequest from "./screens/SignRequest";
|
||||||
import AddSession from './screens/AddSession';
|
import AddSession from "./screens/AddSession";
|
||||||
import WalletConnect from './screens/WalletConnect';
|
import WalletConnect from "./screens/WalletConnect";
|
||||||
import ApproveTransaction from './screens/ApproveTransaction';
|
import ApproveTransaction from "./screens/ApproveTransaction";
|
||||||
import { StackParamsList } from './types';
|
import { StackParamsList } from "./types";
|
||||||
import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data';
|
import { EIP155_SIGNING_METHODS } from "./utils/wallet-connect/EIP155Data";
|
||||||
import { getSignParamsMessage } from './utils/wallet-connect/helpers';
|
import { getSignParamsMessage } from "./utils/wallet-connect/helpers";
|
||||||
import ApproveTransfer from './screens/ApproveTransfer';
|
import ApproveTransfer from "./screens/ApproveTransfer";
|
||||||
import AddNetwork from './screens/AddNetwork';
|
import AddNetwork from "./screens/AddNetwork";
|
||||||
import EditNetwork from './screens/EditNetwork';
|
import EditNetwork from "./screens/EditNetwork";
|
||||||
import { COSMOS, EIP155 } from './utils/constants';
|
import { CHECK_BALANCE, COSMOS, EIP155, IS_SUFFICIENT } from "./utils/constants";
|
||||||
import { useNetworks } from './context/NetworksContext';
|
import { useNetworks } from "./context/NetworksContext";
|
||||||
import { NETWORK_METHODS } from './utils/wallet-connect/common-data';
|
import { NETWORK_METHODS } from "./utils/wallet-connect/common-data";
|
||||||
import { COSMOS_METHODS } from './utils/wallet-connect/COSMOSData';
|
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";
|
||||||
|
import { SignTxEmbed } from "./screens/SignTxEmbed";
|
||||||
|
import useCreateNetwork from "./hooks/useCreateNetwork";
|
||||||
|
|
||||||
const Stack = createStackNavigator<StackParamsList>();
|
const Stack = createStackNavigator<StackParamsList>();
|
||||||
|
|
||||||
const App = (): React.JSX.Element => {
|
const App = (): React.JSX.Element => {
|
||||||
const navigation =
|
const navigation = useNavigation<StackNavigationProp<StackParamsList>>();
|
||||||
useNavigation<StackNavigationProp<StackParamsList>>();
|
|
||||||
|
|
||||||
const { web3wallet, setActiveSessions } = useWalletConnect();
|
const { web3wallet, setActiveSessions } = useWalletConnect();
|
||||||
const { accounts, setCurrentIndex } = useAccounts();
|
const { accounts, setCurrentIndex } = useAccounts();
|
||||||
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
|
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
|
||||||
|
const { getAccountsData } = useAccountsData();
|
||||||
|
|
||||||
const [modalVisible, setModalVisible] = useState(false);
|
const [modalVisible, setModalVisible] = useState(false);
|
||||||
const [toastVisible, setToastVisible] = useState(false);
|
const [toastVisible, setToastVisible] = useState(false);
|
||||||
const [currentProposal, setCurrentProposal] = useState<
|
const [currentProposal, setCurrentProposal] = useState<
|
||||||
SignClientTypes.EventArguments['session_proposal'] | undefined
|
SignClientTypes.EventArguments["session_proposal"] | undefined
|
||||||
>();
|
>();
|
||||||
|
|
||||||
const onSessionProposal = useCallback(
|
const onSessionProposal = useCallback(
|
||||||
async (proposal: SignClientTypes.EventArguments['session_proposal']) => {
|
async (proposal: SignClientTypes.EventArguments["session_proposal"]) => {
|
||||||
if (!accounts.length || !accounts.length) {
|
if (!accounts.length || !accounts.length) {
|
||||||
const { id } = proposal;
|
const { id } = proposal;
|
||||||
await web3wallet!.rejectSession({
|
await web3wallet!.rejectSession({
|
||||||
id,
|
id,
|
||||||
reason: getSdkError('UNSUPPORTED_ACCOUNTS'),
|
reason: getSdkError("UNSUPPORTED_ACCOUNTS"),
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -74,10 +89,11 @@ const App = (): React.JSX.Element => {
|
|||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case NETWORK_METHODS.GET_NETWORKS:
|
case NETWORK_METHODS.GET_NETWORKS:
|
||||||
const currentNetworkId = networksData.find(
|
const currentNetworkId = networksData.find(
|
||||||
networkData => networkData.networkId === selectedNetwork!.networkId,
|
(networkData) =>
|
||||||
|
networkData.networkId === selectedNetwork!.networkId,
|
||||||
)?.networkId;
|
)?.networkId;
|
||||||
|
|
||||||
const networkNamesData = networksData.map(networkData => {
|
const networkNamesData = networksData.map((networkData) => {
|
||||||
return {
|
return {
|
||||||
id: networkData.networkId,
|
id: networkData.networkId,
|
||||||
name: networkData.networkName,
|
name: networkData.networkName,
|
||||||
@ -98,13 +114,13 @@ const App = (): React.JSX.Element => {
|
|||||||
case NETWORK_METHODS.CHANGE_NETWORK:
|
case NETWORK_METHODS.CHANGE_NETWORK:
|
||||||
const networkNameData = request.params[0];
|
const networkNameData = request.params[0];
|
||||||
const network = networksData.find(
|
const network = networksData.find(
|
||||||
networkData => networkData.networkId === networkNameData.id,
|
(networkData) => networkData.networkId === networkNameData.id,
|
||||||
);
|
);
|
||||||
setCurrentIndex(0);
|
setCurrentIndex(0);
|
||||||
setSelectedNetwork(network);
|
setSelectedNetwork(network);
|
||||||
|
|
||||||
const response = formatJsonRpcResult(id, {
|
const response = formatJsonRpcResult(id, {
|
||||||
response: 'true',
|
response: "true",
|
||||||
});
|
});
|
||||||
|
|
||||||
await web3wallet!.respondSessionRequest({
|
await web3wallet!.respondSessionRequest({
|
||||||
@ -114,7 +130,7 @@ const App = (): React.JSX.Element => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
|
case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION:
|
||||||
navigation.navigate('ApproveTransfer', {
|
navigation.navigate("ApproveTransfer", {
|
||||||
transaction: request.params[0],
|
transaction: request.params[0],
|
||||||
requestEvent,
|
requestEvent,
|
||||||
requestSessionData,
|
requestSessionData,
|
||||||
@ -122,7 +138,7 @@ const App = (): React.JSX.Element => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
|
case EIP155_SIGNING_METHODS.PERSONAL_SIGN:
|
||||||
navigation.navigate('SignRequest', {
|
navigation.navigate("SignRequest", {
|
||||||
namespace: EIP155,
|
namespace: EIP155,
|
||||||
address: request.params[1],
|
address: request.params[1],
|
||||||
message: getSignParamsMessage(request.params),
|
message: getSignParamsMessage(request.params),
|
||||||
@ -136,19 +152,19 @@ const App = (): React.JSX.Element => {
|
|||||||
txbody: TxBody.toJSON(
|
txbody: TxBody.toJSON(
|
||||||
TxBody.decode(
|
TxBody.decode(
|
||||||
Uint8Array.from(
|
Uint8Array.from(
|
||||||
Buffer.from(request.params.signDoc.bodyBytes, 'hex'),
|
Buffer.from(request.params.signDoc.bodyBytes, "hex"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
authInfo: AuthInfo.toJSON(
|
authInfo: AuthInfo.toJSON(
|
||||||
AuthInfo.decode(
|
AuthInfo.decode(
|
||||||
Uint8Array.from(
|
Uint8Array.from(
|
||||||
Buffer.from(request.params.signDoc.authInfoBytes, 'hex'),
|
Buffer.from(request.params.signDoc.authInfoBytes, "hex"),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
navigation.navigate('SignRequest', {
|
navigation.navigate("SignRequest", {
|
||||||
namespace: COSMOS,
|
namespace: COSMOS,
|
||||||
address: request.params.signerAddress,
|
address: request.params.signerAddress,
|
||||||
message: JSON.stringify(message, undefined, 2),
|
message: JSON.stringify(message, undefined, 2),
|
||||||
@ -158,7 +174,7 @@ const App = (): React.JSX.Element => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case COSMOS_METHODS.COSMOS_SIGN_AMINO:
|
case COSMOS_METHODS.COSMOS_SIGN_AMINO:
|
||||||
navigation.navigate('SignRequest', {
|
navigation.navigate("SignRequest", {
|
||||||
namespace: COSMOS,
|
namespace: COSMOS,
|
||||||
address: request.params.signerAddress,
|
address: request.params.signerAddress,
|
||||||
message: request.params.signDoc.memo,
|
message: request.params.signDoc.memo,
|
||||||
@ -168,7 +184,7 @@ const App = (): React.JSX.Element => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case COSMOS_METHODS.COSMOS_SEND_TOKENS:
|
case COSMOS_METHODS.COSMOS_SEND_TOKENS:
|
||||||
navigation.navigate('ApproveTransfer', {
|
navigation.navigate("ApproveTransfer", {
|
||||||
transaction: request.params[0],
|
transaction: request.params[0],
|
||||||
requestEvent,
|
requestEvent,
|
||||||
requestSessionData,
|
requestSessionData,
|
||||||
@ -177,7 +193,7 @@ const App = (): React.JSX.Element => {
|
|||||||
|
|
||||||
case COSMOS_METHODS.COSMOS_SEND_TRANSACTION:
|
case COSMOS_METHODS.COSMOS_SEND_TRANSACTION:
|
||||||
const { transactionMessage, signer } = request.params;
|
const { transactionMessage, signer } = request.params;
|
||||||
navigation.navigate('ApproveTransaction', {
|
navigation.navigate("ApproveTransaction", {
|
||||||
transactionMessage,
|
transactionMessage,
|
||||||
signer,
|
signer,
|
||||||
requestEvent,
|
requestEvent,
|
||||||
@ -186,7 +202,7 @@ const App = (): React.JSX.Element => {
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid method');
|
throw new Error("Invalid method");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[
|
[
|
||||||
@ -195,7 +211,7 @@ const App = (): React.JSX.Element => {
|
|||||||
setSelectedNetwork,
|
setSelectedNetwork,
|
||||||
setCurrentIndex,
|
setCurrentIndex,
|
||||||
selectedNetwork,
|
selectedNetwork,
|
||||||
web3wallet
|
web3wallet,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -205,18 +221,77 @@ const App = (): React.JSX.Element => {
|
|||||||
}, [setActiveSessions, web3wallet]);
|
}, [setActiveSessions, web3wallet]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
web3wallet?.on('session_proposal', onSessionProposal);
|
web3wallet?.on("session_proposal", onSessionProposal);
|
||||||
web3wallet?.on('session_request', onSessionRequest);
|
web3wallet?.on("session_request", onSessionRequest);
|
||||||
web3wallet?.on('session_delete', onSessionDelete);
|
web3wallet?.on("session_delete", onSessionDelete);
|
||||||
return () => {
|
return () => {
|
||||||
web3wallet?.off('session_proposal', onSessionProposal);
|
web3wallet?.off("session_proposal", onSessionProposal);
|
||||||
web3wallet?.off('session_request', onSessionRequest);
|
web3wallet?.off("session_request", onSessionRequest);
|
||||||
web3wallet?.off('session_delete', onSessionDelete);
|
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();
|
||||||
|
useCreateNetwork();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Surface style={styles.appSurface}>
|
||||||
<Stack.Navigator
|
<Stack.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
headerBackTitleVisible: true,
|
headerBackTitleVisible: true,
|
||||||
@ -227,7 +302,9 @@ const App = (): React.JSX.Element => {
|
|||||||
component={HomeScreen}
|
component={HomeScreen}
|
||||||
options={{
|
options={{
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
headerTitle: () => <Text variant="titleLarge">Laconic Wallet</Text>,
|
header: () => (
|
||||||
|
<Header title="Wallet" showWalletConnect={showWalletConnect} />
|
||||||
|
),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@ -235,7 +312,7 @@ const App = (): React.JSX.Element => {
|
|||||||
component={SignMessage}
|
component={SignMessage}
|
||||||
options={{
|
options={{
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
headerTitle: () => <Text variant="titleLarge">Sign Message</Text>,
|
header: () => <Header title="Wallet" />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@ -243,7 +320,7 @@ const App = (): React.JSX.Element => {
|
|||||||
component={SignRequest}
|
component={SignRequest}
|
||||||
options={{
|
options={{
|
||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
headerTitle: () => <Text variant="titleLarge">Sign Request</Text>,
|
header: () => <Header title="Wallet" />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@ -254,11 +331,11 @@ const App = (): React.JSX.Element => {
|
|||||||
headerTitle: () => <Text variant="titleLarge">Bad Request</Text>,
|
headerTitle: () => <Text variant="titleLarge">Bad Request</Text>,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="AddSession"
|
name="AddSession"
|
||||||
component={AddSession}
|
component={AddSession}
|
||||||
options={{
|
options={{
|
||||||
title: 'New session',
|
header: () => <Header title="Wallet" />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
@ -271,8 +348,9 @@ const App = (): React.JSX.Element => {
|
|||||||
headerRight: () => (
|
headerRight: () => (
|
||||||
<Button
|
<Button
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigation.navigate('AddSession');
|
navigation.navigate("AddSession");
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
{<Text>PAIR</Text>}
|
{<Text>PAIR</Text>}
|
||||||
</Button>
|
</Button>
|
||||||
),
|
),
|
||||||
@ -282,28 +360,56 @@ const App = (): React.JSX.Element => {
|
|||||||
name="ApproveTransfer"
|
name="ApproveTransfer"
|
||||||
component={ApproveTransfer}
|
component={ApproveTransfer}
|
||||||
options={{
|
options={{
|
||||||
title: 'Approve transfer',
|
header: () => <Header title="Wallet" />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="AddNetwork"
|
name="AddNetwork"
|
||||||
component={AddNetwork}
|
component={AddNetwork}
|
||||||
options={{
|
options={{
|
||||||
title: 'Add Network',
|
header: () => <Header title="Wallet" />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="EditNetwork"
|
name="EditNetwork"
|
||||||
component={EditNetwork}
|
component={EditNetwork}
|
||||||
options={{
|
options={{
|
||||||
title: 'Edit Network',
|
header: () => <Header title="Wallet" />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Stack.Screen
|
<Stack.Screen
|
||||||
name="ApproveTransaction"
|
name="ApproveTransaction"
|
||||||
component={ApproveTransaction}
|
component={ApproveTransaction}
|
||||||
options={{
|
options={{
|
||||||
title: 'Approve Transaction',
|
header: () => <Header title="Wallet" />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="wallet-embed"
|
||||||
|
component={WalletEmbed}
|
||||||
|
options={{
|
||||||
|
header: () => <></>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="sign-tx-request-embed"
|
||||||
|
component={SignTxEmbed}
|
||||||
|
options={{
|
||||||
|
header: () => <></>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="auto-sign-in"
|
||||||
|
component={AutoSignIn}
|
||||||
|
options={{
|
||||||
|
header: () => <></>,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="sign-message-request-embed"
|
||||||
|
component={SignRequestEmbed}
|
||||||
|
options={{
|
||||||
|
header: () => <Header title="Wallet" />,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
@ -317,10 +423,11 @@ const App = (): React.JSX.Element => {
|
|||||||
<Snackbar
|
<Snackbar
|
||||||
visible={toastVisible}
|
visible={toastVisible}
|
||||||
onDismiss={() => setToastVisible(false)}
|
onDismiss={() => setToastVisible(false)}
|
||||||
duration={3000}>
|
duration={3000}
|
||||||
|
>
|
||||||
Session approved
|
Session approved
|
||||||
</Snackbar>
|
</Snackbar>
|
||||||
</>
|
</Surface>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { View } from 'react-native';
|
|
||||||
import { Text } from 'react-native-paper';
|
|
||||||
|
|
||||||
import { Account } from '../types';
|
import { Account } from "../types";
|
||||||
import styles from '../styles/stylesheet';
|
import { Box, Stack, Typography } from "@mui/material";
|
||||||
|
|
||||||
interface AccountDetailsProps {
|
interface AccountDetailsProps {
|
||||||
account: Account | undefined;
|
account: Account | undefined;
|
||||||
@ -11,20 +9,28 @@ interface AccountDetailsProps {
|
|||||||
|
|
||||||
const AccountDetails: React.FC<AccountDetailsProps> = ({ account }) => {
|
const AccountDetails: React.FC<AccountDetailsProps> = ({ account }) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.accountContainer}>
|
<Box sx={{ marginY: 4 }}>
|
||||||
<Text variant="bodyLarge" selectable={true}>
|
<Stack spacing={1}>
|
||||||
<Text style={styles.highlight}>Address: </Text>
|
<Stack flexDirection="row">
|
||||||
{account?.address}
|
<Typography color="secondary" sx={{ mr: 1 }}>
|
||||||
</Text>
|
Address:
|
||||||
<Text variant="bodyLarge" selectable={true}>
|
</Typography>
|
||||||
<Text style={styles.highlight}>Public Key: </Text>
|
<Typography color="text.primary">{account?.address}</Typography>
|
||||||
{account?.pubKey}
|
</Stack>
|
||||||
</Text>
|
</Stack>
|
||||||
<Text variant="bodyLarge">
|
<Stack flexDirection="row">
|
||||||
<Text style={styles.highlight}>HD Path: </Text>
|
<Typography color="secondary" sx={{ mr: 1 }}>
|
||||||
{account?.hdPath}
|
Public Key:
|
||||||
</Text>
|
</Typography>
|
||||||
</View>
|
<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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,22 +1,30 @@
|
|||||||
import React, { useEffect, useState } from 'react';
|
import React, { useEffect, useState } from "react";
|
||||||
import { TouchableOpacity, View } from 'react-native';
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import { Button, List, Text, useTheme } from 'react-native-paper';
|
import { List } from "react-native-paper";
|
||||||
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||||
|
|
||||||
import { StackParamsList, Account } from '../types';
|
import { StackParamsList, Account } from "../types";
|
||||||
import { addAccount } from '../utils/accounts';
|
import { addAccount } from "../utils/accounts";
|
||||||
import styles from '../styles/stylesheet';
|
import HDPathDialog from "./HDPathDialog";
|
||||||
import HDPathDialog from './HDPathDialog';
|
import AccountDetails from "./AccountDetails";
|
||||||
import AccountDetails from './AccountDetails';
|
import { useAccounts } from "../context/AccountsContext";
|
||||||
import { useAccounts } from '../context/AccountsContext';
|
import { useWalletConnect } from "../context/WalletConnectContext";
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
import { useNetworks } from "../context/NetworksContext";
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import ConfirmDialog from "./ConfirmDialog";
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
import { getNamespaces } from "../utils/wallet-connect/helpers";
|
||||||
import { getNamespaces } from '../utils/wallet-connect/helpers';
|
import ShowPKDialog from "./ShowPKDialog";
|
||||||
import ShowPKDialog from './ShowPKDialog';
|
import { setInternetCredentials } from "../utils/key-store";
|
||||||
import { setInternetCredentials } from '../utils/key-store';
|
import {
|
||||||
|
Accordion,
|
||||||
|
AccordionSummary,
|
||||||
|
Button,
|
||||||
|
Link,
|
||||||
|
Stack,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { LoadingButton } from "@mui/lab";
|
||||||
|
|
||||||
const Accounts = () => {
|
const Accounts = () => {
|
||||||
const navigation =
|
const navigation =
|
||||||
@ -27,16 +35,14 @@ const Accounts = () => {
|
|||||||
const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } =
|
const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } =
|
||||||
useNetworks();
|
useNetworks();
|
||||||
|
|
||||||
const {web3wallet} = useWalletConnect();
|
const { web3wallet } = useWalletConnect();
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
||||||
const [hdDialog, setHdDialog] = useState(false);
|
const [hdDialog, setHdDialog] = useState(false);
|
||||||
const [pathCode, setPathCode] = useState('');
|
const [pathCode, setPathCode] = useState("");
|
||||||
const [deleteNetworkDialog, setDeleteNetworkDialog] =
|
const [deleteNetworkDialog, setDeleteNetworkDialog] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const handlePress = () => setExpanded(!expanded);
|
const handlePress = () => setExpanded(!expanded);
|
||||||
|
|
||||||
const hideDeleteNetworkDialog = () => setDeleteNetworkDialog(false);
|
const hideDeleteNetworkDialog = () => setDeleteNetworkDialog(false);
|
||||||
@ -79,7 +85,7 @@ const Accounts = () => {
|
|||||||
|
|
||||||
const addAccountHandler = async () => {
|
const addAccountHandler = async () => {
|
||||||
setIsAccountCreating(true);
|
setIsAccountCreating(true);
|
||||||
const newAccount = await addAccount(selectedNetwork!);
|
const newAccount = await addAccount(selectedNetwork!.chainId);
|
||||||
setIsAccountCreating(false);
|
setIsAccountCreating(false);
|
||||||
if (newAccount) {
|
if (newAccount) {
|
||||||
updateAccounts(newAccount);
|
updateAccounts(newAccount);
|
||||||
@ -88,7 +94,7 @@ const Accounts = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderAccountItems = () =>
|
const renderAccountItems = () =>
|
||||||
accounts.map(account => (
|
accounts.map((account) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
key={account.index}
|
key={account.index}
|
||||||
title={`Account ${account.index + 1}`}
|
title={`Account ${account.index + 1}`}
|
||||||
@ -101,12 +107,12 @@ const Accounts = () => {
|
|||||||
|
|
||||||
const handleRemove = async () => {
|
const handleRemove = async () => {
|
||||||
const updatedNetworks = networksData.filter(
|
const updatedNetworks = networksData.filter(
|
||||||
networkData => selectedNetwork!.networkId !== networkData.networkId,
|
(networkData) => selectedNetwork!.networkId !== networkData.networkId,
|
||||||
);
|
);
|
||||||
|
|
||||||
await setInternetCredentials(
|
await setInternetCredentials(
|
||||||
'networks',
|
"networks",
|
||||||
'_',
|
"_",
|
||||||
JSON.stringify(updatedNetworks),
|
JSON.stringify(updatedNetworks),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -125,94 +131,74 @@ const Accounts = () => {
|
|||||||
updateAccounts={updateAccounts}
|
updateAccounts={updateAccounts}
|
||||||
pathCode={pathCode}
|
pathCode={pathCode}
|
||||||
/>
|
/>
|
||||||
<List.Accordion
|
<Accordion expanded={expanded} onClick={handlePress}>
|
||||||
title={`Account ${currentIndex + 1}`}
|
<AccordionSummary
|
||||||
expanded={expanded}
|
expandIcon={<ExpandMoreIcon />}
|
||||||
onPress={handlePress}>
|
>{`Account ${currentIndex + 1}`}</AccordionSummary>
|
||||||
{renderAccountItems()}
|
{renderAccountItems()}
|
||||||
</List.Accordion>
|
</Accordion>
|
||||||
|
|
||||||
<View style={styles.addAccountButton}>
|
<Stack direction="row" spacing={3} sx={{ mt: 4 }}>
|
||||||
<Button
|
<LoadingButton
|
||||||
mode="contained"
|
variant="contained"
|
||||||
onPress={addAccountHandler}
|
onClick={addAccountHandler}
|
||||||
loading={isAccountCreating}
|
loading={isAccountCreating}
|
||||||
disabled={isAccountCreating}>
|
disabled={isAccountCreating}
|
||||||
{isAccountCreating ? 'Adding' : 'Add Account'}
|
>
|
||||||
</Button>
|
{isAccountCreating ? "Adding" : "Add Account"}
|
||||||
</View>
|
</LoadingButton>
|
||||||
|
|
||||||
<View style={styles.addAccountButton}>
|
|
||||||
<Button
|
<Button
|
||||||
mode="contained"
|
variant="contained"
|
||||||
onPress={() => {
|
onClick={() => {
|
||||||
setHdDialog(true);
|
setHdDialog(true);
|
||||||
setPathCode(`m/44'/${selectedNetwork!.coinType}'/`);
|
setPathCode(`m/44'/${selectedNetwork!.coinType}'/`);
|
||||||
}}>
|
}}
|
||||||
|
>
|
||||||
Add Account from HD path
|
Add Account from HD path
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</Stack>
|
||||||
|
|
||||||
<AccountDetails account={accounts[currentIndex]} />
|
<AccountDetails account={accounts[currentIndex]} />
|
||||||
<View style={styles.linkContainer}>
|
<Stack direction="row" spacing={4}>
|
||||||
<View style={styles.signLink}>
|
<TouchableOpacity
|
||||||
<TouchableOpacity
|
onPress={() => {
|
||||||
onPress={() => {
|
navigation.navigate("SignMessage", {
|
||||||
navigation.navigate('SignMessage', {
|
selectedNamespace: selectedNetwork!.namespace,
|
||||||
selectedNamespace: selectedNetwork!.namespace,
|
selectedChainId: selectedNetwork!.chainId,
|
||||||
selectedChainId: selectedNetwork!.chainId,
|
accountInfo: accounts[currentIndex],
|
||||||
accountInfo: accounts[currentIndex],
|
});
|
||||||
});
|
}}
|
||||||
}}>
|
>
|
||||||
<Text
|
<Link>Sign Message</Link>
|
||||||
variant="titleSmall"
|
</TouchableOpacity>
|
||||||
style={[styles.hyperlink, { color: theme.colors.primary }]}>
|
|
||||||
Sign Message
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.signLink}>
|
<TouchableOpacity
|
||||||
<TouchableOpacity
|
onPress={() => {
|
||||||
onPress={() => {
|
navigation.navigate("AddNetwork");
|
||||||
navigation.navigate('AddNetwork');
|
}}
|
||||||
}}>
|
>
|
||||||
<Text
|
<Link>Add Network</Link>
|
||||||
variant="titleSmall"
|
</TouchableOpacity>
|
||||||
style={[styles.hyperlink, { color: theme.colors.primary }]}>
|
|
||||||
Add Network
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<View style={styles.signLink}>
|
<TouchableOpacity
|
||||||
<TouchableOpacity
|
onPress={() => {
|
||||||
onPress={() => {
|
navigation.navigate("EditNetwork", {
|
||||||
navigation.navigate('EditNetwork', {
|
selectedNetwork: selectedNetwork!,
|
||||||
selectedNetwork: selectedNetwork!,
|
});
|
||||||
});
|
}}
|
||||||
}}>
|
>
|
||||||
<Text
|
<Link>Edit Network</Link>
|
||||||
variant="titleSmall"
|
</TouchableOpacity>
|
||||||
style={[styles.hyperlink, { color: theme.colors.primary }]}>
|
|
||||||
Edit Network
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{!selectedNetwork!.isDefault && (
|
{!selectedNetwork!.isDefault && (
|
||||||
<View style={styles.signLink}>
|
<TouchableOpacity
|
||||||
<TouchableOpacity
|
onPress={() => {
|
||||||
onPress={() => {
|
setDeleteNetworkDialog(true);
|
||||||
setDeleteNetworkDialog(true);
|
}}
|
||||||
}}>
|
>
|
||||||
<Text
|
<Link>Delete Network</Link>
|
||||||
variant="titleSmall"
|
</TouchableOpacity>
|
||||||
style={[styles.hyperlink, { color: theme.colors.primary }]}>
|
|
||||||
Delete Network
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
)}
|
)}
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
title="Delete Network"
|
title="Delete Network"
|
||||||
@ -221,7 +207,7 @@ const Accounts = () => {
|
|||||||
onConfirm={handleRemove}
|
onConfirm={handleRemove}
|
||||||
/>
|
/>
|
||||||
<ShowPKDialog />
|
<ShowPKDialog />
|
||||||
</View>
|
</Stack>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography } from '@mui/material';
|
import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography } from '@mui/material';
|
||||||
import { ResetDialogProps } from '../types';
|
import { ResetDialogProps } from '../types';
|
||||||
|
import styles from "../styles/stylesheet";
|
||||||
|
|
||||||
const ConfirmDialog = ({
|
const ConfirmDialog = ({
|
||||||
title,
|
title,
|
||||||
@ -10,15 +11,15 @@ const ConfirmDialog = ({
|
|||||||
}: ResetDialogProps) => {
|
}: ResetDialogProps) => {
|
||||||
return (
|
return (
|
||||||
<Dialog open={visible} onClose={hideDialog}>
|
<Dialog open={visible} onClose={hideDialog}>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle style={{...styles.resetDialogTitle}} >{title}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent style={{...styles.resetDialogContent}}>
|
||||||
<Typography variant="body1">Are you sure?</Typography>
|
<Typography>Are you sure?</Typography>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions style={{...styles.resetDialogActionRow}}>
|
||||||
<Button color="error" onClick={onConfirm}>
|
<Button style={{...styles.buttonRed, ...styles.button}} color="error" onClick={onConfirm}>
|
||||||
Yes
|
Yes
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={hideDialog}>No</Button>
|
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideDialog}>No</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
21
src/components/Container.tsx
Normal file
21
src/components/Container.tsx
Normal 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>
|
||||||
|
);
|
@ -1,9 +1,8 @@
|
|||||||
import { View } from 'react-native';
|
import React from "react";
|
||||||
import React from 'react';
|
import { View } from "react-native";
|
||||||
import { Button } from 'react-native-paper';
|
import { LoadingButton } from "@mui/lab";
|
||||||
|
|
||||||
import { CreateWalletProps } from '../types';
|
import { CreateWalletProps } from "../types";
|
||||||
import styles from '../styles/stylesheet';
|
|
||||||
|
|
||||||
const CreateWallet = ({
|
const CreateWallet = ({
|
||||||
isWalletCreating,
|
isWalletCreating,
|
||||||
@ -11,14 +10,15 @@ const CreateWallet = ({
|
|||||||
}: CreateWalletProps) => {
|
}: CreateWalletProps) => {
|
||||||
return (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<View style={styles.createWalletContainer}>
|
<View>
|
||||||
<Button
|
<LoadingButton
|
||||||
mode="contained"
|
variant="contained"
|
||||||
loading={isWalletCreating}
|
loading={isWalletCreating}
|
||||||
disabled={isWalletCreating}
|
disabled={isWalletCreating}
|
||||||
onPress={createWalletHandler}>
|
onClick={createWalletHandler}
|
||||||
{isWalletCreating ? 'Creating' : 'Create Wallet'}
|
>
|
||||||
</Button>
|
{isWalletCreating ? "Creating" : "CREATE WALLET"}
|
||||||
|
</LoadingButton>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
@ -1,28 +1,44 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { Dialog, DialogActions, DialogContent, DialogTitle, Button, Typography } from '@mui/material';
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
Button,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
|
||||||
import styles from '../styles/stylesheet';
|
import styles from "../styles/stylesheet";
|
||||||
import GridView from './Grid';
|
import GridView from "./Grid";
|
||||||
import { CustomDialogProps } from '../types';
|
import { CustomDialogProps } from "../types";
|
||||||
|
|
||||||
|
const DialogComponent = ({
|
||||||
|
visible,
|
||||||
|
hideDialog,
|
||||||
|
contentText,
|
||||||
|
}: CustomDialogProps) => {
|
||||||
|
const words = contentText.split(" ");
|
||||||
|
|
||||||
const DialogComponent = ({ visible, hideDialog, contentText }: CustomDialogProps) => {
|
const copyMnemonic = () => {
|
||||||
const words = contentText.split(' ');
|
navigator.clipboard.writeText(contentText);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={visible} onClose={hideDialog}>
|
<Dialog open={visible} onClose={hideDialog}>
|
||||||
<DialogTitle>Mnemonic</DialogTitle>
|
<DialogTitle style={{...styles.mnemonicTitle}}>Mnemonic</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent style={{...styles.mnemonicContainer}}>
|
||||||
<Typography variant="h6" component="div" style={{ color: 'rgba(0, 0, 0, 0.87)' }}>
|
<Typography component="div">
|
||||||
Your mnemonic provides full access to your wallet and funds. Make sure to note it down.
|
Your mnemonic provides full access to your wallet and funds.<br/>
|
||||||
|
Make sure to note it down.
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="h6" component="div" style={{ ...styles.dialogWarning }}>
|
<Typography component="div" style={{ ...styles.mnemonicDialogWarning }}>
|
||||||
Do not share your mnemonic with anyone
|
Do not share your mnemonic with anyone
|
||||||
</Typography>
|
</Typography>
|
||||||
<GridView words={words} />
|
<GridView words={words} />
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions style={{...styles.mnemonicButtonRow}}>
|
||||||
<Button onClick={hideDialog}>Done</Button>
|
<Button style={{...styles.mnemonicButton}} onClick={copyMnemonic}>Copy</Button>
|
||||||
|
<Button style={{...styles.mnemonicButton}} onClick={hideDialog}>Done</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
|
@ -7,7 +7,7 @@ import { GridViewProps } from '../types';
|
|||||||
|
|
||||||
const GridView = ({ words }: GridViewProps) => {
|
const GridView = ({ words }: GridViewProps) => {
|
||||||
return (
|
return (
|
||||||
<View style={styles.gridContainer}>
|
<View style={styles.mnemonicGridContainer}>
|
||||||
{words.map((word, index) => (
|
{words.map((word, index) => (
|
||||||
<View key={index} style={styles.gridItem}>
|
<View key={index} style={styles.gridItem}>
|
||||||
<Text>{index + 1}. </Text>
|
<Text>{index + 1}. </Text>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { ScrollView, View, Text } from 'react-native';
|
import { ScrollView, View, Text } from "react-native";
|
||||||
import { Button, TextInput } from 'react-native-paper';
|
import { TextInput } from "react-native-paper";
|
||||||
|
|
||||||
import { addAccountFromHDPath } from '../utils/accounts';
|
import { addAccountFromHDPath } from "../utils/accounts";
|
||||||
import { Account, NetworksDataState, PathState } from '../types';
|
import { Account, NetworksDataState, PathState } from "../types";
|
||||||
import styles from '../styles/stylesheet';
|
import styles from "../styles/stylesheet";
|
||||||
import { useAccounts } from '../context/AccountsContext';
|
import { useAccounts } from "../context/AccountsContext";
|
||||||
|
import { LoadingButton } from "@mui/lab";
|
||||||
|
|
||||||
const HDPath = ({
|
const HDPath = ({
|
||||||
pathCode,
|
pathCode,
|
||||||
@ -21,19 +22,19 @@ const HDPath = ({
|
|||||||
const { setCurrentIndex } = useAccounts();
|
const { setCurrentIndex } = useAccounts();
|
||||||
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
||||||
const [path, setPath] = useState<PathState>({
|
const [path, setPath] = useState<PathState>({
|
||||||
firstNumber: '',
|
firstNumber: "",
|
||||||
secondNumber: '',
|
secondNumber: "",
|
||||||
thirdNumber: '',
|
thirdNumber: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleChange = (key: keyof PathState, value: string) => {
|
const handleChange = (key: keyof PathState, value: string) => {
|
||||||
if (key === 'secondNumber' && value !== '' && !['0', '1'].includes(value)) {
|
if (key === "secondNumber" && value !== "" && !["0", "1"].includes(value)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setPath({
|
setPath({
|
||||||
...path,
|
...path,
|
||||||
[key]: value.replace(/[^0-9]/g, ''),
|
[key]: value.replace(/[^0-9]/g, ""),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -50,7 +51,7 @@ const HDPath = ({
|
|||||||
hideDialog();
|
hideDialog();
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating account:', error);
|
console.error("Error creating account:", error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsAccountCreating(false);
|
setIsAccountCreating(false);
|
||||||
}
|
}
|
||||||
@ -63,15 +64,15 @@ const HDPath = ({
|
|||||||
<TextInput
|
<TextInput
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
onChangeText={text => handleChange('firstNumber', text)}
|
onChangeText={(text) => handleChange("firstNumber", text)}
|
||||||
value={path.firstNumber}
|
value={path.firstNumber}
|
||||||
style={styles.HDtextInput}
|
style={styles.HDtextInput}
|
||||||
/>
|
/>
|
||||||
<Text style={styles.HDtext}>'/</Text>
|
<Text style={styles.HDtext}>{"'/"}</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
onChangeText={text => handleChange('secondNumber', text)}
|
onChangeText={(text) => handleChange("secondNumber", text)}
|
||||||
value={path.secondNumber}
|
value={path.secondNumber}
|
||||||
style={styles.HDtextInput}
|
style={styles.HDtextInput}
|
||||||
/>
|
/>
|
||||||
@ -79,19 +80,20 @@ const HDPath = ({
|
|||||||
<TextInput
|
<TextInput
|
||||||
keyboardType="numeric"
|
keyboardType="numeric"
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
onChangeText={text => handleChange('thirdNumber', text)}
|
onChangeText={(text) => handleChange("thirdNumber", text)}
|
||||||
value={path.thirdNumber}
|
value={path.thirdNumber}
|
||||||
style={styles.HDtextInput}
|
style={styles.HDtextInput}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.HDbuttonContainer}>
|
<View style={styles.HDbuttonContainer}>
|
||||||
<Button
|
<LoadingButton
|
||||||
mode="contained"
|
variant="contained"
|
||||||
onPress={createFromHDPathHandler}
|
onClick={createFromHDPathHandler}
|
||||||
loading={isAccountCreating}
|
loading={isAccountCreating}
|
||||||
disabled={isAccountCreating}>
|
disabled={isAccountCreating}
|
||||||
{isAccountCreating ? 'Adding' : 'Add Account'}
|
>
|
||||||
</Button>
|
{isAccountCreating ? "Adding" : "Add Account"}
|
||||||
|
</LoadingButton>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
113
src/components/Header.tsx
Normal file
113
src/components/Header.tsx
Normal 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
src/components/ImportWalletDialog.tsx
Normal file
84
src/components/ImportWalletDialog.tsx
Normal 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
src/components/Layout.tsx
Normal file
34
src/components/Layout.tsx
Normal 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
src/components/MnemonicDialog.tsx
Normal file
51
src/components/MnemonicDialog.tsx
Normal 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 };
|
@ -1,10 +1,12 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { List } from 'react-native-paper';
|
import { List } from "react-native-paper";
|
||||||
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||||
|
|
||||||
import { NetworkDropdownProps, NetworksDataState } from '../types';
|
import { NetworkDropdownProps, NetworksDataState } from "../types";
|
||||||
import styles from '../styles/stylesheet';
|
import styles from "../styles/stylesheet";
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import { useNetworks } from "../context/NetworksContext";
|
||||||
|
import { Accordion, AccordionSummary } from "@mui/material";
|
||||||
|
|
||||||
const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
|
const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
|
||||||
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
|
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
|
||||||
@ -19,18 +21,19 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.networkDropdown}>
|
<View style={styles.networkDropdown}>
|
||||||
<List.Accordion
|
<Accordion expanded={expanded} onClick={() => setExpanded(!expanded)}>
|
||||||
title={selectedNetwork!.networkName}
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
expanded={expanded}
|
{selectedNetwork!.networkName}
|
||||||
onPress={() => setExpanded(!expanded)}>
|
</AccordionSummary>
|
||||||
{networksData.map(networkData => (
|
|
||||||
|
{networksData.map((networkData) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
key={networkData.networkId}
|
key={networkData.networkId}
|
||||||
title={networkData.networkName}
|
title={networkData.networkName}
|
||||||
onPress={() => handleNetworkPress(networkData)}
|
onPress={() => handleNetworkPress(networkData)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</List.Accordion>
|
</Accordion>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { TouchableOpacity, View } from 'react-native';
|
import { TouchableOpacity, View } from "react-native";
|
||||||
import { Button, Typography } from '@mui/material';
|
import { Button, Link, Typography } from "@mui/material";
|
||||||
import Dialog from '@mui/material/Dialog';
|
import Dialog from "@mui/material/Dialog";
|
||||||
import DialogTitle from '@mui/material/DialogTitle';
|
import DialogTitle from "@mui/material/DialogTitle";
|
||||||
import DialogContent from '@mui/material/DialogContent';
|
import DialogContent from "@mui/material/DialogContent";
|
||||||
import DialogActions from '@mui/material/DialogActions';
|
import DialogActions from "@mui/material/DialogActions";
|
||||||
|
|
||||||
import styles from '../styles/stylesheet';
|
import styles from "../styles/stylesheet";
|
||||||
import { getPathKey } from '../utils/misc';
|
import { getPathKey } from "../utils/misc";
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import { useNetworks } from "../context/NetworksContext";
|
||||||
import { useAccounts } from '../context/AccountsContext';
|
import { useAccounts } from "../context/AccountsContext";
|
||||||
import { Text, useTheme } from 'react-native-paper';
|
|
||||||
|
|
||||||
const ShowPKDialog = () => {
|
const ShowPKDialog = () => {
|
||||||
const { currentIndex } = useAccounts();
|
const { currentIndex } = useAccounts();
|
||||||
@ -19,8 +18,6 @@ const ShowPKDialog = () => {
|
|||||||
const [privateKey, setPrivateKey] = useState<string>();
|
const [privateKey, setPrivateKey] = useState<string>();
|
||||||
const [showPKDialog, setShowPKDialog] = useState<boolean>(false);
|
const [showPKDialog, setShowPKDialog] = useState<boolean>(false);
|
||||||
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const handleShowPrivateKey = async () => {
|
const handleShowPrivateKey = async () => {
|
||||||
const pathKey = await getPathKey(
|
const pathKey = await getPathKey(
|
||||||
`${selectedNetwork!.namespace}:${selectedNetwork!.chainId}`,
|
`${selectedNetwork!.namespace}:${selectedNetwork!.chainId}`,
|
||||||
@ -41,12 +38,9 @@ const ShowPKDialog = () => {
|
|||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
setShowPKDialog(true);
|
setShowPKDialog(true);
|
||||||
}}>
|
}}
|
||||||
<Text
|
>
|
||||||
variant="titleSmall"
|
<Link>Show Private Key</Link>
|
||||||
style={[styles.hyperlink, { color: theme.colors.primary }]}>
|
|
||||||
Show Private Key
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
<View>
|
<View>
|
||||||
@ -66,8 +60,8 @@ const ShowPKDialog = () => {
|
|||||||
variant="body1"
|
variant="body1"
|
||||||
style={styles.dataBoxData}
|
style={styles.dataBoxData}
|
||||||
sx={{
|
sx={{
|
||||||
wordWrap: 'break-word',
|
wordWrap: "break-word",
|
||||||
whiteSpace: 'initial',
|
whiteSpace: "initial"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{privateKey}
|
{privateKey}
|
||||||
@ -76,24 +70,19 @@ const ShowPKDialog = () => {
|
|||||||
)}
|
)}
|
||||||
<View>
|
<View>
|
||||||
<Typography variant="body1" style={styles.dialogWarning}>
|
<Typography variant="body1" style={styles.dialogWarning}>
|
||||||
<Typography component="span">
|
Warning: Never disclose this key. Anyone with your private keys can steal
|
||||||
Warning:
|
any assets held in your account.
|
||||||
</Typography>
|
|
||||||
Never disclose this key. Anyone with your private keys can
|
|
||||||
steal any assets held in your account.
|
|
||||||
</Typography>
|
</Typography>
|
||||||
</View>
|
</View>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
{!privateKey ? (
|
{!privateKey ? (
|
||||||
<>
|
<>
|
||||||
<Button onClick={handleShowPrivateKey} color="error">
|
<Button style={{...styles.buttonRed, ...styles.button}} onClick={handleShowPrivateKey}>Yes</Button>
|
||||||
Yes
|
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideShowPKDialog}>No</Button>
|
||||||
</Button>
|
|
||||||
<Button onClick={hideShowPKDialog}>No</Button>
|
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Button onClick={hideShowPKDialog}>Ok</Button>
|
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideShowPKDialog}>Ok</Button>
|
||||||
)}
|
)}
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
@ -4,15 +4,15 @@ import { Account } from '../types';
|
|||||||
|
|
||||||
const AccountsContext = createContext<{
|
const AccountsContext = createContext<{
|
||||||
accounts: Account[];
|
accounts: Account[];
|
||||||
setAccounts: (account: Account[]) => void;
|
setAccounts: React.Dispatch<React.SetStateAction<Account[]>>;
|
||||||
currentIndex: number;
|
currentIndex: number;
|
||||||
setCurrentIndex: (index: number) => void;
|
setCurrentIndex: (index: number) => void;
|
||||||
}>({
|
}>({
|
||||||
accounts: [],
|
accounts: [],
|
||||||
setAccounts: () => {},
|
setAccounts: () => {},
|
||||||
currentIndex: 0,
|
currentIndex: 0,
|
||||||
setCurrentIndex: () => {},
|
setCurrentIndex: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const useAccounts = () => {
|
const useAccounts = () => {
|
||||||
const accountsContext = useContext(AccountsContext);
|
const accountsContext = useContext(AccountsContext);
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { NetworksDataState } from '../types';
|
import { NetworksDataState } from '../types';
|
||||||
import { retrieveNetworksData, storeNetworkData } from '../utils/accounts';
|
import { retrieveNetworksData } from '../utils/accounts';
|
||||||
import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants';
|
import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants';
|
||||||
|
import { setInternetCredentials } from '../utils/key-store';
|
||||||
|
|
||||||
const NetworksContext = createContext<{
|
const NetworksContext = createContext<{
|
||||||
networksData: NetworksDataState[];
|
networksData: NetworksDataState[];
|
||||||
@ -13,42 +14,52 @@ const NetworksContext = createContext<{
|
|||||||
setSelectedNetwork: React.Dispatch<
|
setSelectedNetwork: React.Dispatch<
|
||||||
React.SetStateAction<NetworksDataState | undefined>
|
React.SetStateAction<NetworksDataState | undefined>
|
||||||
>;
|
>;
|
||||||
}>({
|
}>({
|
||||||
networksData: [],
|
networksData: [],
|
||||||
setNetworksData: () => {},
|
setNetworksData: () => {},
|
||||||
networkType: '',
|
networkType: '',
|
||||||
setNetworkType: () => {},
|
setNetworkType: () => {},
|
||||||
selectedNetwork: {} as NetworksDataState,
|
selectedNetwork: {} as NetworksDataState,
|
||||||
setSelectedNetwork: () => {},
|
setSelectedNetwork: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const useNetworks = () => {
|
const useNetworks = () => {
|
||||||
const networksContext = useContext(NetworksContext);
|
const networksContext = useContext(NetworksContext);
|
||||||
return networksContext;
|
return networksContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_NETWORKS_DATA = DEFAULT_NETWORKS.map((defaultNetwork, index) => (
|
||||||
|
{
|
||||||
|
...defaultNetwork,
|
||||||
|
networkId: index.toString()
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const NetworksProvider = ({ children }: { children: React.ReactNode }) => {
|
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 [networkType, setNetworkType] = useState<string>(EIP155);
|
||||||
const [selectedNetwork, setSelectedNetwork] = useState<NetworksDataState>();
|
const [selectedNetwork, setSelectedNetwork] = useState<NetworksDataState>();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const retrievedNetworks = await retrieveNetworksData();
|
let retrievedNetworks = await retrieveNetworksData();
|
||||||
|
|
||||||
if (retrievedNetworks.length === 0) {
|
if (retrievedNetworks.length === 0) {
|
||||||
for (const defaultNetwork of DEFAULT_NETWORKS) {
|
setInternetCredentials(
|
||||||
await storeNetworkData(defaultNetwork);
|
'networks',
|
||||||
}
|
'_',
|
||||||
|
JSON.stringify(DEFAULT_NETWORKS_DATA),
|
||||||
|
);
|
||||||
|
|
||||||
|
retrievedNetworks = DEFAULT_NETWORKS_DATA;
|
||||||
}
|
}
|
||||||
const retrievedNewNetworks = await retrieveNetworksData();
|
|
||||||
setNetworksData(retrievedNewNetworks);
|
setNetworksData(retrievedNetworks);
|
||||||
setSelectedNetwork(retrievedNewNetworks[0]);
|
setSelectedNetwork(retrievedNetworks[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (networksData.length === 0) {
|
fetchData();
|
||||||
fetchData();
|
}, []);
|
||||||
}
|
|
||||||
}, [networksData]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setSelectedNetwork(prevSelectedNetwork => {
|
setSelectedNetwork(prevSelectedNetwork => {
|
||||||
|
24
src/global.d.ts
vendored
Normal file
24
src/global.d.ts
vendored
Normal 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
src/hooks/useAccountsData.ts
Normal file
23
src/hooks/useAccountsData.ts
Normal 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;
|
59
src/hooks/useAddAccountEmbed.ts
Normal file
59
src/hooks/useAddAccountEmbed.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
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';
|
||||||
|
import { ADD_ACCOUNT_RESPONSE, REQUEST_ADD_ACCOUNT } from '../utils/constants';
|
||||||
|
|
||||||
|
const REACT_APP_ALLOWED_URLS = import.meta.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.chainId);
|
||||||
|
if (newAccount) {
|
||||||
|
setAccounts(prev => [...prev, newAccount]);
|
||||||
|
setCurrentIndex(newAccount.index);
|
||||||
|
}
|
||||||
|
}, [setAccounts, setCurrentIndex]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleAddAccount = async (event: MessageEvent) => {
|
||||||
|
if (event.data.type !== REQUEST_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;
|
99
src/hooks/useCreateNetwork.ts
Normal file
99
src/hooks/useCreateNetwork.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { useEffect, useCallback } from "react";
|
||||||
|
|
||||||
|
import { addNewNetwork, createWallet, checkNetworkForChainID, isWalletCreated } from "../utils/accounts";
|
||||||
|
import { useNetworks } from "../context/NetworksContext";
|
||||||
|
import { NETWORK_ADDED_RESPONSE, NETWORK_ADD_FAILED_RESPONSE, NETWORK_ALREADY_EXISTS_RESPONSE, REQUEST_ADD_NETWORK } from "../utils/constants";
|
||||||
|
import { NetworksFormData } from "../types";
|
||||||
|
import { sendMessage } from "../utils/misc";
|
||||||
|
|
||||||
|
const REACT_APP_ALLOWED_URLS = import.meta.env.REACT_APP_ALLOWED_URLS;
|
||||||
|
|
||||||
|
const useCreateNetwork = () => {
|
||||||
|
const { networksData, setNetworksData } = useNetworks();
|
||||||
|
|
||||||
|
const getOrCreateNetwork = useCallback(
|
||||||
|
async (chainId: string, networkData: NetworksFormData, sourceOrigin?: string) => {
|
||||||
|
if (!sourceOrigin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const isCreated = await isWalletCreated();
|
||||||
|
|
||||||
|
if (!isCreated) {
|
||||||
|
console.log("Wallet not found, creating wallet...");
|
||||||
|
await createWallet(networksData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isNetworkPresent = await checkNetworkForChainID(chainId);
|
||||||
|
|
||||||
|
if (!isNetworkPresent) {
|
||||||
|
console.log("ChainId not found. Adding network");
|
||||||
|
|
||||||
|
const resolvedNetworkData = {
|
||||||
|
chainId,
|
||||||
|
namespace: networkData.namespace,
|
||||||
|
networkName: networkData.networkName,
|
||||||
|
rpcUrl: networkData.rpcUrl,
|
||||||
|
blockExplorerUrl: networkData.blockExplorerUrl || "",
|
||||||
|
addressPrefix: networkData.addressPrefix || "",
|
||||||
|
coinType: networkData.coinType,
|
||||||
|
nativeDenom: networkData.nativeDenom || "",
|
||||||
|
gasPrice: networkData.gasPrice || String(import.meta.env.REACT_APP_DEFAULT_GAS_PRICE),
|
||||||
|
currencySymbol: networkData.currencySymbol || "",
|
||||||
|
isDefault: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const retrievedNetworksData = await addNewNetwork(resolvedNetworkData);
|
||||||
|
setNetworksData(retrievedNetworksData);
|
||||||
|
|
||||||
|
sendMessage(window.parent, NETWORK_ADDED_RESPONSE, {
|
||||||
|
type: NETWORK_ADDED_RESPONSE,
|
||||||
|
chainId
|
||||||
|
}, sourceOrigin);
|
||||||
|
} else {
|
||||||
|
sendMessage(window.parent, NETWORK_ALREADY_EXISTS_RESPONSE, {
|
||||||
|
type: NETWORK_ALREADY_EXISTS_RESPONSE,
|
||||||
|
chainId
|
||||||
|
}, sourceOrigin);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in getOrCreateNetwork:", error);
|
||||||
|
sendMessage(window.parent, NETWORK_ADD_FAILED_RESPONSE, {
|
||||||
|
type: NETWORK_ADD_FAILED_RESPONSE,
|
||||||
|
message: error instanceof Error ? error.message : "Unknown error"
|
||||||
|
}, sourceOrigin);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[networksData, setNetworksData]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleCreateNetwork = async (event: MessageEvent) => {
|
||||||
|
if (event.data.type !== REQUEST_ADD_NETWORK) 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 { chainId, networkData } = event.data;
|
||||||
|
|
||||||
|
await getOrCreateNetwork(chainId, networkData, event.origin);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("message", handleCreateNetwork);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("message", handleCreateNetwork);
|
||||||
|
};
|
||||||
|
}, [getOrCreateNetwork]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCreateNetwork;
|
43
src/hooks/useExportPrivateKeyEmbed.ts
Normal file
43
src/hooks/useExportPrivateKeyEmbed.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { useAccounts } from '../context/AccountsContext';
|
||||||
|
import { getPathKey, sendMessage } from '../utils/misc';
|
||||||
|
import { ACCOUNT_PK_RESPONSE, REQUEST_ACCOUNT_PK } from '../utils/constants';
|
||||||
|
|
||||||
|
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_RESPONSE,
|
||||||
|
{ 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;
|
92
src/hooks/useGetOrCreateAccounts.ts
Normal file
92
src/hooks/useGetOrCreateAccounts.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
import { useEffect, useCallback } from "react";
|
||||||
|
|
||||||
|
import { createWallet, isWalletCreated } from "../utils/accounts";
|
||||||
|
import { sendMessage } from "../utils/misc";
|
||||||
|
import useAccountsData from "./useAccountsData";
|
||||||
|
import { useNetworks } from "../context/NetworksContext";
|
||||||
|
import { useAccounts } from "../context/AccountsContext";
|
||||||
|
import { REQUEST_CREATE_OR_GET_ACCOUNTS, WALLET_ACCOUNTS_DATA } from "../utils/constants";
|
||||||
|
|
||||||
|
const REACT_APP_ALLOWED_URLS = import.meta.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) => {
|
||||||
|
const isCreated = await isWalletCreated();
|
||||||
|
|
||||||
|
if (!isCreated) {
|
||||||
|
console.log("Accounts not found, creating wallet...");
|
||||||
|
await createWallet(networksData);
|
||||||
|
}
|
||||||
|
|
||||||
|
const 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
src/hooks/useWebViewHandler.ts
Normal file
81
src/hooks/useWebViewHandler.ts
Normal 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
|
||||||
|
};
|
13
src/import-meta-env.d.ts
vendored
Normal file
13
src/import-meta-env.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Generated by '@import-meta-env/typescript'
|
||||||
|
|
||||||
|
interface ImportMetaEnv {
|
||||||
|
readonly REACT_APP_WALLET_CONNECT_PROJECT_ID: string;
|
||||||
|
readonly REACT_APP_DEFAULT_GAS_PRICE: string;
|
||||||
|
readonly REACT_APP_GAS_ADJUSTMENT: string;
|
||||||
|
readonly REACT_APP_LACONICD_RPC_URL: string;
|
||||||
|
readonly REACT_APP_ALLOWED_URLS: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ImportMeta {
|
||||||
|
readonly env: ImportMetaEnv;
|
||||||
|
}
|
158
src/index.tsx
158
src/index.tsx
@ -1,59 +1,141 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactDOM from 'react-dom/client';
|
import ReactDOM from "react-dom/client";
|
||||||
import { PaperProvider, MD3LightTheme as DefaultTheme, } from 'react-native-paper';
|
import {
|
||||||
import { NavigationContainer } from '@react-navigation/native';
|
PaperProvider,
|
||||||
import { Platform } from 'react-native';
|
MD3DarkTheme as DefaultTheme,
|
||||||
import { Buffer } from 'buffer';
|
} from "react-native-paper";
|
||||||
|
import { NavigationContainer, DarkTheme } from "@react-navigation/native";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
import { Buffer } from "buffer";
|
||||||
|
|
||||||
import './index.css';
|
import "./index.css";
|
||||||
import App from './App';
|
import App from "./App";
|
||||||
import { AccountsProvider } from './context/AccountsContext';
|
import { AccountsProvider } from "./context/AccountsContext";
|
||||||
import { NetworksProvider } from './context/NetworksContext';
|
import { NetworksProvider } from "./context/NetworksContext";
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from "./reportWebVitals";
|
||||||
import { WalletConnectProvider } from './context/WalletConnectContext';
|
import { WalletConnectProvider } from "./context/WalletConnectContext";
|
||||||
|
import { createTheme, ThemeProvider } from "@mui/material";
|
||||||
|
|
||||||
globalThis.Buffer = Buffer;
|
globalThis.Buffer = Buffer;
|
||||||
|
|
||||||
const linking = {
|
const linking = {
|
||||||
prefixes: ['https://wallet.laconic.com']
|
prefixes: ["https://wallet.laconic.com"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
...DefaultTheme,
|
...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(
|
const root = ReactDOM.createRoot(
|
||||||
document.getElementById('root') as HTMLElement
|
document.getElementById("root") as HTMLElement,
|
||||||
);
|
);
|
||||||
root.render(
|
root.render(
|
||||||
<PaperProvider theme={theme}>
|
<div id="app">
|
||||||
<NetworksProvider>
|
<PaperProvider theme={theme}>
|
||||||
<AccountsProvider>
|
<NetworksProvider>
|
||||||
<WalletConnectProvider>
|
<AccountsProvider>
|
||||||
<NavigationContainer
|
<WalletConnectProvider>
|
||||||
linking={linking}
|
<NavigationContainer
|
||||||
documentTitle={{
|
linking={linking}
|
||||||
formatter: () =>
|
documentTitle={{
|
||||||
`Laconic Wallet`,
|
formatter: () => `Laconic | Wallet`,
|
||||||
}}
|
}}
|
||||||
>
|
theme={navigationTheme}
|
||||||
<React.Fragment>
|
>
|
||||||
{Platform.OS === 'web' ? (
|
<React.Fragment>
|
||||||
<style type="text/css">{`
|
{Platform.OS === "web" ? (
|
||||||
|
<style type="text/css">{`
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'MaterialCommunityIcons';
|
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>
|
`}</style>
|
||||||
) : null}
|
) : null}
|
||||||
<App />
|
<ThemeProvider theme={muiTheme}>
|
||||||
</React.Fragment>
|
<App />
|
||||||
</NavigationContainer>
|
</ThemeProvider>
|
||||||
</WalletConnectProvider>
|
</React.Fragment>
|
||||||
</AccountsProvider>
|
</NavigationContainer>
|
||||||
</NetworksProvider>
|
</WalletConnectProvider>
|
||||||
</PaperProvider>
|
</AccountsProvider>
|
||||||
|
</NetworksProvider>
|
||||||
|
</PaperProvider>
|
||||||
|
</div>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
@ -1,36 +1,29 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { useForm, Controller, useWatch, FieldErrors } from "react-hook-form";
|
||||||
import { useForm, Controller, useWatch, FieldErrors } from 'react-hook-form';
|
import { TextInput, HelperText } from "react-native-paper";
|
||||||
import { TextInput, Button, HelperText } from 'react-native-paper';
|
import { chains } from "chain-registry";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
import { HDNode } from 'ethers/lib/utils';
|
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||||
import { chains } from 'chain-registry';
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { z } from 'zod';
|
import { Divider, Grid } from "@mui/material";
|
||||||
|
import { LoadingButton } from "@mui/lab";
|
||||||
|
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { StackParamsList } from "../types";
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { SelectNetworkType } from "../components/SelectNetworkType";
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { addNewNetwork } 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 {
|
import {
|
||||||
COSMOS,
|
|
||||||
EIP155,
|
EIP155,
|
||||||
CHAINID_DEBOUNCE_DELAY,
|
CHAINID_DEBOUNCE_DELAY,
|
||||||
EMPTY_FIELD_ERROR,
|
EMPTY_FIELD_ERROR,
|
||||||
INVALID_URL_ERROR,
|
INVALID_URL_ERROR,
|
||||||
IS_NUMBER_REGEX,
|
IS_NUMBER_REGEX,
|
||||||
} from '../utils/constants';
|
} from "../utils/constants";
|
||||||
import { getCosmosAccounts } from '../utils/accounts';
|
import ETH_CHAINS from "../assets/ethereum-chains.json";
|
||||||
import ETH_CHAINS from '../assets/ethereum-chains.json';
|
import { Layout } from "../components/Layout";
|
||||||
import {
|
|
||||||
getInternetCredentials,
|
|
||||||
setInternetCredentials,
|
|
||||||
} from '../utils/key-store';
|
|
||||||
import styles from '../styles/stylesheet';
|
|
||||||
|
|
||||||
const ethNetworkDataSchema = z.object({
|
const ethNetworkDataSchema = z.object({
|
||||||
chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
||||||
@ -39,7 +32,7 @@ const ethNetworkDataSchema = z.object({
|
|||||||
blockExplorerUrl: z
|
blockExplorerUrl: z
|
||||||
.string()
|
.string()
|
||||||
.url({ message: INVALID_URL_ERROR })
|
.url({ message: INVALID_URL_ERROR })
|
||||||
.or(z.literal('')),
|
.or(z.literal("")),
|
||||||
coinType: z
|
coinType: z
|
||||||
.string()
|
.string()
|
||||||
.nonempty({ message: EMPTY_FIELD_ERROR })
|
.nonempty({ message: EMPTY_FIELD_ERROR })
|
||||||
@ -54,7 +47,7 @@ const cosmosNetworkDataSchema = z.object({
|
|||||||
blockExplorerUrl: z
|
blockExplorerUrl: z
|
||||||
.string()
|
.string()
|
||||||
.url({ message: INVALID_URL_ERROR })
|
.url({ message: INVALID_URL_ERROR })
|
||||||
.or(z.literal('')),
|
.or(z.literal("")),
|
||||||
coinType: z
|
coinType: z
|
||||||
.string()
|
.string()
|
||||||
.nonempty({ message: EMPTY_FIELD_ERROR })
|
.nonempty({ message: EMPTY_FIELD_ERROR })
|
||||||
@ -85,13 +78,13 @@ const AddNetwork = () => {
|
|||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
} = useForm<z.infer<typeof networksFormDataSchema>>({
|
} = useForm<z.infer<typeof networksFormDataSchema>>({
|
||||||
mode: 'onChange',
|
mode: "onChange",
|
||||||
resolver: zodResolver(networksFormDataSchema),
|
resolver: zodResolver(networksFormDataSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const watchChainId = useWatch({
|
const watchChainId = useWatch({
|
||||||
control,
|
control,
|
||||||
name: 'chainId',
|
name: "chainId",
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateNetworkType = (newNetworkType: string) => {
|
const updateNetworkType = (newNetworkType: string) => {
|
||||||
@ -101,16 +94,16 @@ const AddNetwork = () => {
|
|||||||
const fetchChainDetails = useDebouncedCallback((chainId: string) => {
|
const fetchChainDetails = useDebouncedCallback((chainId: string) => {
|
||||||
if (namespace === EIP155) {
|
if (namespace === EIP155) {
|
||||||
const ethChainDetails = ETH_CHAINS.find(
|
const ethChainDetails = ETH_CHAINS.find(
|
||||||
chain => chain.chainId === Number(chainId),
|
(chain) => chain.chainId === Number(chainId),
|
||||||
);
|
);
|
||||||
if (!ethChainDetails) {
|
if (!ethChainDetails) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setValue('networkName', ethChainDetails.name);
|
setValue("networkName", ethChainDetails.name);
|
||||||
setValue('rpcUrl', ethChainDetails.rpc[0]);
|
setValue("rpcUrl", ethChainDetails.rpc[0]);
|
||||||
setValue('blockExplorerUrl', ethChainDetails.explorers?.[0].url || '');
|
setValue("blockExplorerUrl", ethChainDetails.explorers?.[0].url || "");
|
||||||
setValue('coinType', String(ethChainDetails.slip44 ?? '60'));
|
setValue("coinType", String(ethChainDetails.slip44 ?? "60"));
|
||||||
setValue('currencySymbol', ethChainDetails.nativeCurrency.symbol);
|
setValue("currencySymbol", ethChainDetails.nativeCurrency.symbol);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cosmosChainDetails = chains.find(
|
const cosmosChainDetails = chains.find(
|
||||||
@ -119,17 +112,17 @@ const AddNetwork = () => {
|
|||||||
if (!cosmosChainDetails) {
|
if (!cosmosChainDetails) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setValue('networkName', cosmosChainDetails.pretty_name);
|
setValue("networkName", cosmosChainDetails.pretty_name);
|
||||||
setValue('rpcUrl', cosmosChainDetails.apis?.rpc?.[0]?.address || '');
|
setValue("rpcUrl", cosmosChainDetails.apis?.rpc?.[0]?.address || "");
|
||||||
setValue('blockExplorerUrl', cosmosChainDetails.explorers?.[0].url || '');
|
setValue("blockExplorerUrl", cosmosChainDetails.explorers?.[0].url || "");
|
||||||
setValue('addressPrefix', cosmosChainDetails.bech32_prefix);
|
setValue("addressPrefix", cosmosChainDetails.bech32_prefix);
|
||||||
setValue('coinType', String(cosmosChainDetails.slip44 ?? '118'));
|
setValue("coinType", String(cosmosChainDetails.slip44 ?? "118"));
|
||||||
setValue('nativeDenom', cosmosChainDetails.fees?.fee_tokens[0].denom || '');
|
setValue("nativeDenom", cosmosChainDetails.fees?.fee_tokens[0].denom || "");
|
||||||
setValue(
|
setValue(
|
||||||
'gasPrice',
|
"gasPrice",
|
||||||
String(
|
String(
|
||||||
cosmosChainDetails.fees?.fee_tokens[0].average_gas_price ||
|
cosmosChainDetails.fees?.fee_tokens[0].average_gas_price ||
|
||||||
String(process.env.DEFAULT_GAS_PRICE),
|
String(import.meta.env.REACT_APP_DEFAULT_GAS_PRICE),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}, CHAINID_DEBOUNCE_DELAY);
|
}, CHAINID_DEBOUNCE_DELAY);
|
||||||
@ -142,63 +135,11 @@ const AddNetwork = () => {
|
|||||||
isDefault: false,
|
isDefault: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const mnemonicServer = await getInternetCredentials('mnemonicServer');
|
const retrievedNetworksData = await addNewNetwork(newNetworkData);
|
||||||
const mnemonic = mnemonicServer;
|
|
||||||
|
|
||||||
if (!mnemonic) {
|
|
||||||
throw new Error('Mnemonic not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
|
||||||
|
|
||||||
const hdPath = `m/44'/${newNetworkData.coinType}'/0'/0/0`;
|
|
||||||
const node = hdNode.derivePath(hdPath);
|
|
||||||
let address;
|
|
||||||
|
|
||||||
switch (newNetworkData.namespace) {
|
|
||||||
case EIP155:
|
|
||||||
address = node.address;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case COSMOS:
|
|
||||||
address = (
|
|
||||||
await getCosmosAccounts(
|
|
||||||
mnemonic,
|
|
||||||
hdPath,
|
|
||||||
(newNetworkData as z.infer<typeof cosmosNetworkDataSchema>)
|
|
||||||
.addressPrefix,
|
|
||||||
)
|
|
||||||
).data.address;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error('Unsupported namespace');
|
|
||||||
}
|
|
||||||
|
|
||||||
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
|
|
||||||
|
|
||||||
const retrievedNetworksData = await storeNetworkData(newNetworkData);
|
|
||||||
setNetworksData(retrievedNetworksData);
|
setNetworksData(retrievedNetworksData);
|
||||||
|
|
||||||
await Promise.all([
|
navigation.navigate("Home");
|
||||||
setInternetCredentials(
|
|
||||||
`accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`,
|
|
||||||
'_',
|
|
||||||
accountInfo,
|
|
||||||
),
|
|
||||||
setInternetCredentials(
|
|
||||||
`addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`,
|
|
||||||
'_',
|
|
||||||
'1',
|
|
||||||
),
|
|
||||||
setInternetCredentials(
|
|
||||||
`accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`,
|
|
||||||
'_',
|
|
||||||
'0',
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
navigation.navigate('Home');
|
|
||||||
},
|
},
|
||||||
[navigation, namespace, setNetworksData],
|
[navigation, namespace, setNetworksData],
|
||||||
);
|
);
|
||||||
@ -212,208 +153,237 @@ const AddNetwork = () => {
|
|||||||
}, [namespace, reset]);
|
}, [namespace, reset]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.appContainer}>
|
<Layout title="Add Network">
|
||||||
<SelectNetworkType updateNetworkType={updateNetworkType} />
|
<SelectNetworkType updateNetworkType={updateNetworkType} />
|
||||||
|
<Divider flexItem sx={{ my: 4 }} />
|
||||||
|
|
||||||
<Controller
|
<Grid container spacing={2} sx={{ px: 1 }}>
|
||||||
control={control}
|
<Grid item xs={6}>
|
||||||
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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="nativeDenom"
|
name="chainId"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field: { onChange, onBlur, value } }) => (
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
<>
|
<>
|
||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
value={value}
|
value={value}
|
||||||
label="Native Denom"
|
label="Chain ID"
|
||||||
onBlur={onBlur}
|
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">
|
<HelperText type="error">
|
||||||
{
|
{errors.networkName?.message}
|
||||||
(
|
|
||||||
errors as FieldErrors<
|
|
||||||
z.infer<typeof cosmosNetworkDataSchema>
|
|
||||||
>
|
|
||||||
).nativeDenom?.message
|
|
||||||
}
|
|
||||||
</HelperText>
|
</HelperText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid item xs={6}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="addressPrefix"
|
name="rpcUrl"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field: { onChange, onBlur, value } }) => (
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
<>
|
<>
|
||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
value={value}
|
label="New RPC URL"
|
||||||
label="Address Prefix"
|
|
||||||
onBlur={onBlur}
|
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">
|
<HelperText type="error">
|
||||||
{
|
{errors.blockExplorerUrl?.message}
|
||||||
(
|
|
||||||
errors as FieldErrors<
|
|
||||||
z.infer<typeof cosmosNetworkDataSchema>
|
|
||||||
>
|
|
||||||
).addressPrefix?.message
|
|
||||||
}
|
|
||||||
</HelperText>
|
</HelperText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={namespace === EIP155 ? 12 : 6}>
|
||||||
<Controller
|
<Controller
|
||||||
control={control}
|
control={control}
|
||||||
name="gasPrice"
|
name="coinType"
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
render={({ field: { onChange, onBlur, value } }) => (
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
<>
|
<>
|
||||||
<TextInput
|
<TextInput
|
||||||
mode="outlined"
|
mode="outlined"
|
||||||
value={value}
|
value={value}
|
||||||
label="Gas Price"
|
label="Coin Type"
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onChangeText={onChange}
|
onChangeText={onChange}
|
||||||
/>
|
/>
|
||||||
<HelperText type="error">
|
<HelperText type="error">{errors.coinType?.message}</HelperText>
|
||||||
{
|
|
||||||
(
|
|
||||||
errors as FieldErrors<
|
|
||||||
z.infer<typeof cosmosNetworkDataSchema>
|
|
||||||
>
|
|
||||||
).gasPrice?.message
|
|
||||||
}
|
|
||||||
</HelperText>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</Grid>
|
||||||
)}
|
{namespace === EIP155 ? (
|
||||||
<Button
|
<Grid item xs={12}>
|
||||||
mode="contained"
|
<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}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
style={styles.networksButton}
|
onClick={handleSubmit(submit)}
|
||||||
onPress={handleSubmit(submit)}>
|
sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }}
|
||||||
{isSubmitting ? 'Adding' : 'Submit'}
|
>
|
||||||
</Button>
|
{isSubmitting ? "Adding" : "Submit"}
|
||||||
</View>
|
</LoadingButton>
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { View } from "react-native";
|
||||||
import { Button, Text, TextInput } from 'react-native-paper';
|
import { Text, TextInput } from "react-native-paper";
|
||||||
|
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||||
|
import { Box, Button } from "@mui/material";
|
||||||
|
|
||||||
import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils';
|
import { web3WalletPair } from "../utils/wallet-connect/WalletConnectUtils";
|
||||||
import styles from '../styles/stylesheet';
|
import styles from "../styles/stylesheet";
|
||||||
import { StackParamsList } from '../types';
|
import { StackParamsList } from "../types";
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
import { useWalletConnect } from "../context/WalletConnectContext";
|
||||||
|
import { Layout } from "../components/Layout";
|
||||||
|
|
||||||
const AddSession = () => {
|
const AddSession = () => {
|
||||||
const navigation =
|
const navigation =
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
const [currentWCURI, setCurrentWCURI] = useState<string>('');
|
const [currentWCURI, setCurrentWCURI] = useState<string>("");
|
||||||
|
|
||||||
const {web3wallet} = useWalletConnect();
|
const { web3wallet } = useWalletConnect();
|
||||||
|
|
||||||
const pair = useCallback(async () => {
|
const pair = useCallback(async () => {
|
||||||
if (!web3wallet) {
|
if (!web3wallet) {
|
||||||
@ -24,12 +26,12 @@ const AddSession = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const pairing = await web3WalletPair(web3wallet, { uri: currentWCURI });
|
const pairing = await web3WalletPair(web3wallet, { uri: currentWCURI });
|
||||||
navigation.navigate('WalletConnect');
|
navigation.navigate("WalletConnect");
|
||||||
return pairing;
|
return pairing;
|
||||||
}, [currentWCURI, navigation, web3wallet]);
|
}, [currentWCURI, navigation, web3wallet]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.appContainer}>
|
<Layout title="Add Session">
|
||||||
<View style={styles.inputContainer}>
|
<View style={styles.inputContainer}>
|
||||||
<Text variant="titleMedium">Enter WalletConnect URI</Text>
|
<Text variant="titleMedium">Enter WalletConnect URI</Text>
|
||||||
<TextInput
|
<TextInput
|
||||||
@ -41,13 +43,13 @@ const AddSession = () => {
|
|||||||
style={styles.walletConnectUriText}
|
style={styles.walletConnectUriText}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<View style={styles.signButton}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Button mode="contained" onPress={pair}>
|
<Button variant="contained" onClick={pair}>
|
||||||
Pair Session
|
Pair Session
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</Box>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export default AddSession;
|
export default AddSession;
|
||||||
|
@ -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 { Image, ScrollView, View } from 'react-native';
|
||||||
import { Button, Text, TextInput } from 'react-native-paper';
|
import { Button, Text, TextInput } from 'react-native-paper';
|
||||||
|
import { MsgCreateValidator } from 'cosmjs-types/cosmos/staking/v1beta1/tx';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
NativeStackNavigationProp,
|
NativeStackNavigationProp,
|
||||||
NativeStackScreenProps,
|
NativeStackScreenProps,
|
||||||
} from '@react-navigation/native-stack';
|
} from '@react-navigation/native-stack';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
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 { LaconicClient } from '@cerc-io/registry-sdk';
|
||||||
import { GasPrice, calculateFee } from '@cosmjs/stargate';
|
import { GasPrice, calculateFee } from '@cosmjs/stargate';
|
||||||
import { formatJsonRpcError } from '@json-rpc-tools/utils';
|
import { formatJsonRpcError } from '@json-rpc-tools/utils';
|
||||||
@ -40,7 +41,6 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
const requestName = requestSession.peer.metadata.name;
|
const requestName = requestSession.peer.metadata.name;
|
||||||
const requestIcon = requestSession.peer.metadata.icons[0];
|
const requestIcon = requestSession.peer.metadata.icons[0];
|
||||||
const requestURL = requestSession.peer.metadata.url;
|
const requestURL = requestSession.peer.metadata.url;
|
||||||
const transactionMessage = route.params.transactionMessage;
|
|
||||||
const signer = route.params.signer;
|
const signer = route.params.signer;
|
||||||
const requestEvent = route.params.requestEvent;
|
const requestEvent = route.params.requestEvent;
|
||||||
const chainId = requestEvent.params.chainId;
|
const chainId = requestEvent.params.chainId;
|
||||||
@ -67,6 +67,20 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
|
|
||||||
const { web3wallet } = useWalletConnect();
|
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(() => {
|
useEffect(() => {
|
||||||
if (namespace !== COSMOS) {
|
if (namespace !== COSMOS) {
|
||||||
return;
|
return;
|
||||||
@ -138,14 +152,14 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const gasEstimation = await cosmosStargateClient!.simulate(
|
const gasEstimation = await cosmosStargateClient!.simulate(
|
||||||
transactionMessage.value.participant!,
|
signer,
|
||||||
[transactionMessage],
|
[transactionMessage],
|
||||||
MEMO,
|
MEMO,
|
||||||
);
|
);
|
||||||
|
|
||||||
setCosmosGasLimit(
|
setCosmosGasLimit(
|
||||||
String(
|
String(
|
||||||
Math.round(gasEstimation * Number(process.env.REACT_APP_GAS_ADJUSTMENT)),
|
Math.round(gasEstimation * Number(import.meta.env.REACT_APP_GAS_ADJUSTMENT)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -160,7 +174,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
getCosmosGas();
|
getCosmosGas();
|
||||||
}, [cosmosStargateClient, transactionMessage, requestEventId, topic, web3wallet]);
|
}, [cosmosStargateClient, transactionMessage, requestEventId, topic, web3wallet, signer]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const gasPrice = GasPrice.fromString(
|
const gasPrice = GasPrice.fromString(
|
||||||
@ -239,6 +253,13 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
navigation.navigate('Home');
|
navigation.navigate('Home');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const replacer = (key: string, value: any): any => {
|
||||||
|
if (value instanceof Uint8Array) {
|
||||||
|
return Buffer.from(value).toString('hex');
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ScrollView contentContainerStyle={styles.approveTransaction}>
|
<ScrollView contentContainerStyle={styles.approveTransaction}>
|
||||||
@ -264,7 +285,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
|
|||||||
</Text>
|
</Text>
|
||||||
<View style={styles.messageBody}>
|
<View style={styles.messageBody}>
|
||||||
<Text variant="bodyLarge">
|
<Text variant="bodyLarge">
|
||||||
{JSON.stringify(transactionMessage, null, 2)}
|
{JSON.stringify(transactionMessage, replacer, 2)}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<>
|
<>
|
||||||
|
@ -477,7 +477,7 @@ const ApproveTransfer = ({ route }: SignRequestProps) => {
|
|||||||
|
|
||||||
setCosmosGasLimit(
|
setCosmosGasLimit(
|
||||||
String(
|
String(
|
||||||
Math.round(gasEstimation * Number(process.env.REACT_APP_GAS_ADJUSTMENT)),
|
Math.round(gasEstimation * Number(import.meta.env.REACT_APP_GAS_ADJUSTMENT)),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
58
src/screens/AutoSignIn.tsx
Normal file
58
src/screens/AutoSignIn.tsx
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import React, { useEffect } from 'react';
|
||||||
|
|
||||||
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
|
import { signMessage } from '../utils/sign-message';
|
||||||
|
import { AUTO_SIGN_IN, EIP155, SIGN_IN_RESPONSE } from '../utils/constants';
|
||||||
|
import { sendMessage } from '../utils/misc';
|
||||||
|
import useAccountsData from '../hooks/useAccountsData';
|
||||||
|
import useGetOrCreateAccounts from '../hooks/useGetOrCreateAccounts';
|
||||||
|
|
||||||
|
const REACT_APP_ALLOWED_URLS = import.meta.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 (
|
||||||
|
<>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
};
|
@ -1,27 +1,28 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from "react";
|
||||||
import { View } from 'react-native';
|
import { useForm, Controller, FieldErrors } from "react-hook-form";
|
||||||
import { useForm, Controller, FieldErrors } from 'react-hook-form';
|
import { TextInput, HelperText } from "react-native-paper";
|
||||||
import { TextInput, Button, HelperText, Text } from 'react-native-paper';
|
import { z } from "zod";
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import {
|
import {
|
||||||
NativeStackNavigationProp,
|
NativeStackNavigationProp,
|
||||||
NativeStackScreenProps,
|
NativeStackScreenProps,
|
||||||
} from '@react-navigation/native-stack';
|
} from "@react-navigation/native-stack";
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from "@react-navigation/native";
|
||||||
|
|
||||||
import { setInternetCredentials } from '../utils/key-store';
|
import { setInternetCredentials } from "../utils/key-store";
|
||||||
import { StackParamsList } from '../types';
|
import { StackParamsList } from "../types";
|
||||||
import styles from '../styles/stylesheet';
|
import { retrieveNetworksData } from "../utils/accounts";
|
||||||
import { retrieveNetworksData } from '../utils/accounts';
|
import { useNetworks } from "../context/NetworksContext";
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
|
||||||
import {
|
import {
|
||||||
COSMOS,
|
COSMOS,
|
||||||
EIP155,
|
EIP155,
|
||||||
EMPTY_FIELD_ERROR,
|
EMPTY_FIELD_ERROR,
|
||||||
INVALID_URL_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({
|
const ethNetworksFormSchema = z.object({
|
||||||
// Adding type field for resolving typescript error
|
// Adding type field for resolving typescript error
|
||||||
@ -31,7 +32,7 @@ const ethNetworksFormSchema = z.object({
|
|||||||
blockExplorerUrl: z
|
blockExplorerUrl: z
|
||||||
.string()
|
.string()
|
||||||
.url({ message: INVALID_URL_ERROR })
|
.url({ message: INVALID_URL_ERROR })
|
||||||
.or(z.literal('')),
|
.or(z.literal("")),
|
||||||
});
|
});
|
||||||
|
|
||||||
const cosmosNetworksFormDataSchema = z.object({
|
const cosmosNetworksFormDataSchema = z.object({
|
||||||
@ -41,14 +42,14 @@ const cosmosNetworksFormDataSchema = z.object({
|
|||||||
blockExplorerUrl: z
|
blockExplorerUrl: z
|
||||||
.string()
|
.string()
|
||||||
.url({ message: INVALID_URL_ERROR })
|
.url({ message: INVALID_URL_ERROR })
|
||||||
.or(z.literal('')),
|
.or(z.literal("")),
|
||||||
gasPrice: z
|
gasPrice: z
|
||||||
.string()
|
.string()
|
||||||
.nonempty({ message: EMPTY_FIELD_ERROR })
|
.nonempty({ message: EMPTY_FIELD_ERROR })
|
||||||
.regex(/^\d+(\.\d+)?$/),
|
.regex(/^\d+(\.\d+)?$/),
|
||||||
});
|
});
|
||||||
|
|
||||||
type EditNetworkProps = NativeStackScreenProps<StackParamsList, 'EditNetwork'>;
|
type EditNetworkProps = NativeStackScreenProps<StackParamsList, "EditNetwork">;
|
||||||
|
|
||||||
const EditNetwork = ({ route }: EditNetworkProps) => {
|
const EditNetwork = ({ route }: EditNetworkProps) => {
|
||||||
const { setNetworksData } = useNetworks();
|
const { setNetworksData } = useNetworks();
|
||||||
@ -67,7 +68,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
|
|||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
} = useForm<z.infer<typeof networksFormDataSchema>>({
|
} = useForm<z.infer<typeof networksFormDataSchema>>({
|
||||||
mode: 'onChange',
|
mode: "onChange",
|
||||||
resolver: zodResolver(networksFormDataSchema),
|
resolver: zodResolver(networksFormDataSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -77,121 +78,134 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
|
|||||||
const { type, ...dataWithoutType } = data;
|
const { type, ...dataWithoutType } = data;
|
||||||
const newNetworkData = { ...networkData, ...dataWithoutType };
|
const newNetworkData = { ...networkData, ...dataWithoutType };
|
||||||
const index = retrievedNetworksData.findIndex(
|
const index = retrievedNetworksData.findIndex(
|
||||||
network => network.networkId === networkData.networkId,
|
(network) => network.networkId === networkData.networkId,
|
||||||
);
|
);
|
||||||
|
|
||||||
retrievedNetworksData.splice(index, 1, newNetworkData);
|
retrievedNetworksData.splice(index, 1, newNetworkData);
|
||||||
|
|
||||||
await setInternetCredentials(
|
await setInternetCredentials(
|
||||||
'networks',
|
"networks",
|
||||||
'_',
|
"_",
|
||||||
JSON.stringify(retrievedNetworksData),
|
JSON.stringify(retrievedNetworksData),
|
||||||
);
|
);
|
||||||
|
|
||||||
setNetworksData(retrievedNetworksData);
|
setNetworksData(retrievedNetworksData);
|
||||||
|
|
||||||
navigation.navigate('Home');
|
navigation.navigate("Home");
|
||||||
},
|
},
|
||||||
[networkData, navigation, setNetworksData],
|
[networkData, navigation, setNetworksData],
|
||||||
);
|
);
|
||||||
|
const isCosmos = networkData.namespace === COSMOS;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.appContainer}>
|
<Layout title="Edit Network">
|
||||||
<View>
|
<Typography fontSize="1.5rem" fontWeight={500}>
|
||||||
<Text style={styles.subHeading}>
|
Edit {networkData?.networkName} details
|
||||||
Edit {networkData?.networkName} details
|
</Typography>
|
||||||
</Text>
|
<Divider flexItem sx={{ my: 4 }} />
|
||||||
</View>
|
<Grid container spacing={2}>
|
||||||
<Controller
|
<Grid item xs={6}>
|
||||||
control={control}
|
<Controller
|
||||||
defaultValue={networkData.networkName}
|
control={control}
|
||||||
name="networkName"
|
defaultValue={networkData.networkName}
|
||||||
render={({ field: { onChange, onBlur, value } }) => (
|
name="networkName"
|
||||||
<>
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
<TextInput
|
<>
|
||||||
mode="outlined"
|
<TextInput
|
||||||
label="Network Name"
|
mode="outlined"
|
||||||
value={value}
|
label="Network Name"
|
||||||
onBlur={onBlur}
|
value={value}
|
||||||
onChangeText={textValue => onChange(textValue)}
|
onBlur={onBlur}
|
||||||
/>
|
onChangeText={(textValue) => onChange(textValue)}
|
||||||
<HelperText type="error">{errors.networkName?.message}</HelperText>
|
/>
|
||||||
</>
|
<HelperText type="error">
|
||||||
)}
|
{errors.networkName?.message}
|
||||||
/>
|
</HelperText>
|
||||||
<Controller
|
</>
|
||||||
control={control}
|
)}
|
||||||
name="rpcUrl"
|
/>
|
||||||
defaultValue={networkData.rpcUrl}
|
</Grid>
|
||||||
render={({ field: { onChange, onBlur, value } }) => (
|
<Grid item xs={6}>
|
||||||
<>
|
<Controller
|
||||||
<TextInput
|
control={control}
|
||||||
mode="outlined"
|
name="rpcUrl"
|
||||||
label="New RPC URL"
|
defaultValue={networkData.rpcUrl}
|
||||||
onBlur={onBlur}
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
value={value}
|
<>
|
||||||
onChangeText={textValue => onChange(textValue)}
|
<TextInput
|
||||||
/>
|
mode="outlined"
|
||||||
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
|
label="New RPC URL"
|
||||||
</>
|
onBlur={onBlur}
|
||||||
)}
|
value={value}
|
||||||
/>
|
onChangeText={(textValue) => onChange(textValue)}
|
||||||
|
/>
|
||||||
|
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Controller
|
<Grid item xs={isCosmos ? 6 : 12}>
|
||||||
control={control}
|
<Controller
|
||||||
defaultValue={networkData.blockExplorerUrl}
|
control={control}
|
||||||
name="blockExplorerUrl"
|
defaultValue={networkData.blockExplorerUrl}
|
||||||
render={({ field: { onChange, onBlur, value } }) => (
|
name="blockExplorerUrl"
|
||||||
<>
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
<TextInput
|
<>
|
||||||
mode="outlined"
|
<TextInput
|
||||||
value={value}
|
mode="outlined"
|
||||||
label="Block Explorer URL (Optional)"
|
value={value}
|
||||||
onBlur={onBlur}
|
label="Block Explorer URL (Optional)"
|
||||||
onChangeText={textValue => onChange(textValue)}
|
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">
|
</Grid>
|
||||||
{errors.blockExplorerUrl?.message}
|
|
||||||
</HelperText>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
/>
|
</Grid>
|
||||||
{networkData.namespace === COSMOS && (
|
<LoadingButton
|
||||||
<Controller
|
variant="contained"
|
||||||
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"
|
|
||||||
loading={isSubmitting}
|
loading={isSubmitting}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
style={styles.networksButton}
|
onClick={handleSubmit(submit)}
|
||||||
onPress={handleSubmit(submit)}>
|
sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }}
|
||||||
{isSubmitting ? 'Adding' : 'Submit'}
|
>
|
||||||
</Button>
|
{isSubmitting ? "Adding" : "Submit"}
|
||||||
</View>
|
</LoadingButton>
|
||||||
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,66 +1,43 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { View, ActivityIndicator, Image } from 'react-native';
|
import { View, ActivityIndicator } from "react-native";
|
||||||
import { Button, Text } from 'react-native-paper';
|
import { Text } from "react-native-paper";
|
||||||
|
import { Button, Divider, Portal, Snackbar } from "@mui/material";
|
||||||
|
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { getSdkError } from "@walletconnect/utils";
|
||||||
import { useNavigation } from '@react-navigation/native';
|
|
||||||
import { getSdkError } from '@walletconnect/utils';
|
|
||||||
|
|
||||||
import { createWallet, resetWallet, retrieveAccounts } from '../utils/accounts';
|
import { createWallet, resetWallet, retrieveAccounts } from "../utils/accounts";
|
||||||
import { DialogComponent } from '../components/Dialog';
|
import { NetworkDropdown } from "../components/NetworkDropdown";
|
||||||
import { NetworkDropdown } from '../components/NetworkDropdown';
|
import Accounts from "../components/Accounts";
|
||||||
import Accounts from '../components/Accounts';
|
import CreateWallet from "../components/CreateWallet";
|
||||||
import CreateWallet from '../components/CreateWallet';
|
import ConfirmDialog from "../components/ConfirmDialog";
|
||||||
import ConfirmDialog from '../components/ConfirmDialog';
|
import styles from "../styles/stylesheet";
|
||||||
import styles from '../styles/stylesheet';
|
import { useAccounts } from "../context/AccountsContext";
|
||||||
import { useAccounts } from '../context/AccountsContext';
|
import { useWalletConnect } from "../context/WalletConnectContext";
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
import { NetworksDataState } from "../types";
|
||||||
import { NetworksDataState, StackParamsList } from '../types';
|
import { useNetworks } from "../context/NetworksContext";
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import ImportWalletDialog from "../components/ImportWalletDialog";
|
||||||
|
import { MnemonicDialog } from "../components/MnemonicDialog";
|
||||||
const WCLogo = () => {
|
import { Container } from "../components/Container";
|
||||||
return (
|
import { IS_IMPORT_WALLET_ENABLED } from "../utils/constants";
|
||||||
<Image
|
|
||||||
style={styles.walletConnectLogo}
|
|
||||||
source={require('../assets/WalletConnect-Icon-Blueberry.png')}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const HomeScreen = () => {
|
const HomeScreen = () => {
|
||||||
const { accounts, setAccounts, setCurrentIndex } = useAccounts();
|
const { setAccounts, setCurrentIndex } = useAccounts();
|
||||||
|
|
||||||
const { networksData, selectedNetwork, setSelectedNetwork, setNetworksData } =
|
const { networksData, selectedNetwork, setSelectedNetwork, setNetworksData } =
|
||||||
useNetworks();
|
useNetworks();
|
||||||
const { setActiveSessions, web3wallet } = useWalletConnect();
|
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 [isWalletCreated, setIsWalletCreated] = useState<boolean>(false);
|
||||||
const [isWalletCreating, setIsWalletCreating] = 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 [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false);
|
||||||
|
const [toastVisible, setToastVisible] = useState(false);
|
||||||
|
const [invalidMnemonicError, setInvalidMnemonicError] = useState("");
|
||||||
const [isAccountsFetched, setIsAccountsFetched] = useState<boolean>(true);
|
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 hideResetDialog = () => setResetWalletDialog(false);
|
||||||
|
|
||||||
const fetchAccounts = useCallback(async () => {
|
const fetchAccounts = useCallback(async () => {
|
||||||
@ -83,12 +60,27 @@ const HomeScreen = () => {
|
|||||||
const mnemonic = await createWallet(networksData);
|
const mnemonic = await createWallet(networksData);
|
||||||
if (mnemonic) {
|
if (mnemonic) {
|
||||||
fetchAccounts();
|
fetchAccounts();
|
||||||
setWalletDialog(true);
|
setMnemonicDialog(true);
|
||||||
setPhrase(mnemonic);
|
setPhrase(mnemonic);
|
||||||
setSelectedNetwork(networksData[0]);
|
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 () => {
|
const confirmResetWallet = useCallback(async () => {
|
||||||
setIsWalletCreated(false);
|
setIsWalletCreated(false);
|
||||||
setIsWalletCreating(false);
|
setIsWalletCreating(false);
|
||||||
@ -99,10 +91,10 @@ const HomeScreen = () => {
|
|||||||
await resetWallet();
|
await resetWallet();
|
||||||
const sessions = web3wallet!.getActiveSessions();
|
const sessions = web3wallet!.getActiveSessions();
|
||||||
|
|
||||||
Object.keys(sessions).forEach(async sessionId => {
|
Object.keys(sessions).forEach(async (sessionId) => {
|
||||||
await web3wallet!.disconnectSession({
|
await web3wallet!.disconnectSession({
|
||||||
topic: sessionId,
|
topic: sessionId,
|
||||||
reason: getSdkError('USER_DISCONNECTED'),
|
reason: getSdkError("USER_DISCONNECTED"),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
setActiveSessions({});
|
setActiveSessions({});
|
||||||
@ -114,7 +106,7 @@ const HomeScreen = () => {
|
|||||||
setCurrentIndex,
|
setCurrentIndex,
|
||||||
setNetworksData,
|
setNetworksData,
|
||||||
setSelectedNetwork,
|
setSelectedNetwork,
|
||||||
web3wallet
|
web3wallet,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const updateNetwork = (networkData: NetworksDataState) => {
|
const updateNetwork = (networkData: NetworksDataState) => {
|
||||||
@ -128,46 +120,74 @@ const HomeScreen = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.appContainer}>
|
<View style={styles.appContainer}>
|
||||||
{!isAccountsFetched ? (
|
<Container>
|
||||||
<View style={styles.spinnerContainer}>
|
{!isAccountsFetched ? (
|
||||||
<Text style={styles.LoadingText}>Loading...</Text>
|
<View style={styles.spinnerContainer}>
|
||||||
<ActivityIndicator size="large" color="#0000ff" />
|
<Text style={styles.LoadingText}>Loading...</Text>
|
||||||
</View>
|
<ActivityIndicator size="large" color="#0000ff" />
|
||||||
) : isWalletCreated && selectedNetwork ? (
|
|
||||||
<>
|
|
||||||
<NetworkDropdown updateNetwork={updateNetwork} />
|
|
||||||
<View style={styles.accountComponent}>
|
|
||||||
<Accounts />
|
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.resetContainer}>
|
) : isWalletCreated && selectedNetwork ? (
|
||||||
|
<>
|
||||||
|
<NetworkDropdown updateNetwork={updateNetwork} />
|
||||||
|
<View style={styles.accountComponent}>
|
||||||
|
<Accounts />
|
||||||
|
</View>
|
||||||
|
<Divider flexItem sx={{ my: 4 }} />
|
||||||
<Button
|
<Button
|
||||||
style={styles.resetButton}
|
variant="contained"
|
||||||
mode="contained"
|
onClick={() => {
|
||||||
buttonColor="#B82B0D"
|
|
||||||
onPress={() => {
|
|
||||||
setResetWalletDialog(true);
|
setResetWalletDialog(true);
|
||||||
}}>
|
}}
|
||||||
|
color="error"
|
||||||
|
>
|
||||||
Reset Wallet
|
Reset Wallet
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</>
|
||||||
</>
|
) : (
|
||||||
) : (
|
<>
|
||||||
<CreateWallet
|
<CreateWallet
|
||||||
isWalletCreating={isWalletCreating}
|
isWalletCreating={isWalletCreating}
|
||||||
createWalletHandler={createWalletHandler}
|
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}
|
||||||
/>
|
/>
|
||||||
)}
|
<MnemonicDialog
|
||||||
<DialogComponent
|
visible={mnemonicDialog}
|
||||||
visible={walletDialog}
|
hideDialog={hideMnemonicDialog}
|
||||||
hideDialog={hideWalletDialog}
|
contentText={phrase}
|
||||||
contentText={phrase}
|
/>
|
||||||
/>
|
<ConfirmDialog
|
||||||
<ConfirmDialog
|
title="Reset wallet"
|
||||||
title="Reset wallet"
|
visible={resetWalletDialog}
|
||||||
visible={resetWalletDialog}
|
hideDialog={hideResetDialog}
|
||||||
hideDialog={hideResetDialog}
|
onConfirm={confirmResetWallet}
|
||||||
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>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,22 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from "react";
|
||||||
import { View } from 'react-native';
|
import { Text, TextInput } from "react-native-paper";
|
||||||
import { Button, 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 { StackParamsList } from "../types";
|
||||||
import styles from '../styles/stylesheet';
|
import { signMessage } from "../utils/sign-message";
|
||||||
import { signMessage } from '../utils/sign-message';
|
import AccountDetails from "../components/AccountDetails";
|
||||||
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 SignMessage = ({ route }: SignProps) => {
|
||||||
const namespace = route.params.selectedNamespace;
|
const namespace = route.params.selectedNamespace;
|
||||||
const chainId = route.params.selectedChainId;
|
const chainId = route.params.selectedChainId;
|
||||||
const account = route.params.accountInfo;
|
const account = route.params.accountInfo;
|
||||||
|
|
||||||
const [message, setMessage] = useState<string>('');
|
const [message, setMessage] = useState<string>("");
|
||||||
|
|
||||||
const signMessageHandler = async () => {
|
const signMessageHandler = async () => {
|
||||||
const signedMessage = await signMessage({
|
const signedMessage = await signMessage({
|
||||||
@ -29,31 +29,30 @@ const SignMessage = ({ route }: SignProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.signPage}>
|
<Layout title="Sign Message">
|
||||||
<View style={styles.accountInfo}>
|
<Text variant="titleMedium">
|
||||||
<View>
|
{account && `Account ${account.index + 1}`}
|
||||||
<Text variant="titleMedium">
|
</Text>
|
||||||
{account && `Account ${account.index + 1}`}
|
<AccountDetails account={account} />
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.accountContainer}>
|
|
||||||
<AccountDetails account={account} />
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<TextInput
|
<Stack spacing={4}>
|
||||||
mode="outlined"
|
<Divider flexItem />
|
||||||
placeholder="Enter your message"
|
<TextInput
|
||||||
onChangeText={text => setMessage(text)}
|
mode="outlined"
|
||||||
value={message}
|
placeholder="Enter your message"
|
||||||
/>
|
onChangeText={(text) => setMessage(text)}
|
||||||
|
value={message}
|
||||||
|
/>
|
||||||
|
|
||||||
<View style={styles.signButton}>
|
<Button
|
||||||
<Button mode="contained" onPress={signMessageHandler}>
|
variant="contained"
|
||||||
|
onClick={signMessageHandler}
|
||||||
|
sx={{ width: "200px", px: 4, py: 1, mt: 2 }}
|
||||||
|
>
|
||||||
Sign
|
Sign
|
||||||
</Button>
|
</Button>
|
||||||
</View>
|
</Stack>
|
||||||
</View>
|
</Layout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -202,7 +202,13 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
|||||||
chainId,
|
chainId,
|
||||||
accountId: account.index,
|
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);
|
setIsRejecting(false);
|
||||||
navigation.navigate('Home');
|
if (window.Android?.onSignatureCancelled) {
|
||||||
|
window.Android.onSignatureCancelled();
|
||||||
|
} else {
|
||||||
|
navigation.navigate('Home');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
212
src/screens/SignRequestEmbed.tsx
Normal file
212
src/screens/SignRequestEmbed.tsx
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
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 { getCosmosAccountByHDPath, retrieveSingleAccount } from '../utils/accounts';
|
||||||
|
import { getMnemonic, getPathKey, sendMessage } from '../utils/misc';
|
||||||
|
import { COSMOS, REQUEST_SIGN_MESSAGE, SIGN_MESSAGE_RESPONSE } from '../utils/constants';
|
||||||
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
|
|
||||||
|
const REACT_APP_ALLOWED_URLS = import.meta.env.REACT_APP_ALLOWED_URLS;
|
||||||
|
|
||||||
|
type SignRequestProps = NativeStackScreenProps<StackParamsList, 'sign-message-request-embed'>;
|
||||||
|
|
||||||
|
const SignRequestEmbed = ({ route }: SignRequestProps) => {
|
||||||
|
const [displayAccount, setDisplayAccount] = useState<Account>();
|
||||||
|
const [message, setMessage] = useState<string>('');
|
||||||
|
const [chainId, setChainId] = useState<string>('');
|
||||||
|
const [namespace, setNamespace] = 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 { networksData } = useNetworks();
|
||||||
|
const navigation =
|
||||||
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
|
const signMessageHandler = async () => {
|
||||||
|
if (!signDoc || !signerAddress || !sourceWindow) return;
|
||||||
|
|
||||||
|
setIsApproving(true);
|
||||||
|
try {
|
||||||
|
if (namespace !== COSMOS) {
|
||||||
|
// TODO: Support ETH namespace
|
||||||
|
throw new Error(`namespace ${namespace} is not supported`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const requestAccount = await retrieveSingleAccount(namespace, chainId, signerAddress);
|
||||||
|
const path = (await getPathKey(`${namespace}:${chainId}`, requestAccount!.index)).path;
|
||||||
|
const mnemonic = await getMnemonic();
|
||||||
|
|
||||||
|
const requestedNetworkData = networksData.find(networkData => networkData.chainId === chainId)
|
||||||
|
if (!requestedNetworkData) {
|
||||||
|
throw new Error("Requested network not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const cosmosAccount = await getCosmosAccountByHDPath(mnemonic, path, requestedNetworkData?.addressPrefix);
|
||||||
|
|
||||||
|
const cosmosAminoSignature = await cosmosAccount.cosmosWallet.signAmino(
|
||||||
|
signerAddress,
|
||||||
|
signDoc,
|
||||||
|
);
|
||||||
|
|
||||||
|
const signature = cosmosAminoSignature.signature.signature;
|
||||||
|
|
||||||
|
sendMessage(
|
||||||
|
sourceWindow,
|
||||||
|
SIGN_MESSAGE_RESPONSE,
|
||||||
|
{ signature },
|
||||||
|
origin,
|
||||||
|
);
|
||||||
|
|
||||||
|
navigation.navigate('Home');
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Signing failed:', err);
|
||||||
|
sendMessage(
|
||||||
|
sourceWindow!,
|
||||||
|
SIGN_MESSAGE_RESPONSE,
|
||||||
|
{ error: err },
|
||||||
|
origin,
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsApproving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectRequestHandler = useCallback(async () => {
|
||||||
|
if (sourceWindow && origin) {
|
||||||
|
sendMessage(
|
||||||
|
sourceWindow,
|
||||||
|
SIGN_MESSAGE_RESPONSE,
|
||||||
|
{ error: 'User rejected the request' },
|
||||||
|
origin,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [sourceWindow, origin]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleCosmosSignMessage = async (event: MessageEvent) => {
|
||||||
|
if (event.data.type !== REQUEST_SIGN_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;
|
||||||
|
|
||||||
|
const receivedNamespace = event.data.chainId.split(':')[0]
|
||||||
|
const receivedChainId = event.data.chainId.split(':')[1]
|
||||||
|
|
||||||
|
if (receivedNamespace !== COSMOS) {
|
||||||
|
// TODO: Support ETH namespace
|
||||||
|
throw new Error(`namespace ${receivedNamespace} is not supported`)
|
||||||
|
}
|
||||||
|
|
||||||
|
setSignerAddress(signerAddress);
|
||||||
|
setSignDoc(signDoc);
|
||||||
|
setMessage(signDoc.memo || '');
|
||||||
|
setOrigin(event.origin);
|
||||||
|
setSourceWindow(event.source as Window);
|
||||||
|
setNamespace(receivedNamespace);
|
||||||
|
setChainId(receivedChainId);
|
||||||
|
|
||||||
|
const requestAccount = await retrieveSingleAccount(
|
||||||
|
receivedNamespace,
|
||||||
|
receivedChainId,
|
||||||
|
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;
|
392
src/screens/SignTxEmbed.tsx
Normal file
392
src/screens/SignTxEmbed.tsx
Normal file
@ -0,0 +1,392 @@
|
|||||||
|
import React, { useEffect, useState, useCallback } from 'react';
|
||||||
|
import { ScrollView, View } from 'react-native';
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Text,
|
||||||
|
} from 'react-native-paper';
|
||||||
|
import JSONbig from 'json-bigint';
|
||||||
|
import { AuthInfo, SignDoc } from "cosmjs-types/cosmos/tx/v1beta1/tx";
|
||||||
|
|
||||||
|
import { DirectSecp256k1Wallet, Algo, TxBodyEncodeObject, decodeOptionalPubkey } from '@cosmjs/proto-signing';
|
||||||
|
import { SigningStargateClient } from '@cosmjs/stargate';
|
||||||
|
import { toHex } from '@cosmjs/encoding';
|
||||||
|
|
||||||
|
import { getCosmosAccountByHDPath, retrieveAccounts, retrieveSingleAccount } from '../utils/accounts';
|
||||||
|
import AccountDetails from '../components/AccountDetails';
|
||||||
|
import styles from '../styles/stylesheet';
|
||||||
|
import { getMnemonic, getPathKey, sendMessage } from '../utils/misc';
|
||||||
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
|
import TxErrorDialog from '../components/TxErrorDialog';
|
||||||
|
import { Account, NetworksDataState } from '../types';
|
||||||
|
import { REQUEST_SIGN_TX, REQUEST_COSMOS_ACCOUNTS, COSMOS_ACCOUNTS_RESPONSE, SIGN_TX_RESPONSE } from '../utils/constants';
|
||||||
|
|
||||||
|
// Type Definitions
|
||||||
|
interface GetAccountsRequestData {
|
||||||
|
chainId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SignTxRequestData {
|
||||||
|
address: string;
|
||||||
|
signDoc: SignDoc;
|
||||||
|
txBody: TxBodyEncodeObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
type IncomingMessageData = SignTxRequestData | GetAccountsRequestData;
|
||||||
|
|
||||||
|
interface IncomingMessageEventData {
|
||||||
|
id: string;
|
||||||
|
type: typeof REQUEST_SIGN_TX | typeof REQUEST_COSMOS_ACCOUNTS;
|
||||||
|
data: IncomingMessageData;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TransactionDetails = {
|
||||||
|
source: MessageEventSource;
|
||||||
|
origin: string;
|
||||||
|
signerAddress: string;
|
||||||
|
chainId: string;
|
||||||
|
account: Account;
|
||||||
|
requestedNetwork: NetworksDataState;
|
||||||
|
balance: string;
|
||||||
|
signDoc: SignDoc;
|
||||||
|
txBody: TxBodyEncodeObject;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GetAccountsResponse {
|
||||||
|
accounts: Array<{
|
||||||
|
algo: Algo;
|
||||||
|
address: string;
|
||||||
|
pubkey: string;
|
||||||
|
}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const REACT_APP_ALLOWED_URLS = import.meta.env.REACT_APP_ALLOWED_URLS;
|
||||||
|
|
||||||
|
export const SignTxEmbed = () => {
|
||||||
|
const [isTxApprovalVisible, setIsTxApprovalVisible] = useState<boolean>(false);
|
||||||
|
const [transactionDetails, setTransactionDetails] = useState<TransactionDetails | null>(null);
|
||||||
|
const [isTxLoading, setIsTxLoading] = useState(false);
|
||||||
|
const [txError, setTxError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const { networksData } = useNetworks();
|
||||||
|
|
||||||
|
// Message Handlers
|
||||||
|
|
||||||
|
const handleGetCosmosAccountsRequest = useCallback(async (event: MessageEvent<IncomingMessageEventData>) => {
|
||||||
|
const { data } = event.data;
|
||||||
|
const source = event.source as Window;
|
||||||
|
const origin = event.origin;
|
||||||
|
const requestData = data as GetAccountsRequestData;
|
||||||
|
const mnemonic = await getMnemonic();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestedNetworkData = networksData.find(networkData => networkData.chainId === requestData.chainId)
|
||||||
|
|
||||||
|
if(!requestedNetworkData) {
|
||||||
|
throw new Error("Network data not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
const allAccounts = await retrieveAccounts(requestedNetworkData);
|
||||||
|
|
||||||
|
if (!allAccounts || allAccounts.length === 0) {
|
||||||
|
throw new Error("Accounts not found for network")
|
||||||
|
}
|
||||||
|
|
||||||
|
const responseAccounts = await Promise.all(
|
||||||
|
allAccounts.map(async (acc) => {
|
||||||
|
const cosmosAccount = (await getCosmosAccountByHDPath(mnemonic, acc.hdPath, requestedNetworkData.addressPrefix)).data;
|
||||||
|
return {
|
||||||
|
...cosmosAccount,
|
||||||
|
pubkey: toHex(cosmosAccount.pubkey),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const response: GetAccountsResponse = { accounts: responseAccounts };
|
||||||
|
sendMessage(source, COSMOS_ACCOUNTS_RESPONSE, {data: response}, origin);
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(`Error handling ${REQUEST_COSMOS_ACCOUNTS}:`, error);
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
sendMessage(source, COSMOS_ACCOUNTS_RESPONSE, { error: `Failed to get accounts: ${errorMsg}` }, origin);
|
||||||
|
}
|
||||||
|
}, [networksData]);
|
||||||
|
|
||||||
|
const handleSignTxRequest = useCallback(async (event: MessageEvent<IncomingMessageEventData>) => {
|
||||||
|
const { data } = event.data;
|
||||||
|
const source = event.source as Window;
|
||||||
|
const origin = event.origin;
|
||||||
|
const requestData = data as SignTxRequestData;
|
||||||
|
|
||||||
|
setIsTxApprovalVisible(false);
|
||||||
|
setTransactionDetails(null);
|
||||||
|
setTxError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { address: signerAddress, signDoc, txBody } = requestData;
|
||||||
|
|
||||||
|
const network = networksData.find(net => net.chainId === signDoc.chainId);
|
||||||
|
if (!network) throw new Error(`Network with chainId "${signDoc.chainId}" not supported.`);
|
||||||
|
|
||||||
|
const account = await retrieveSingleAccount(network.namespace, network.chainId, signerAddress);
|
||||||
|
if (!account) throw new Error(`Account not found for address "${signerAddress}" on chain "${signDoc.chainId}".`);
|
||||||
|
|
||||||
|
// Balance Check
|
||||||
|
// Use a temporary read-only client for balance
|
||||||
|
const { privKey } = await getPathKey(`${network.namespace}:${network.chainId}`, account.index)
|
||||||
|
|
||||||
|
const tempWallet = await DirectSecp256k1Wallet.fromKey(
|
||||||
|
new Uint8Array(Buffer.from(privKey.replace(/^0x/, ''), 'hex')),
|
||||||
|
network.addressPrefix
|
||||||
|
);
|
||||||
|
|
||||||
|
const client = await SigningStargateClient.connectWithSigner(network.rpcUrl!, tempWallet);
|
||||||
|
const balance = await client.getBalance(account.address, network.nativeDenom!);
|
||||||
|
client.disconnect();
|
||||||
|
|
||||||
|
if (!balance || balance.amount === "0") {
|
||||||
|
throw new Error(`${account.address} does not have any balance`)
|
||||||
|
}
|
||||||
|
|
||||||
|
setTransactionDetails({
|
||||||
|
source: source,
|
||||||
|
origin: origin,
|
||||||
|
signerAddress,
|
||||||
|
chainId: signDoc.chainId,
|
||||||
|
account,
|
||||||
|
requestedNetwork: network,
|
||||||
|
balance: balance.amount,
|
||||||
|
signDoc,
|
||||||
|
txBody
|
||||||
|
});
|
||||||
|
|
||||||
|
setIsTxApprovalVisible(true);
|
||||||
|
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error(`Error handling ${REQUEST_SIGN_TX}:`, error);
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
sendMessage(source, SIGN_TX_RESPONSE, { error: `Failed to prepare transaction: ${errorMsg}` }, origin);
|
||||||
|
setTxError(errorMsg);
|
||||||
|
}
|
||||||
|
}, [networksData]);
|
||||||
|
|
||||||
|
const handleIncomingMessage = useCallback((event: MessageEvent) => {
|
||||||
|
if (!event.data || typeof event.data !== 'object' || !event.data.type || !event.source || event.source === window) {
|
||||||
|
return; // Basic validation
|
||||||
|
}
|
||||||
|
|
||||||
|
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 messageData = event.data as IncomingMessageEventData;
|
||||||
|
|
||||||
|
switch (messageData.type) {
|
||||||
|
case REQUEST_COSMOS_ACCOUNTS:
|
||||||
|
handleGetCosmosAccountsRequest(event as MessageEvent<IncomingMessageEventData>);
|
||||||
|
break;
|
||||||
|
case REQUEST_SIGN_TX:
|
||||||
|
handleSignTxRequest(event as MessageEvent<IncomingMessageEventData>);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(`Received unknown message type: ${messageData.type}`);
|
||||||
|
}
|
||||||
|
}, [handleGetCosmosAccountsRequest, handleSignTxRequest]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('message', handleIncomingMessage);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('message', handleIncomingMessage);
|
||||||
|
};
|
||||||
|
}, [handleIncomingMessage]);
|
||||||
|
|
||||||
|
// Action Handlers
|
||||||
|
|
||||||
|
const acceptRequestHandler = async () => {
|
||||||
|
if (!transactionDetails) {
|
||||||
|
setTxError("Transaction details are missing.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsTxLoading(true);
|
||||||
|
setTxError(null);
|
||||||
|
|
||||||
|
const { source, origin, requestedNetwork, chainId, account, signerAddress, signDoc } = transactionDetails;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { privKey } = await getPathKey(`${requestedNetwork.namespace}:${chainId}`, account.index);
|
||||||
|
|
||||||
|
const privateKeyBytes = Buffer.from(privKey.replace(/^0x/, ''), 'hex');
|
||||||
|
const wallet = await DirectSecp256k1Wallet.fromKey(new Uint8Array(privateKeyBytes), requestedNetwork.addressPrefix); // Wrap in Uint8Array
|
||||||
|
|
||||||
|
// Perform the actual signing
|
||||||
|
const signResponse = await wallet.signDirect(signerAddress, signDoc);
|
||||||
|
|
||||||
|
sendMessage(source as Window, SIGN_TX_RESPONSE, {data: signResponse}, origin);
|
||||||
|
|
||||||
|
setIsTxApprovalVisible(false);
|
||||||
|
setTransactionDetails(null);
|
||||||
|
|
||||||
|
} catch (error: unknown) {
|
||||||
|
console.error("Error during signDirect:", error);
|
||||||
|
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||||
|
|
||||||
|
setTxError(errorMsg);
|
||||||
|
sendMessage(source as Window, SIGN_TX_RESPONSE, { error: `Failed to sign transaction: ${errorMsg}` }, origin);
|
||||||
|
} finally {
|
||||||
|
setIsTxLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const rejectRequestHandler = () => {
|
||||||
|
if (!transactionDetails) return;
|
||||||
|
const { source, origin } = transactionDetails;
|
||||||
|
|
||||||
|
sendMessage(source as Window, SIGN_TX_RESPONSE, { error: "User rejected the signature request." }, origin);
|
||||||
|
setIsTxApprovalVisible(false);
|
||||||
|
setTransactionDetails(null);
|
||||||
|
setTxError(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const decodedAuth = React.useMemo(() => {
|
||||||
|
if (!transactionDetails) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = AuthInfo.decode(transactionDetails.signDoc.authInfoBytes);
|
||||||
|
return {
|
||||||
|
...info,
|
||||||
|
signerInfos: info.signerInfos.map((signerInfo) => ({
|
||||||
|
...signerInfo,
|
||||||
|
publicKey: decodeOptionalPubkey(signerInfo.publicKey),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}, [transactionDetails]);
|
||||||
|
|
||||||
|
const formattedTxBody = React.useMemo(
|
||||||
|
() => {
|
||||||
|
if (!transactionDetails) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONbig.stringify(transactionDetails.txBody, null, 2)
|
||||||
|
},
|
||||||
|
[transactionDetails]
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedAuthInfo = React.useMemo(
|
||||||
|
() => JSONbig.stringify(decodedAuth, null, 2),
|
||||||
|
[decodedAuth]
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedSignDoc = React.useMemo(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
if (!transactionDetails) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONbig.stringify(
|
||||||
|
{
|
||||||
|
...transactionDetails.signDoc,
|
||||||
|
bodyBytes: toHex(transactionDetails.signDoc.bodyBytes),
|
||||||
|
authInfoBytes: toHex(transactionDetails.signDoc.authInfoBytes),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[transactionDetails]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isTxApprovalVisible && transactionDetails ? (
|
||||||
|
<>
|
||||||
|
<ScrollView contentContainerStyle={styles.appContainer}>
|
||||||
|
<View style={{ marginBottom: 16 }}>
|
||||||
|
<Text variant='titleLarge'>Account</Text>
|
||||||
|
<View style={styles.dataBox}>
|
||||||
|
<AccountDetails account={transactionDetails.account} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ marginBottom: 16 }}>
|
||||||
|
<Text variant='titleLarge'>{`Balance (${transactionDetails.requestedNetwork.nativeDenom})`}</Text>
|
||||||
|
<View style={styles.dataBox}>
|
||||||
|
<Text variant='bodyLarge'>
|
||||||
|
{transactionDetails.balance}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ marginBottom: 16 }}>
|
||||||
|
<Text variant='titleLarge'>Transaction Body</Text>
|
||||||
|
<ScrollView style={styles.messageBody} contentContainerStyle={{ padding: 10 }}>
|
||||||
|
<Text style={styles.codeText}>
|
||||||
|
{formattedTxBody}
|
||||||
|
</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ marginBottom: 16 }}>
|
||||||
|
<Text variant='titleLarge'>Auth Info</Text>
|
||||||
|
<ScrollView style={styles.messageBody} contentContainerStyle={{ padding: 10 }}>
|
||||||
|
<Text style={styles.codeText}>
|
||||||
|
{formattedAuthInfo}
|
||||||
|
</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={{ marginBottom: 16 }}>
|
||||||
|
<Text variant='titleLarge'>Transaction Data To Be Signed</Text>
|
||||||
|
<ScrollView style={styles.messageBody} contentContainerStyle={{ padding: 10 }}>
|
||||||
|
<Text style={styles.codeText}>
|
||||||
|
{formattedSignDoc}
|
||||||
|
</Text>
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<View style={styles.buttonContainer}>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={acceptRequestHandler}
|
||||||
|
loading={isTxLoading}
|
||||||
|
disabled={isTxLoading}
|
||||||
|
style={{marginTop: 10}}
|
||||||
|
>
|
||||||
|
{isTxLoading ? 'Processing...' : 'Approve'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
onPress={rejectRequestHandler}
|
||||||
|
buttonColor="#B82B0D"
|
||||||
|
disabled={isTxLoading}
|
||||||
|
style={{ marginTop: 10 }}
|
||||||
|
>
|
||||||
|
Reject
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<View style={styles.spinnerContainer}>
|
||||||
|
<Text style={{ marginTop: 50, textAlign: 'center' }}>Waiting for request...</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
<TxErrorDialog
|
||||||
|
error={txError!}
|
||||||
|
visible={!!txError}
|
||||||
|
hideDialog={() => setTxError(null)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,11 +1,12 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from "react";
|
||||||
import { Image, TouchableOpacity, View } from 'react-native';
|
import { Image, TouchableOpacity, View } from "react-native";
|
||||||
import { List, Text } from 'react-native-paper';
|
import { List, Text } from "react-native-paper";
|
||||||
|
|
||||||
import { getSdkError } from '@walletconnect/utils';
|
import { getSdkError } from "@walletconnect/utils";
|
||||||
|
|
||||||
import { useWalletConnect } from '../context/WalletConnectContext';
|
import { useWalletConnect } from "../context/WalletConnectContext";
|
||||||
import styles from '../styles/stylesheet';
|
import styles from "../styles/stylesheet";
|
||||||
|
import { Layout } from "../components/Layout";
|
||||||
|
|
||||||
export default function WalletConnect() {
|
export default function WalletConnect() {
|
||||||
const { web3wallet, activeSessions, setActiveSessions } = useWalletConnect();
|
const { web3wallet, activeSessions, setActiveSessions } = useWalletConnect();
|
||||||
@ -13,7 +14,7 @@ export default function WalletConnect() {
|
|||||||
const disconnect = async (sessionId: string) => {
|
const disconnect = async (sessionId: string) => {
|
||||||
await web3wallet!.disconnectSession({
|
await web3wallet!.disconnectSession({
|
||||||
topic: sessionId,
|
topic: sessionId,
|
||||||
reason: getSdkError('USER_DISCONNECTED'),
|
reason: getSdkError("USER_DISCONNECTED"),
|
||||||
});
|
});
|
||||||
const sessions = web3wallet?.getActiveSessions() || {};
|
const sessions = web3wallet?.getActiveSessions() || {};
|
||||||
setActiveSessions(sessions);
|
setActiveSessions(sessions);
|
||||||
@ -26,12 +27,10 @@ export default function WalletConnect() {
|
|||||||
}, [web3wallet, setActiveSessions]);
|
}, [web3wallet, setActiveSessions]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View>
|
<Layout title="Active Sessions">
|
||||||
{Object.keys(activeSessions).length > 0 ? (
|
{Object.keys(activeSessions).length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<View style={styles.sessionsContainer}>
|
<View style={styles.sessionsContainer} />
|
||||||
<Text variant="titleMedium">Active Sessions</Text>
|
|
||||||
</View>
|
|
||||||
<List.Section>
|
<List.Section>
|
||||||
{Object.entries(activeSessions).map(([sessionId, session]) => (
|
{Object.entries(activeSessions).map(([sessionId, session]) => (
|
||||||
<List.Item
|
<List.Item
|
||||||
@ -44,7 +43,7 @@ export default function WalletConnect() {
|
|||||||
// eslint-disable-next-line react/no-unstable-nested-components
|
// eslint-disable-next-line react/no-unstable-nested-components
|
||||||
left={() => (
|
left={() => (
|
||||||
<>
|
<>
|
||||||
{session.peer.metadata.icons[0].endsWith('.svg') ? (
|
{session.peer.metadata.icons[0].endsWith(".svg") ? (
|
||||||
<View style={styles.dappLogo}>
|
<View style={styles.dappLogo}>
|
||||||
<Text>SvgURI peerMetaDataIcon</Text>
|
<Text>SvgURI peerMetaDataIcon</Text>
|
||||||
</View>
|
</View>
|
||||||
@ -60,7 +59,8 @@ export default function WalletConnect() {
|
|||||||
right={() => (
|
right={() => (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
onPress={() => disconnect(sessionId)}
|
onPress={() => disconnect(sessionId)}
|
||||||
style={styles.disconnectSession}>
|
style={styles.disconnectSession}
|
||||||
|
>
|
||||||
<List.Icon icon="close" />
|
<List.Icon icon="close" />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
)}
|
)}
|
||||||
@ -73,6 +73,6 @@ export default function WalletConnect() {
|
|||||||
<Text>You have no active sessions</Text>
|
<Text>You have no active sessions</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</View>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
317
src/screens/WalletEmbed.tsx
Normal file
317
src/screens/WalletEmbed.tsx
Normal 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';
|
||||||
|
import { REQUEST_TX, REQUEST_WALLET_ACCOUNTS, TRANSACTION_RESPONSE, WALLET_ACCOUNTS_DATA } from '../utils/constants';
|
||||||
|
|
||||||
|
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)) {
|
||||||
|
throw new Error('Insufficient funds');
|
||||||
|
}
|
||||||
|
|
||||||
|
const gasEstimation = await client.simulate(fromAddress, [sendMsg], MEMO);
|
||||||
|
const gasLimit = String(
|
||||||
|
Math.round(gasEstimation * Number(import.meta.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, '*');
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,22 +1,21 @@
|
|||||||
import { StyleSheet } from 'react-native';
|
import { StyleSheet } from "react-native";
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
createWalletContainer: {
|
createWalletContainer: {
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: 150,
|
alignSelf: "center",
|
||||||
alignSelf: 'center',
|
marginBottom: 30,
|
||||||
marginBottom: 40
|
|
||||||
},
|
},
|
||||||
signLink: {
|
signLink: {
|
||||||
alignItems: 'flex-end',
|
alignItems: "flex-end",
|
||||||
marginTop: 24,
|
marginTop: 24,
|
||||||
},
|
},
|
||||||
hyperlink: {
|
hyperlink: {
|
||||||
fontWeight: '500',
|
fontWeight: "500",
|
||||||
textDecorationLine: 'underline',
|
textDecorationLine: "underline",
|
||||||
},
|
},
|
||||||
highlight: {
|
highlight: {
|
||||||
fontWeight: '700',
|
fontWeight: "700",
|
||||||
},
|
},
|
||||||
accountContainer: {
|
accountContainer: {
|
||||||
padding: 8,
|
padding: 8,
|
||||||
@ -24,28 +23,32 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
addAccountButton: {
|
addAccountButton: {
|
||||||
marginTop: 24,
|
marginTop: 24,
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
},
|
},
|
||||||
accountComponent: {
|
accountComponent: {
|
||||||
flex: 4,
|
flex: 4,
|
||||||
},
|
},
|
||||||
|
appSurface: {
|
||||||
|
backgroundColor: "#0f0f0f",
|
||||||
|
},
|
||||||
appContainer: {
|
appContainer: {
|
||||||
flexGrow: 1,
|
flexGrow: 1,
|
||||||
marginTop: 24,
|
marginTop: 24,
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 24,
|
||||||
|
backgroundColor: "#0f0f0f",
|
||||||
},
|
},
|
||||||
resetContainer: {
|
resetContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
resetButton: {
|
resetButton: {
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
},
|
},
|
||||||
signButton: {
|
signButton: {
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
width: 150,
|
width: 150,
|
||||||
alignSelf: 'center',
|
alignSelf: "center",
|
||||||
},
|
},
|
||||||
signPage: {
|
signPage: {
|
||||||
paddingHorizontal: 24,
|
paddingHorizontal: 24,
|
||||||
@ -71,33 +74,81 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 10,
|
borderRadius: 10,
|
||||||
},
|
},
|
||||||
dialogWarning: {
|
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: {
|
gridContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
flexWrap: 'wrap',
|
flexWrap: "wrap",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
gridItem: {
|
gridItem: {
|
||||||
width: '25%',
|
width: "30%",
|
||||||
margin: 8,
|
margin: 4,
|
||||||
padding: 6,
|
padding: 4,
|
||||||
borderWidth: 1,
|
borderRadius: 4,
|
||||||
borderColor: '#ccc',
|
alignItems: "center",
|
||||||
borderRadius: 8,
|
justifyContent: "flex-start",
|
||||||
alignItems: 'center',
|
backgroundColor: "#29292E",
|
||||||
justifyContent: 'flex-start',
|
|
||||||
},
|
},
|
||||||
HDcontainer: {
|
HDcontainer: {
|
||||||
marginTop: 24,
|
marginTop: 24,
|
||||||
paddingHorizontal: 8,
|
paddingHorizontal: 8,
|
||||||
},
|
},
|
||||||
HDrowContainer: {
|
HDrowContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
HDtext: {
|
HDtext: {
|
||||||
color: 'black',
|
color: "#FBFBFB",
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
margin: 4,
|
margin: 4,
|
||||||
},
|
},
|
||||||
@ -107,15 +158,15 @@ const styles = StyleSheet.create({
|
|||||||
HDbuttonContainer: {
|
HDbuttonContainer: {
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
width: 200,
|
width: 200,
|
||||||
alignSelf: 'center',
|
alignSelf: "flex-start",
|
||||||
},
|
},
|
||||||
spinnerContainer: {
|
spinnerContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
LoadingText: {
|
LoadingText: {
|
||||||
color: 'black',
|
color: "black",
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
},
|
},
|
||||||
@ -123,9 +174,9 @@ const styles = StyleSheet.create({
|
|||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
marginTop: 50,
|
marginTop: 50,
|
||||||
height: 'auto',
|
height: "auto",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
},
|
},
|
||||||
requestDirectMessage: {
|
requestDirectMessage: {
|
||||||
@ -134,51 +185,51 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginBottom: 50,
|
marginBottom: 50,
|
||||||
height: 500,
|
height: 500,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
padding: 8,
|
padding: 8,
|
||||||
},
|
},
|
||||||
approveTransfer: {
|
approveTransfer: {
|
||||||
height: '40%',
|
height: "40%",
|
||||||
marginBottom: 30,
|
marginBottom: 30,
|
||||||
},
|
},
|
||||||
buttonContainer: {
|
buttonContainer: {
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
marginLeft: 20,
|
marginLeft: 20,
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
justifyContent: 'space-evenly',
|
justifyContent: "space-evenly",
|
||||||
},
|
},
|
||||||
badRequestContainer: {
|
badRequestContainer: {
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
padding: 20,
|
padding: 20,
|
||||||
},
|
},
|
||||||
invalidMessageText: {
|
invalidMessageText: {
|
||||||
color: 'black',
|
color: "black",
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
container: {
|
container: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
paddingHorizontal: 20,
|
paddingHorizontal: 20,
|
||||||
},
|
},
|
||||||
modalContentContainer: {
|
modalContentContainer: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
borderRadius: 34,
|
borderRadius: 34,
|
||||||
borderBottomStartRadius: 0,
|
borderBottomStartRadius: 0,
|
||||||
borderBottomEndRadius: 0,
|
borderBottomEndRadius: 0,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
width: '100%',
|
width: "100%",
|
||||||
height: '50%',
|
height: "50%",
|
||||||
position: 'absolute',
|
position: "absolute",
|
||||||
backgroundColor: 'white',
|
backgroundColor: "#0f0f0f",
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
},
|
},
|
||||||
modalOuterContainer: { flex: 1 },
|
modalOuterContainer: { flex: 1 },
|
||||||
@ -187,32 +238,32 @@ const styles = StyleSheet.create({
|
|||||||
height: 50,
|
height: 50,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
marginVertical: 16,
|
marginVertical: 16,
|
||||||
overflow: 'hidden',
|
overflow: "hidden",
|
||||||
},
|
},
|
||||||
space: {
|
space: {
|
||||||
width: 50,
|
width: 50,
|
||||||
},
|
},
|
||||||
flexRow: {
|
flexRow: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
flexDirection: 'row',
|
flexDirection: "row",
|
||||||
justifyContent: 'space-between',
|
justifyContent: "space-between",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
paddingHorizontal: 16,
|
paddingHorizontal: 16,
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
},
|
},
|
||||||
marginVertical8: {
|
marginVertical8: {
|
||||||
marginVertical: 8,
|
marginVertical: 8,
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
subHeading: {
|
subHeading: {
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
fontSize: 20
|
fontSize: 20,
|
||||||
},
|
},
|
||||||
centerText: {
|
centerText: {
|
||||||
textAlign: 'center',
|
textAlign: "center",
|
||||||
},
|
},
|
||||||
messageBody: {
|
messageBody: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
@ -225,46 +276,48 @@ const styles = StyleSheet.create({
|
|||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
},
|
},
|
||||||
dappDetails: {
|
dappDetails: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
},
|
},
|
||||||
dataBoxContainer: {
|
dataBoxContainer: {
|
||||||
marginBottom: 10,
|
marginBottom: 10,
|
||||||
|
backgroundColor: "#29292E",
|
||||||
|
border: "none",
|
||||||
},
|
},
|
||||||
dataBoxLabel: {
|
dataBoxLabel: {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
marginBottom: 3,
|
marginBottom: 3,
|
||||||
color: 'black',
|
color: "white",
|
||||||
},
|
},
|
||||||
dataBox: {
|
dataBox: {
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: '#ccc',
|
borderColor: "#ccc",
|
||||||
padding: 10,
|
padding: 10,
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
},
|
},
|
||||||
dataBoxData: {
|
dataBoxData: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: 'black',
|
color: "white",
|
||||||
},
|
},
|
||||||
transactionText: {
|
transactionText: {
|
||||||
padding: 8,
|
padding: 8,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
color: 'black',
|
color: "black",
|
||||||
},
|
},
|
||||||
balancePadding: {
|
balancePadding: {
|
||||||
padding: 8,
|
padding: 8,
|
||||||
},
|
},
|
||||||
noActiveSessions: {
|
noActiveSessions: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
alignItems: 'center',
|
alignItems: "center",
|
||||||
marginTop: 20,
|
marginTop: 20,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
},
|
},
|
||||||
disconnectSession: {
|
disconnectSession: {
|
||||||
display: 'flex',
|
display: "flex",
|
||||||
justifyContent: 'center',
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
sessionItem: {
|
sessionItem: {
|
||||||
paddingLeft: 12,
|
paddingLeft: 12,
|
||||||
@ -281,7 +334,7 @@ const styles = StyleSheet.create({
|
|||||||
margin: 0,
|
margin: 0,
|
||||||
},
|
},
|
||||||
selectNetworkText: {
|
selectNetworkText: {
|
||||||
fontWeight: 'bold',
|
fontWeight: "bold",
|
||||||
marginVertical: 10,
|
marginVertical: 10,
|
||||||
},
|
},
|
||||||
transactionFeesInput: { marginBottom: 10 },
|
transactionFeesInput: { marginBottom: 10 },
|
||||||
@ -292,7 +345,7 @@ const styles = StyleSheet.create({
|
|||||||
paddingVertical: 5,
|
paddingVertical: 5,
|
||||||
},
|
},
|
||||||
transactionLabel: {
|
transactionLabel: {
|
||||||
fontWeight: '700',
|
fontWeight: "700",
|
||||||
padding: 8,
|
padding: 8,
|
||||||
},
|
},
|
||||||
linkContainer: {
|
linkContainer: {
|
||||||
@ -301,7 +354,11 @@ const styles = StyleSheet.create({
|
|||||||
networksButton: {
|
networksButton: {
|
||||||
marginTop: 12,
|
marginTop: 12,
|
||||||
marginBottom: 20,
|
marginBottom: 20,
|
||||||
}
|
},
|
||||||
|
codeText: {
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 12,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default styles;
|
export default styles;
|
||||||
|
@ -13,8 +13,10 @@ export type StackParamsList = {
|
|||||||
};
|
};
|
||||||
SignRequest: {
|
SignRequest: {
|
||||||
namespace: string;
|
namespace: string;
|
||||||
|
chainId?: string;
|
||||||
address: string;
|
address: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
accountInfo?: Account;
|
||||||
requestEvent?: Web3WalletTypes.SessionRequest;
|
requestEvent?: Web3WalletTypes.SessionRequest;
|
||||||
requestSessionData?: SessionTypes.Struct;
|
requestSessionData?: SessionTypes.Struct;
|
||||||
};
|
};
|
||||||
@ -36,6 +38,10 @@ export type StackParamsList = {
|
|||||||
requestEvent: Web3WalletTypes.SessionRequest;
|
requestEvent: Web3WalletTypes.SessionRequest;
|
||||||
requestSessionData: SessionTypes.Struct;
|
requestSessionData: SessionTypes.Struct;
|
||||||
};
|
};
|
||||||
|
"wallet-embed": undefined;
|
||||||
|
"auto-sign-in": undefined;
|
||||||
|
"sign-message-request-embed": undefined;
|
||||||
|
"sign-tx-request-embed": undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Account = {
|
export type Account = {
|
||||||
@ -58,7 +64,7 @@ export type NetworksFormData = {
|
|||||||
namespace: string;
|
namespace: string;
|
||||||
nativeDenom?: string;
|
nativeDenom?: string;
|
||||||
addressPrefix?: string;
|
addressPrefix?: string;
|
||||||
coinType?: string;
|
coinType: string;
|
||||||
gasPrice?: string;
|
gasPrice?: string;
|
||||||
isDefault: boolean;
|
isDefault: boolean;
|
||||||
};
|
};
|
||||||
|
@ -8,10 +8,10 @@ import { utils } from 'ethers';
|
|||||||
import { HDNode } from 'ethers/lib/utils';
|
import { HDNode } from 'ethers/lib/utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
setInternetCredentials,
|
setInternetCredentials,
|
||||||
resetInternetCredentials,
|
resetInternetCredentials,
|
||||||
getInternetCredentials,
|
getInternetCredentials,
|
||||||
} from './key-store';
|
} from './key-store';
|
||||||
import { Secp256k1HdWallet } from '@cosmjs/amino';
|
import { Secp256k1HdWallet } from '@cosmjs/amino';
|
||||||
import { AccountData } from '@cosmjs/proto-signing';
|
import { AccountData } from '@cosmjs/proto-signing';
|
||||||
import { stringToPath } from '@cosmjs/crypto';
|
import { stringToPath } from '@cosmjs/crypto';
|
||||||
@ -27,12 +27,23 @@ import { COSMOS, EIP155 } from './constants';
|
|||||||
|
|
||||||
const createWallet = async (
|
const createWallet = async (
|
||||||
networksData: NetworksDataState[],
|
networksData: NetworksDataState[],
|
||||||
|
recoveryPhrase?: string,
|
||||||
): Promise<string> => {
|
): Promise<string> => {
|
||||||
const mnemonic = utils.entropyToMnemonic(utils.randomBytes(16));
|
const mnemonic = recoveryPhrase ? recoveryPhrase : utils.entropyToMnemonic(utils.randomBytes(16));
|
||||||
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
|
|
||||||
|
|
||||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
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) {
|
for (const network of networksData) {
|
||||||
const hdPath = `m/44'/${network.coinType}'/0'/0/0`;
|
const hdPath = `m/44'/${network.coinType}'/0'/0/0`;
|
||||||
const node = hdNode.derivePath(hdPath);
|
const node = hdNode.derivePath(hdPath);
|
||||||
@ -45,7 +56,7 @@ const createWallet = async (
|
|||||||
|
|
||||||
case COSMOS:
|
case COSMOS:
|
||||||
address = (
|
address = (
|
||||||
await getCosmosAccounts(mnemonic, hdPath, network.addressPrefix)
|
await getCosmosAccountByHDPath(mnemonic, hdPath, network.addressPrefix)
|
||||||
).data.address;
|
).data.address;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -53,7 +64,7 @@ const createWallet = async (
|
|||||||
throw new Error('Unsupported namespace');
|
throw new Error('Unsupported namespace');
|
||||||
}
|
}
|
||||||
|
|
||||||
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
|
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
setInternetCredentials(
|
setInternetCredentials(
|
||||||
@ -73,20 +84,47 @@ const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return mnemonic;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const addAccount = async (
|
const addAccount = async (
|
||||||
networkData: NetworksDataState,
|
chainId: string,
|
||||||
): Promise<Account | undefined> => {
|
): Promise<Account | undefined> => {
|
||||||
try {
|
try {
|
||||||
const namespaceChainId = `${networkData.namespace}:${networkData.chainId}`;
|
let selectedNetworkAccount
|
||||||
const id = await getNextAccountId(namespaceChainId);
|
const networksData = await retrieveNetworksData();
|
||||||
const hdPath = getHDPath(namespaceChainId, `0'/0/${id}`);
|
|
||||||
const accounts = await addAccountFromHDPath(hdPath, networkData);
|
// Add account to all networks and return account for selected network
|
||||||
await updateAccountCounter(namespaceChainId, id);
|
for (const network of networksData) {
|
||||||
return accounts;
|
const namespaceChainId = `${network.namespace}:${network.chainId}`;
|
||||||
|
const id = await getNextAccountId(namespaceChainId);
|
||||||
|
const hdPath = getHDPath(namespaceChainId, `0'/0/${id}`);
|
||||||
|
const account = await addAccountFromHDPath(hdPath, network);
|
||||||
|
await updateAccountCounter(namespaceChainId, id);
|
||||||
|
|
||||||
|
if (network.chainId === chainId) {
|
||||||
|
selectedNetworkAccount = account;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectedNetworkAccount;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating account:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addAccountsForNetwork = async (
|
||||||
|
network: NetworksDataState,
|
||||||
|
numberOfAccounts: number,
|
||||||
|
): Promise<void> => {
|
||||||
|
try {
|
||||||
|
const namespaceChainId = `${network.namespace}:${network.chainId}`;
|
||||||
|
|
||||||
|
for (let i = 0; i < numberOfAccounts; i++) {
|
||||||
|
const id = await getNextAccountId(namespaceChainId);
|
||||||
|
const hdPath = getHDPath(namespaceChainId, `0'/0/${id}`);
|
||||||
|
await addAccountFromHDPath(hdPath, network);
|
||||||
|
await updateAccountCounter(namespaceChainId, id);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error creating account:', error);
|
console.error('Error creating account:', error);
|
||||||
}
|
}
|
||||||
@ -122,6 +160,77 @@ const addAccountFromHDPath = async (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addNewNetwork = async (
|
||||||
|
newNetworkData: NetworksFormData
|
||||||
|
): Promise<NetworksDataState[]> => {
|
||||||
|
const mnemonicServer = await getInternetCredentials("mnemonicServer");
|
||||||
|
const mnemonic = mnemonicServer;
|
||||||
|
|
||||||
|
if (!mnemonic) {
|
||||||
|
throw new Error("Mnemonic not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||||
|
|
||||||
|
const hdPath = `m/44'/${newNetworkData.coinType}'/0'/0/0`;
|
||||||
|
const node = hdNode.derivePath(hdPath);
|
||||||
|
let address;
|
||||||
|
|
||||||
|
switch (newNetworkData.namespace) {
|
||||||
|
case EIP155:
|
||||||
|
address = node.address;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case COSMOS:
|
||||||
|
address = (
|
||||||
|
await getCosmosAccountByHDPath(
|
||||||
|
mnemonic,
|
||||||
|
hdPath,
|
||||||
|
newNetworkData.addressPrefix,
|
||||||
|
)
|
||||||
|
).data.address;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error("Unsupported namespace");
|
||||||
|
}
|
||||||
|
|
||||||
|
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
|
||||||
|
|
||||||
|
await Promise.all([
|
||||||
|
setInternetCredentials(
|
||||||
|
`accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`,
|
||||||
|
"_",
|
||||||
|
accountInfo,
|
||||||
|
),
|
||||||
|
setInternetCredentials(
|
||||||
|
`addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`,
|
||||||
|
"_",
|
||||||
|
"1",
|
||||||
|
),
|
||||||
|
setInternetCredentials(
|
||||||
|
`accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`,
|
||||||
|
"_",
|
||||||
|
"0",
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const retrievedNetworksData = await storeNetworkData(newNetworkData);
|
||||||
|
|
||||||
|
// Get number of accounts in first network
|
||||||
|
const nextAccountId = await getNextAccountId(
|
||||||
|
`${retrievedNetworksData[0].namespace}:${retrievedNetworksData[0].chainId}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedNetwork = retrievedNetworksData.find(
|
||||||
|
(network) => network.chainId === newNetworkData.chainId,
|
||||||
|
);
|
||||||
|
|
||||||
|
await addAccountsForNetwork(selectedNetwork!, nextAccountId - 1);
|
||||||
|
|
||||||
|
return retrievedNetworksData;
|
||||||
|
}
|
||||||
|
|
||||||
const storeNetworkData = async (
|
const storeNetworkData = async (
|
||||||
networkData: NetworksFormData,
|
networkData: NetworksFormData,
|
||||||
): Promise<NetworksDataState[]> => {
|
): Promise<NetworksDataState[]> => {
|
||||||
@ -142,21 +251,25 @@ const storeNetworkData = async (
|
|||||||
networkId: String(networkId),
|
networkId: String(networkId),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
await setInternetCredentials(
|
await setInternetCredentials(
|
||||||
'networks',
|
'networks',
|
||||||
'_',
|
'_',
|
||||||
JSON.stringify(updatedNetworks),
|
JSON.stringify(updatedNetworks),
|
||||||
);
|
);
|
||||||
|
|
||||||
return updatedNetworks;
|
return updatedNetworks;
|
||||||
};
|
};
|
||||||
|
|
||||||
const retrieveNetworksData = async (): Promise<NetworksDataState[]> => {
|
const retrieveNetworksData = async (): Promise<NetworksDataState[]> => {
|
||||||
const networks = await getInternetCredentials('networks');
|
const networks = await getInternetCredentials('networks');
|
||||||
|
|
||||||
if(!networks){
|
if (!networks) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedNetworks: NetworksDataState[] = JSON.parse(networks);
|
const parsedNetworks: NetworksDataState[] = JSON.parse(networks);
|
||||||
|
|
||||||
return parsedNetworks;
|
return parsedNetworks;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -179,6 +292,7 @@ export const retrieveAccountsForNetwork = async (
|
|||||||
address,
|
address,
|
||||||
hdPath: path,
|
hdPath: path,
|
||||||
};
|
};
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -196,10 +310,11 @@ const retrieveAccounts = async (
|
|||||||
if (!accountIndices) {
|
if (!accountIndices) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadedAccounts = await retrieveAccountsForNetwork(
|
const loadedAccounts = await retrieveAccountsForNetwork(
|
||||||
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
|
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
|
||||||
accountIndices,
|
accountIndices,
|
||||||
)
|
)
|
||||||
|
|
||||||
return loadedAccounts;
|
return loadedAccounts;
|
||||||
};
|
};
|
||||||
@ -272,7 +387,7 @@ const accountInfoFromHDPath = async (
|
|||||||
break;
|
break;
|
||||||
case COSMOS:
|
case COSMOS:
|
||||||
address = (
|
address = (
|
||||||
await getCosmosAccounts(mnemonic, hdPath, networkData.addressPrefix)
|
await getCosmosAccountByHDPath(mnemonic, hdPath, networkData.addressPrefix)
|
||||||
).data.address;
|
).data.address;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -314,7 +429,7 @@ const updateAccountCounter = async (
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getCosmosAccounts = async (
|
const getCosmosAccountByHDPath = async (
|
||||||
mnemonic: string,
|
mnemonic: string,
|
||||||
path: string,
|
path: string,
|
||||||
prefix: string = COSMOS,
|
prefix: string = COSMOS,
|
||||||
@ -330,10 +445,33 @@ const getCosmosAccounts = async (
|
|||||||
return { cosmosWallet, data };
|
return { cosmosWallet, data };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkNetworkForChainID = async (
|
||||||
|
chainId: string,
|
||||||
|
): Promise<boolean> => {
|
||||||
|
const networks = await getInternetCredentials('networks');
|
||||||
|
|
||||||
|
if (!networks) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const networksData: NetworksFormData[] = JSON.parse(networks);
|
||||||
|
|
||||||
|
return networksData.some((network) => network.chainId === chainId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isWalletCreated = async (
|
||||||
|
): Promise<boolean> => {
|
||||||
|
const mnemonicServer = await getInternetCredentials("mnemonicServer");
|
||||||
|
const mnemonic = mnemonicServer;
|
||||||
|
|
||||||
|
return mnemonic !== null;
|
||||||
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createWallet,
|
createWallet,
|
||||||
addAccount,
|
addAccount,
|
||||||
addAccountFromHDPath,
|
addAccountFromHDPath,
|
||||||
|
addAccountsForNetwork,
|
||||||
storeNetworkData,
|
storeNetworkData,
|
||||||
retrieveNetworksData,
|
retrieveNetworksData,
|
||||||
retrieveAccounts,
|
retrieveAccounts,
|
||||||
@ -342,5 +480,8 @@ export {
|
|||||||
accountInfoFromHDPath,
|
accountInfoFromHDPath,
|
||||||
getNextAccountId,
|
getNextAccountId,
|
||||||
updateAccountCounter,
|
updateAccountCounter,
|
||||||
getCosmosAccounts,
|
getCosmosAccountByHDPath,
|
||||||
|
addNewNetwork,
|
||||||
|
checkNetworkForChainID,
|
||||||
|
isWalletCreated
|
||||||
};
|
};
|
||||||
|
@ -1,20 +1,34 @@
|
|||||||
import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData';
|
import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData';
|
||||||
import { EIP155_CHAINS } from './wallet-connect/EIP155Data';
|
import { EIP155_CHAINS } from './wallet-connect/EIP155Data';
|
||||||
|
import { NetworksFormData } from '../types';
|
||||||
|
|
||||||
export const EIP155 = 'eip155';
|
export const EIP155 = 'eip155';
|
||||||
export const COSMOS = 'cosmos';
|
export const COSMOS = 'cosmos';
|
||||||
export const DEFAULT_NETWORKS = [
|
|
||||||
|
export const DEFAULT_NETWORKS: NetworksFormData[] = [
|
||||||
|
{
|
||||||
|
chainId: 'laconic-testnet-2',
|
||||||
|
networkName: 'laconicd testnet-2',
|
||||||
|
namespace: COSMOS,
|
||||||
|
rpcUrl: import.meta.env.REACT_APP_LACONICD_RPC_URL!,
|
||||||
|
blockExplorerUrl: '',
|
||||||
|
nativeDenom: 'alnt',
|
||||||
|
addressPrefix: 'laconic',
|
||||||
|
coinType: '118',
|
||||||
|
gasPrice: '0.001',
|
||||||
|
isDefault: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
chainId: 'laconic_9000-1',
|
chainId: 'laconic_9000-1',
|
||||||
networkName: 'laconicd',
|
networkName: 'laconicd',
|
||||||
namespace: COSMOS,
|
namespace: COSMOS,
|
||||||
rpcUrl: process.env.REACT_APP_LACONICD_RPC_URL!,
|
rpcUrl: "https://laconicd.laconic.com",
|
||||||
blockExplorerUrl: '',
|
blockExplorerUrl: '',
|
||||||
nativeDenom: 'alnt',
|
nativeDenom: 'alnt',
|
||||||
addressPrefix: 'laconic',
|
addressPrefix: 'laconic',
|
||||||
coinType: '118',
|
coinType: '118',
|
||||||
gasPrice: '1',
|
gasPrice: '1',
|
||||||
isDefault: true,
|
isDefault: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
chainId: '1',
|
chainId: '1',
|
||||||
@ -46,3 +60,32 @@ export const EMPTY_FIELD_ERROR = 'Field cannot be empty';
|
|||||||
export const INVALID_URL_ERROR = 'Invalid URL';
|
export const INVALID_URL_ERROR = 'Invalid URL';
|
||||||
|
|
||||||
export const IS_NUMBER_REGEX = /^\d+$/;
|
export const IS_NUMBER_REGEX = /^\d+$/;
|
||||||
|
|
||||||
|
export const IS_IMPORT_WALLET_ENABLED = false;
|
||||||
|
|
||||||
|
// iframe request types
|
||||||
|
export const REQUEST_COSMOS_ACCOUNTS = 'REQUEST_COSMOS_ACCOUNTS';
|
||||||
|
export const REQUEST_SIGN_TX = 'REQUEST_SIGN_TX';
|
||||||
|
export const REQUEST_SIGN_MESSAGE = 'REQUEST_SIGN_MESSAGE';
|
||||||
|
export const REQUEST_WALLET_ACCOUNTS = 'REQUEST_WALLET_ACCOUNTS';
|
||||||
|
export const REQUEST_CREATE_OR_GET_ACCOUNTS = 'REQUEST_CREATE_OR_GET_ACCOUNTS';
|
||||||
|
export const REQUEST_TX = 'REQUEST_TX';
|
||||||
|
export const REQUEST_ACCOUNT_PK = 'REQUEST_ACCOUNT_PK';
|
||||||
|
export const REQUEST_ADD_ACCOUNT = 'REQUEST_ADD_ACCOUNT';
|
||||||
|
export const AUTO_SIGN_IN = 'AUTO_SIGN_IN';
|
||||||
|
export const CHECK_BALANCE = 'CHECK_BALANCE';
|
||||||
|
export const REQUEST_ADD_NETWORK = 'REQUEST_ADD_NETWORK';
|
||||||
|
|
||||||
|
// iframe response types
|
||||||
|
export const COSMOS_ACCOUNTS_RESPONSE = 'COSMOS_ACCOUNTS_RESPONSE';
|
||||||
|
export const SIGN_TX_RESPONSE = 'SIGN_TX_RESPONSE';
|
||||||
|
export const SIGN_MESSAGE_RESPONSE = 'SIGN_MESSAGE_RESPONSE';
|
||||||
|
export const TRANSACTION_RESPONSE = 'TRANSACTION_RESPONSE';
|
||||||
|
export const SIGN_IN_RESPONSE = 'SIGN_IN_RESPONSE';
|
||||||
|
export const ACCOUNT_PK_RESPONSE = 'ACCOUNT_PK_RESPONSE';
|
||||||
|
export const ADD_ACCOUNT_RESPONSE = 'ADD_ACCOUNT_RESPONSE';
|
||||||
|
export const WALLET_ACCOUNTS_DATA = 'WALLET_ACCOUNTS_DATA';
|
||||||
|
export const IS_SUFFICIENT = 'IS_SUFFICIENT';
|
||||||
|
export const NETWORK_ADDED_RESPONSE = "NETWORK_ADDED_RESPONSE";
|
||||||
|
export const NETWORK_ALREADY_EXISTS_RESPONSE = "NETWORK_ALREADY_EXISTS_RESPONSE";
|
||||||
|
export const NETWORK_ADD_FAILED_RESPONSE = "NETWORK_ADD_FAILED_RESPONSE";
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
const setInternetCredentials = (name:string, username:string, password:string) => {
|
const setInternetCredentials = (name:string, username:string, password:string) => {
|
||||||
localStorage.setItem(name, password);
|
localStorage.setItem(name, password);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInternetCredentials = (name:string) : string | null => {
|
const getInternetCredentials = (name:string) : string | null => {
|
||||||
return localStorage.getItem(name);
|
return localStorage.getItem(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetInternetCredentials = (name:string) => {
|
const resetInternetCredentials = (name:string) => {
|
||||||
localStorage.removeItem(name);
|
localStorage.removeItem(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
export {
|
export {
|
||||||
setInternetCredentials,
|
setInternetCredentials,
|
||||||
getInternetCredentials,
|
getInternetCredentials,
|
||||||
resetInternetCredentials
|
resetInternetCredentials
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
/* Importing this library provides react native with a secure random source.
|
/* 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" */
|
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
|
||||||
import 'react-native-get-random-values';
|
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 '@ethersproject/shims';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -9,10 +13,6 @@ import {
|
|||||||
resetInternetCredentials,
|
resetInternetCredentials,
|
||||||
setInternetCredentials,
|
setInternetCredentials,
|
||||||
} from './key-store';
|
} from './key-store';
|
||||||
|
|
||||||
import { AccountData } from '@cosmjs/amino';
|
|
||||||
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
|
|
||||||
import { stringToPath } from '@cosmjs/crypto';
|
|
||||||
import { EIP155 } from './constants';
|
import { EIP155 } from './constants';
|
||||||
import { NetworksDataState } from '../types';
|
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 {
|
export {
|
||||||
getMnemonic,
|
getMnemonic,
|
||||||
getPathKey,
|
getPathKey,
|
||||||
updateAccountIndices,
|
updateAccountIndices,
|
||||||
getHDPath,
|
getHDPath,
|
||||||
resetKeyServers,
|
resetKeyServers,
|
||||||
|
sendMessage,
|
||||||
|
checkSufficientFunds,
|
||||||
};
|
};
|
||||||
|
@ -3,13 +3,14 @@ For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#c
|
|||||||
import 'react-native-get-random-values';
|
import 'react-native-get-random-values';
|
||||||
|
|
||||||
import '@ethersproject/shims';
|
import '@ethersproject/shims';
|
||||||
|
import { fromBech32 } from '@cosmjs/encoding';
|
||||||
|
|
||||||
import { Wallet } from 'ethers';
|
import { Wallet } from 'ethers';
|
||||||
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
|
import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
|
||||||
|
|
||||||
import { SignMessageParams } from '../types';
|
import { SignMessageParams } from '../types';
|
||||||
import { getDirectWallet, getMnemonic, getPathKey } from './misc';
|
import { getDirectWallet, getMnemonic, getPathKey } from './misc';
|
||||||
import { getCosmosAccounts } from './accounts';
|
import { getCosmosAccountByHDPath } from './accounts';
|
||||||
import { COSMOS, EIP155 } from './constants';
|
import { COSMOS, EIP155 } from './constants';
|
||||||
|
|
||||||
const signMessage = async ({
|
const signMessage = async ({
|
||||||
@ -24,7 +25,7 @@ const signMessage = async ({
|
|||||||
case EIP155:
|
case EIP155:
|
||||||
return await signEthMessage(message, accountId, chainId);
|
return await signEthMessage(message, accountId, chainId);
|
||||||
case COSMOS:
|
case COSMOS:
|
||||||
return await signCosmosMessage(message, path.path);
|
return await signCosmosMessage(message, path.path, path.address);
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid wallet type');
|
throw new Error('Invalid wallet type');
|
||||||
}
|
}
|
||||||
@ -51,10 +52,13 @@ const signEthMessage = async (
|
|||||||
const signCosmosMessage = async (
|
const signCosmosMessage = async (
|
||||||
message: string,
|
message: string,
|
||||||
path: string,
|
path: string,
|
||||||
|
cosmosAddress: string,
|
||||||
): Promise<string | undefined> => {
|
): Promise<string | undefined> => {
|
||||||
try {
|
try {
|
||||||
const mnemonic = await getMnemonic();
|
const mnemonic = await getMnemonic();
|
||||||
const cosmosAccount = await getCosmosAccounts(mnemonic, path);
|
const addressPrefix = fromBech32(cosmosAddress).prefix
|
||||||
|
|
||||||
|
const cosmosAccount = await getCosmosAccountByHDPath(mnemonic, path, addressPrefix);
|
||||||
const address = cosmosAccount.data.address;
|
const address = cosmosAccount.data.address;
|
||||||
const cosmosSignature = await cosmosAccount.cosmosWallet.signAmino(
|
const cosmosSignature = await cosmosAccount.cosmosWallet.signAmino(
|
||||||
address,
|
address,
|
||||||
|
@ -11,7 +11,7 @@ export let core: ICore;
|
|||||||
|
|
||||||
export async function createWeb3Wallet() {
|
export async function createWeb3Wallet() {
|
||||||
core = new Core({
|
core = new Core({
|
||||||
projectId: process.env.REACT_APP_WALLET_CONNECT_PROJECT_ID,
|
projectId: import.meta.env.REACT_APP_WALLET_CONNECT_PROJECT_ID,
|
||||||
});
|
});
|
||||||
|
|
||||||
const web3wallet = await Web3Wallet.init({
|
const web3wallet = await Web3Wallet.init({
|
||||||
|
@ -7,8 +7,9 @@ import { getSdkError } from '@walletconnect/utils';
|
|||||||
import {
|
import {
|
||||||
SigningStargateClient,
|
SigningStargateClient,
|
||||||
StdFee,
|
StdFee,
|
||||||
MsgSendEncodeObject,
|
MsgSendEncodeObject
|
||||||
} from '@cosmjs/stargate';
|
} from '@cosmjs/stargate';
|
||||||
|
import { fromBech32 } from '@cosmjs/encoding';
|
||||||
import { EncodeObject } from '@cosmjs/proto-signing';
|
import { EncodeObject } from '@cosmjs/proto-signing';
|
||||||
import { LaconicClient } from '@cerc-io/registry-sdk';
|
import { LaconicClient } from '@cerc-io/registry-sdk';
|
||||||
import { Buffer } from 'buffer';
|
import { Buffer } from 'buffer';
|
||||||
@ -17,8 +18,9 @@ import { EIP155_SIGNING_METHODS } from './EIP155Data';
|
|||||||
import { signDirectMessage, signEthMessage } from '../sign-message';
|
import { signDirectMessage, signEthMessage } from '../sign-message';
|
||||||
import { Account } from '../../types';
|
import { Account } from '../../types';
|
||||||
import { getMnemonic, getPathKey } from '../misc';
|
import { getMnemonic, getPathKey } from '../misc';
|
||||||
import { getCosmosAccounts } from '../accounts';
|
import { getCosmosAccountByHDPath } from '../accounts';
|
||||||
import { COSMOS_METHODS } from './COSMOSData';
|
import { COSMOS_METHODS } from './COSMOSData';
|
||||||
|
import { COSMOS } from '../constants';
|
||||||
|
|
||||||
interface EthSendTransaction {
|
interface EthSendTransaction {
|
||||||
type: 'eth_sendTransaction';
|
type: 'eth_sendTransaction';
|
||||||
@ -80,7 +82,13 @@ export async function approveWalletConnectRequest(
|
|||||||
const path = (await getPathKey(`${namespace}:${chainId}`, account.index))
|
const path = (await getPathKey(`${namespace}:${chainId}`, account.index))
|
||||||
.path;
|
.path;
|
||||||
const mnemonic = await getMnemonic();
|
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 getCosmosAccountByHDPath(mnemonic, path, addressPrefix);
|
||||||
const address = account.address;
|
const address = account.address;
|
||||||
|
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
@ -97,18 +105,18 @@ export async function approveWalletConnectRequest(
|
|||||||
const updatedTransaction =
|
const updatedTransaction =
|
||||||
options.maxFeePerGas && options.maxPriorityFeePerGas
|
options.maxFeePerGas && options.maxPriorityFeePerGas
|
||||||
? {
|
? {
|
||||||
...sendTransaction,
|
...sendTransaction,
|
||||||
gasLimit: options.ethGasLimit,
|
gasLimit: options.ethGasLimit,
|
||||||
maxFeePerGas: options.maxFeePerGas,
|
maxFeePerGas: options.maxFeePerGas,
|
||||||
maxPriorityFeePerGas: options.maxPriorityFeePerGas,
|
maxPriorityFeePerGas: options.maxPriorityFeePerGas,
|
||||||
type: 2,
|
type: 2,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
...sendTransaction,
|
...sendTransaction,
|
||||||
gasLimit: options.ethGasLimit,
|
gasLimit: options.ethGasLimit,
|
||||||
gasPrice: options.ethGasPrice,
|
gasPrice: options.ethGasPrice,
|
||||||
type: 0,
|
type: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectedWallet = wallet.connect(options.provider);
|
const connectedWallet = wallet.connect(options.provider);
|
||||||
|
|
||||||
|
@ -6,12 +6,16 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
CERC_SCRIPT_DEBUG: ${CERC_SCRIPT_DEBUG}
|
CERC_SCRIPT_DEBUG: ${CERC_SCRIPT_DEBUG}
|
||||||
WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}
|
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_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE:-0.025}
|
||||||
CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT:-2}
|
CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT:-2}
|
||||||
CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL:-https://laconicd.laconic.com}
|
CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL:-https://laconicd.laconic.com}
|
||||||
|
CERC_ZENITHD_RPC_URL: ${CERC_ZENITHD_RPC_URL}
|
||||||
|
CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}
|
||||||
command: ["bash", "/scripts/run.sh"]
|
command: ["bash", "/scripts/run.sh"]
|
||||||
volumes:
|
volumes:
|
||||||
- ../config/app/run.sh:/scripts/run.sh
|
- ../config/app/run.sh:/scripts/run.sh
|
||||||
|
- ../config/app/serve.json:/app/serve.json
|
||||||
ports:
|
ports:
|
||||||
- "80"
|
- "80"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
|
@ -10,12 +10,25 @@ echo "WALLET_CONNECT_ID: ${WALLET_CONNECT_ID}"
|
|||||||
echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}"
|
echo "CERC_DEFAULT_GAS_PRICE: ${CERC_DEFAULT_GAS_PRICE}"
|
||||||
echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}"
|
echo "CERC_GAS_ADJUSTMENT: ${CERC_GAS_ADJUSTMENT}"
|
||||||
echo "CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL}"
|
echo "CERC_LACONICD_RPC_URL: ${CERC_LACONICD_RPC_URL}"
|
||||||
|
echo "CERC_ALLOWED_URLS: ${CERC_ALLOWED_URLS}"
|
||||||
|
|
||||||
# Build with required env
|
# Build with required env
|
||||||
REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_ID \
|
export REACT_APP_WALLET_CONNECT_PROJECT_ID=$WALLET_CONNECT_ID
|
||||||
REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE \
|
export REACT_APP_DEFAULT_GAS_PRICE=$CERC_DEFAULT_GAS_PRICE
|
||||||
REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT \
|
export REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT
|
||||||
REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL \
|
export REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL
|
||||||
yarn build
|
export REACT_APP_ALLOWED_URLS=$CERC_ALLOWED_URLS
|
||||||
|
|
||||||
http-server --proxy http://localhost:80? -p 80 /app/build
|
# Set env variables in build
|
||||||
|
yarn set-env
|
||||||
|
|
||||||
|
# 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
|
||||||
|
22
stack/stack-orchestrator/config/app/serve.json
Normal file
22
stack/stack-orchestrator/config/app/serve.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
# Originally from: https://github.com/devcontainers/images/blob/main/src/javascript-node/.devcontainer/Dockerfile
|
# Originally from: https://github.com/devcontainers/images/blob/main/src/javascript-node/.devcontainer/Dockerfile
|
||||||
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
# [Choice] Node.js version (use -bullseye variants on local arm64/Apple Silicon): 18, 16, 14, 18-bullseye, 16-bullseye, 14-bullseye, 18-buster, 16-buster, 14-buster
|
||||||
ARG VARIANT=18-bullseye
|
ARG VARIANT=22-bullseye
|
||||||
FROM node:${VARIANT}
|
FROM node:${VARIANT}
|
||||||
|
|
||||||
ARG USERNAME=node
|
ARG USERNAME=node
|
||||||
@ -33,11 +33,11 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|||||||
RUN mkdir -p /scripts
|
RUN mkdir -p /scripts
|
||||||
|
|
||||||
# Install simple web server for now (use nginx perhaps later)
|
# Install simple web server for now (use nginx perhaps later)
|
||||||
RUN yarn global add http-server
|
RUN yarn global add serve
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN yarn install
|
RUN yarn install && yarn build
|
||||||
|
|
||||||
# Expose port for http
|
# Expose port for http
|
||||||
EXPOSE 80
|
EXPOSE 80
|
||||||
|
@ -7,7 +7,7 @@ Instructions for running the `laconic-wallet-web` using [laconic-so](https://git
|
|||||||
* Clone the stack repo:
|
* Clone the stack repo:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
laconic-so fetch-stack git.vdb.to/cerc-io/laconic-wallet-web
|
laconic-so fetch-stack git.vdb.to/LaconicNetwork/laconic-wallet-web
|
||||||
```
|
```
|
||||||
|
|
||||||
* Build the container image:
|
* Build the container image:
|
||||||
@ -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
|
# WalletConnect project ID, same should be used in the laconic-wallet
|
||||||
WALLET_CONNECT_ID=
|
WALLET_CONNECT_ID=
|
||||||
|
|
||||||
|
# Allowed urls is a comma separated list of allowed urls
|
||||||
|
CERC_ALLOWED_URLS=
|
||||||
|
|
||||||
# Optional
|
# Optional
|
||||||
|
|
||||||
|
# WalletConnect code for hostname verification
|
||||||
|
WALLET_CONNECT_VERIFY_CODE=
|
||||||
|
|
||||||
# Default gas price for txs (default: 0.025)
|
# Default gas price for txs (default: 0.025)
|
||||||
CERC_DEFAULT_GAS_PRICE=
|
CERC_DEFAULT_GAS_PRICE=
|
||||||
|
|
||||||
@ -60,6 +66,9 @@ Instructions for running the `laconic-wallet-web` using [laconic-so](https://git
|
|||||||
|
|
||||||
# RPC endpoint of laconicd node (default: https://laconicd.laconic.com)
|
# RPC endpoint of laconicd node (default: https://laconicd.laconic.com)
|
||||||
CERC_LACONICD_RPC_URL=
|
CERC_LACONICD_RPC_URL=
|
||||||
|
|
||||||
|
# Zenith RPC endpoint
|
||||||
|
CERC_ZENITHD_RPC_URL=
|
||||||
```
|
```
|
||||||
|
|
||||||
## Start the deployment
|
## Start the deployment
|
||||||
|
416
yarn.lock
416
yarn.lock
@ -1410,6 +1410,15 @@
|
|||||||
bech32 "^1.1.4"
|
bech32 "^1.1.4"
|
||||||
readonly-date "^1.0.0"
|
readonly-date "^1.0.0"
|
||||||
|
|
||||||
|
"@cosmjs/encoding@^0.33.1":
|
||||||
|
version "0.33.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.33.1.tgz#77d6a8e0152c658ecf07b5aee3f5968d9071da50"
|
||||||
|
integrity sha512-nuNxf29fUcQE14+1p//VVQDwd1iau5lhaW/7uMz7V2AH3GJbFJoJVaKvVyZvdFk+Cnu+s3wCqgq4gJkhRCJfKw==
|
||||||
|
dependencies:
|
||||||
|
base64-js "^1.3.0"
|
||||||
|
bech32 "^1.1.4"
|
||||||
|
readonly-date "^1.0.0"
|
||||||
|
|
||||||
"@cosmjs/json-rpc@^0.32.4":
|
"@cosmjs/json-rpc@^0.32.4":
|
||||||
version "0.32.4"
|
version "0.32.4"
|
||||||
resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.32.4.tgz#be91eb89ea78bd5dc02d0a9fa184dd6790790f0b"
|
resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.32.4.tgz#be91eb89ea78bd5dc02d0a9fa184dd6790790f0b"
|
||||||
@ -1746,14 +1755,14 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
|
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz#5e13fac887f08c44f76b0ccaf3370eb00fec9bb6"
|
||||||
integrity sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==
|
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"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||||
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||||
dependencies:
|
dependencies:
|
||||||
eslint-visitor-keys "^3.3.0"
|
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"
|
version "4.11.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
|
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
|
||||||
integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
|
integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==
|
||||||
@ -2125,6 +2134,33 @@
|
|||||||
"@ethersproject/properties" "^5.7.0"
|
"@ethersproject/properties" "^5.7.0"
|
||||||
"@ethersproject/strings" "^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":
|
"@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0":
|
||||||
version "9.3.0"
|
version "9.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
||||||
@ -2161,6 +2197,37 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3"
|
||||||
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
|
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
|
||||||
|
|
||||||
|
"@import-meta-env/cli@^0.7.3":
|
||||||
|
version "0.7.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@import-meta-env/cli/-/cli-0.7.3.tgz#95a01da7db11ee92c4802dbe04e198d602ee7296"
|
||||||
|
integrity sha512-7xSPYhpXr0tulKk7Xv332fKRmoTwNUI+6eWUwgekNeRCNUvWsy9C0MfFk2rCDc43gGDJOywb1LxulthWpxFX1g==
|
||||||
|
dependencies:
|
||||||
|
commander "13.1.0"
|
||||||
|
dotenv "^16.0.0"
|
||||||
|
glob "11.0.1"
|
||||||
|
picocolors "1.1.1"
|
||||||
|
serialize-javascript "6.0.2"
|
||||||
|
|
||||||
|
"@import-meta-env/typescript@^0.4.0":
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@import-meta-env/typescript/-/typescript-0.4.0.tgz#e0f0eaa8312a70bf9961b5f73ddbb6db66b8263c"
|
||||||
|
integrity sha512-SHU8u4H/RAaJgTWPOfUKUFP5Dfg3iLy6ovCTOxqbsgOQyIfS+TgRy9EFCMxmuub1ULo77FAJ5nRwf2z3BenXsA==
|
||||||
|
dependencies:
|
||||||
|
commander "^12.0.0"
|
||||||
|
dotenv "^16.0.0"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
|
||||||
|
"@import-meta-env/unplugin@^0.6.2":
|
||||||
|
version "0.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@import-meta-env/unplugin/-/unplugin-0.6.2.tgz#adca83faa42e0ca846b064fd42dd3de529dde6c1"
|
||||||
|
integrity sha512-m8TEQTgWekSkhlT9lkHBKQ4TDf5l8+BWvO6q/cxcsv1AvyfsOXUOHbvjhKSiVDaz/CDDCbOWc/aOAiPFRzcXGA==
|
||||||
|
dependencies:
|
||||||
|
dotenv "^16.0.0"
|
||||||
|
magic-string "^0.30.0"
|
||||||
|
object-hash "^3.0.0"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
unplugin "^2.0.0"
|
||||||
|
|
||||||
"@ipld/dag-cbor@^7.0.1":
|
"@ipld/dag-cbor@^7.0.1":
|
||||||
version "7.0.3"
|
version "7.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e"
|
resolved "https://registry.yarnpkg.com/@ipld/dag-cbor/-/dag-cbor-7.0.3.tgz#aa31b28afb11a807c3d627828a344e5521ac4a1e"
|
||||||
@ -2513,7 +2580,7 @@
|
|||||||
"@jridgewell/gen-mapping" "^0.3.5"
|
"@jridgewell/gen-mapping" "^0.3.5"
|
||||||
"@jridgewell/trace-mapping" "^0.3.25"
|
"@jridgewell/trace-mapping" "^0.3.25"
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
|
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.5.0":
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a"
|
||||||
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
|
integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==
|
||||||
@ -2557,11 +2624,44 @@
|
|||||||
tweetnacl "^1.0.3"
|
tweetnacl "^1.0.3"
|
||||||
tweetnacl-util "^0.15.1"
|
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":
|
"@mui/core-downloads-tracker@^5.16.4":
|
||||||
version "5.16.4"
|
version "5.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.4.tgz#a34de72acd7e81fdbcc7eeb07786205e90dda148"
|
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.4.tgz#a34de72acd7e81fdbcc7eeb07786205e90dda148"
|
||||||
integrity sha512-rNdHXhclwjEZnK+//3SR43YRx0VtjdHnUFhMSGYmAMJve+KiwEja/41EYh8V3pZKqF2geKyfcFUenTfDTYUR4w==
|
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":
|
"@mui/material@^5.16.4":
|
||||||
version "5.16.4"
|
version "5.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.4.tgz#992d630637d9d38620e4937fb11d0a97965fdabf"
|
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.4.tgz#992d630637d9d38620e4937fb11d0a97965fdabf"
|
||||||
@ -2589,6 +2689,15 @@
|
|||||||
"@mui/utils" "^5.16.4"
|
"@mui/utils" "^5.16.4"
|
||||||
prop-types "^15.8.1"
|
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":
|
"@mui/styled-engine@^5.16.4":
|
||||||
version "5.16.4"
|
version "5.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.4.tgz#a7a8c9079c307bab91ccd65ed5dd1496ddf2a3ab"
|
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.4.tgz#a7a8c9079c307bab91ccd65ed5dd1496ddf2a3ab"
|
||||||
@ -2599,6 +2708,16 @@
|
|||||||
csstype "^3.1.3"
|
csstype "^3.1.3"
|
||||||
prop-types "^15.8.1"
|
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":
|
"@mui/system@^5.16.4":
|
||||||
version "5.16.4"
|
version "5.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.4.tgz#c03f971ed273f0ad06c69c949c05e866ad211407"
|
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.4.tgz#c03f971ed273f0ad06c69c949c05e866ad211407"
|
||||||
@ -2613,11 +2732,37 @@
|
|||||||
csstype "^3.1.3"
|
csstype "^3.1.3"
|
||||||
prop-types "^15.8.1"
|
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"
|
version "7.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.15.tgz#dadd232fe9a70be0d526630675dff3b110f30b53"
|
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.15.tgz#dadd232fe9a70be0d526630675dff3b110f30b53"
|
||||||
integrity sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q==
|
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":
|
"@mui/utils@^5.16.4":
|
||||||
version "5.16.4"
|
version "5.16.4"
|
||||||
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.4.tgz#8e50e27a630e3d8eeb3e9d3bc31cbb0e4956f5fd"
|
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.4.tgz#8e50e27a630e3d8eeb3e9d3bc31cbb0e4956f5fd"
|
||||||
@ -3834,7 +3979,12 @@
|
|||||||
expect "^29.0.0"
|
expect "^29.0.0"
|
||||||
pretty-format "^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-bigint@^1.0.4":
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/json-bigint/-/json-bigint-1.0.4.tgz#250d29e593375499d8ba6efaab22d094c3199ef3"
|
||||||
|
integrity sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag==
|
||||||
|
|
||||||
|
"@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"
|
version "7.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
|
||||||
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
|
||||||
@ -3973,7 +4123,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@types/node" "*"
|
"@types/node" "*"
|
||||||
|
|
||||||
"@types/semver@^7.3.12":
|
"@types/semver@^7.3.12", "@types/semver@^7.5.0":
|
||||||
version "7.5.8"
|
version "7.5.8"
|
||||||
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
|
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
|
||||||
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
|
integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
|
||||||
@ -4075,6 +4225,23 @@
|
|||||||
semver "^7.3.7"
|
semver "^7.3.7"
|
||||||
tsutils "^3.21.0"
|
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":
|
"@typescript-eslint/experimental-utils@^5.0.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.62.0.tgz#14559bf73383a308026b427a4a6129bae2146741"
|
||||||
@ -4092,6 +4259,17 @@
|
|||||||
"@typescript-eslint/typescript-estree" "5.62.0"
|
"@typescript-eslint/typescript-estree" "5.62.0"
|
||||||
debug "^4.3.4"
|
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":
|
"@typescript-eslint/scope-manager@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c"
|
||||||
@ -4100,6 +4278,14 @@
|
|||||||
"@typescript-eslint/types" "5.62.0"
|
"@typescript-eslint/types" "5.62.0"
|
||||||
"@typescript-eslint/visitor-keys" "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":
|
"@typescript-eslint/type-utils@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a"
|
||||||
@ -4110,11 +4296,26 @@
|
|||||||
debug "^4.3.4"
|
debug "^4.3.4"
|
||||||
tsutils "^3.21.0"
|
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":
|
"@typescript-eslint/types@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
|
||||||
integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
|
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":
|
"@typescript-eslint/typescript-estree@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
|
||||||
@ -4128,6 +4329,20 @@
|
|||||||
semver "^7.3.7"
|
semver "^7.3.7"
|
||||||
tsutils "^3.21.0"
|
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":
|
"@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.58.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
|
||||||
@ -4142,6 +4357,19 @@
|
|||||||
eslint-scope "^5.1.1"
|
eslint-scope "^5.1.1"
|
||||||
semver "^7.3.7"
|
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":
|
"@typescript-eslint/visitor-keys@5.62.0":
|
||||||
version "5.62.0"
|
version "5.62.0"
|
||||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
|
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
|
||||||
@ -4150,6 +4378,14 @@
|
|||||||
"@typescript-eslint/types" "5.62.0"
|
"@typescript-eslint/types" "5.62.0"
|
||||||
eslint-visitor-keys "^3.3.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":
|
"@ungap/structured-clone@^1.2.0":
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406"
|
||||||
@ -4567,6 +4803,11 @@ acorn@^8.11.3, acorn@^8.2.4, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0:
|
|||||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248"
|
||||||
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
|
integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==
|
||||||
|
|
||||||
|
acorn@^8.14.1:
|
||||||
|
version "8.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
|
||||||
|
integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
|
||||||
|
|
||||||
address@^1.0.1, address@^1.1.2:
|
address@^1.0.1, address@^1.1.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e"
|
resolved "https://registry.yarnpkg.com/address/-/address-1.2.2.tgz#2b5248dac5485a6390532c6a517fda2e3faac89e"
|
||||||
@ -5184,6 +5425,11 @@ big.js@^5.2.2:
|
|||||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||||
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
|
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
|
||||||
|
|
||||||
|
bignumber.js@^9.0.0:
|
||||||
|
version "9.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.0.tgz#bdba7e2a4c1a2eba08290e8dcad4f36393c92acd"
|
||||||
|
integrity sha512-EM7aMFTXbptt/wZdMlBv2t8IViwQL+h6SLHosp8Yf0dqJMTnY6iL32opnAB6kAdL0SZPuvcAzFr31o0c/R3/RA==
|
||||||
|
|
||||||
binary-extensions@^2.0.0:
|
binary-extensions@^2.0.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
|
||||||
@ -5804,6 +6050,16 @@ command-exists@^1.2.8:
|
|||||||
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
|
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69"
|
||||||
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
|
integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==
|
||||||
|
|
||||||
|
commander@13.1.0:
|
||||||
|
version "13.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-13.1.0.tgz#776167db68c78f38dcce1f9b8d7b8b9a488abf46"
|
||||||
|
integrity sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==
|
||||||
|
|
||||||
|
commander@^12.0.0:
|
||||||
|
version "12.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/commander/-/commander-12.1.0.tgz#01423b36f501259fdaac4d0e4d60c96c991585d3"
|
||||||
|
integrity sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==
|
||||||
|
|
||||||
commander@^2.20.0:
|
commander@^2.20.0:
|
||||||
version "2.20.3"
|
version "2.20.3"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||||
@ -6671,6 +6927,11 @@ dotenv@^10.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
|
||||||
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
|
||||||
|
|
||||||
|
dotenv@^16.0.0, dotenv@^16.5.0:
|
||||||
|
version "16.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.5.0.tgz#092b49f25f808f020050051d1ff258e404c78692"
|
||||||
|
integrity sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==
|
||||||
|
|
||||||
duplexer@^0.1.2:
|
duplexer@^0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
|
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6"
|
||||||
@ -7101,7 +7362,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"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz#c829eb06c0e6f484b3fbb85a97e57784f328c596"
|
||||||
integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==
|
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"
|
version "7.35.0"
|
||||||
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41"
|
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41"
|
||||||
integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==
|
integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==
|
||||||
@ -7914,6 +8175,18 @@ glob-to-regexp@^0.4.1:
|
|||||||
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
|
||||||
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
|
||||||
|
|
||||||
|
glob@11.0.1:
|
||||||
|
version "11.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/glob/-/glob-11.0.1.tgz#1c3aef9a59d680e611b53dcd24bb8639cef064d9"
|
||||||
|
integrity sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==
|
||||||
|
dependencies:
|
||||||
|
foreground-child "^3.1.0"
|
||||||
|
jackspeak "^4.0.1"
|
||||||
|
minimatch "^10.0.0"
|
||||||
|
minipass "^7.1.2"
|
||||||
|
package-json-from-dist "^1.0.0"
|
||||||
|
path-scurry "^2.0.0"
|
||||||
|
|
||||||
glob@^10.3.10:
|
glob@^10.3.10:
|
||||||
version "10.4.5"
|
version "10.4.5"
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
|
resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956"
|
||||||
@ -8323,6 +8596,11 @@ human-signals@^5.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
|
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28"
|
||||||
integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
|
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:
|
hyphenate-style-name@^1.0.3:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
|
resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz#1797bf50369588b47b72ca6d5e65374607cf4436"
|
||||||
@ -8369,7 +8647,7 @@ ieee754@^1.1.13, ieee754@^1.2.1:
|
|||||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||||
|
|
||||||
ignore@^5.2.0:
|
ignore@^5.2.0, ignore@^5.2.4:
|
||||||
version "5.3.1"
|
version "5.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
|
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef"
|
||||||
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
|
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==
|
||||||
@ -8915,6 +9193,13 @@ jackspeak@^3.1.2:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@pkgjs/parseargs" "^0.11.0"
|
"@pkgjs/parseargs" "^0.11.0"
|
||||||
|
|
||||||
|
jackspeak@^4.0.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-4.1.1.tgz#96876030f450502047fc7e8c7fcf8ce8124e43ae"
|
||||||
|
integrity sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==
|
||||||
|
dependencies:
|
||||||
|
"@isaacs/cliui" "^8.0.2"
|
||||||
|
|
||||||
jake@^10.8.5:
|
jake@^10.8.5:
|
||||||
version "10.9.2"
|
version "10.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
|
resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f"
|
||||||
@ -9626,6 +9911,13 @@ jsesc@~0.5.0:
|
|||||||
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
|
||||||
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
|
integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
|
||||||
|
|
||||||
|
json-bigint@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
|
||||||
|
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
|
||||||
|
dependencies:
|
||||||
|
bignumber.js "^9.0.0"
|
||||||
|
|
||||||
json-buffer@3.0.1:
|
json-buffer@3.0.1:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
|
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
|
||||||
@ -9995,6 +10287,11 @@ lru-cache@^10.2.0:
|
|||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
|
||||||
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
|
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
|
||||||
|
|
||||||
|
lru-cache@^11.0.0:
|
||||||
|
version "11.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.1.0.tgz#afafb060607108132dbc1cf8ae661afb69486117"
|
||||||
|
integrity sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==
|
||||||
|
|
||||||
lru-cache@^5.1.1:
|
lru-cache@^5.1.1:
|
||||||
version "5.1.1"
|
version "5.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
|
||||||
@ -10014,6 +10311,13 @@ magic-string@^0.25.0, magic-string@^0.25.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sourcemap-codec "^1.4.8"
|
sourcemap-codec "^1.4.8"
|
||||||
|
|
||||||
|
magic-string@^0.30.0:
|
||||||
|
version "0.30.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
|
||||||
|
integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.5.0"
|
||||||
|
|
||||||
make-dir@^2.0.0, make-dir@^2.1.0:
|
make-dir@^2.0.0, make-dir@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||||
@ -10374,6 +10678,20 @@ minimalistic-crypto-utils@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
|
||||||
integrity sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==
|
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@^10.0.0:
|
||||||
|
version "10.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
|
||||||
|
integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
|
||||||
|
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:
|
minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||||
version "3.1.2"
|
version "3.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
|
||||||
@ -11012,6 +11330,14 @@ path-scurry@^1.11.1:
|
|||||||
lru-cache "^10.2.0"
|
lru-cache "^10.2.0"
|
||||||
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
|
||||||
|
|
||||||
|
path-scurry@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-2.0.0.tgz#9f052289f23ad8bf9397a2a0425e7b8615c58580"
|
||||||
|
integrity sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^11.0.0"
|
||||||
|
minipass "^7.1.2"
|
||||||
|
|
||||||
path-to-regexp@0.1.7:
|
path-to-regexp@0.1.7:
|
||||||
version "0.1.7"
|
version "0.1.7"
|
||||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
|
||||||
@ -11043,6 +11369,11 @@ performance-now@^2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||||
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
|
integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==
|
||||||
|
|
||||||
|
picocolors@1.1.1:
|
||||||
|
version "1.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||||
|
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||||
|
|
||||||
picocolors@^0.2.1:
|
picocolors@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f"
|
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f"
|
||||||
@ -11058,6 +11389,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatc
|
|||||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
|
|
||||||
|
picomatch@^4.0.2:
|
||||||
|
version "4.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab"
|
||||||
|
integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==
|
||||||
|
|
||||||
pify@^2.3.0:
|
pify@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
@ -12731,6 +13067,13 @@ serialize-error@^2.1.0:
|
|||||||
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a"
|
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a"
|
||||||
integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==
|
integrity sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==
|
||||||
|
|
||||||
|
serialize-javascript@6.0.2, serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
|
||||||
|
version "6.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
|
||||||
|
integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
|
||||||
|
dependencies:
|
||||||
|
randombytes "^2.1.0"
|
||||||
|
|
||||||
serialize-javascript@^4.0.0:
|
serialize-javascript@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
|
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
|
||||||
@ -12738,13 +13081,6 @@ serialize-javascript@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
randombytes "^2.1.0"
|
randombytes "^2.1.0"
|
||||||
|
|
||||||
serialize-javascript@^6.0.0, serialize-javascript@^6.0.1:
|
|
||||||
version "6.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2"
|
|
||||||
integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==
|
|
||||||
dependencies:
|
|
||||||
randombytes "^2.1.0"
|
|
||||||
|
|
||||||
serve-index@^1.9.1:
|
serve-index@^1.9.1:
|
||||||
version "1.9.1"
|
version "1.9.1"
|
||||||
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
|
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
|
||||||
@ -13127,7 +13463,16 @@ string-natural-compare@^3.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
|
||||||
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@ -13230,7 +13575,7 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
@ -13244,6 +13589,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex "^4.1.0"
|
ansi-regex "^4.1.0"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
strip-ansi@^7.0.1:
|
strip-ansi@^7.0.1:
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
|
||||||
@ -13646,6 +13998,11 @@ tryer@^1.0.1:
|
|||||||
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
resolved "https://registry.yarnpkg.com/tryer/-/tryer-1.0.1.tgz#f2c85406800b9b0f74c9f7465b81eaad241252f8"
|
||||||
integrity sha512-c3zayb8/kWWpycWYg87P71E1S1ZL6b6IJxfb5fvsUgsf0S2MVGaDhDXXjDMpdCpfWXqptc+4mXwmiy1ypXqRAA==
|
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:
|
ts-interface-checker@^0.1.9:
|
||||||
version "0.1.13"
|
version "0.1.13"
|
||||||
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
|
resolved "https://registry.yarnpkg.com/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz#784fd3d679722bc103b1b4b8030bcddb5db2a699"
|
||||||
@ -13911,6 +14268,15 @@ unpipe@1.0.0, unpipe@~1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
|
||||||
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==
|
||||||
|
|
||||||
|
unplugin@^2.0.0:
|
||||||
|
version "2.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.3.5.tgz#c689d806e2a15c95aeb794f285356c6bcdea4a2e"
|
||||||
|
integrity sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==
|
||||||
|
dependencies:
|
||||||
|
acorn "^8.14.1"
|
||||||
|
picomatch "^4.0.2"
|
||||||
|
webpack-virtual-modules "^0.6.2"
|
||||||
|
|
||||||
unquote@~1.1.1:
|
unquote@~1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
|
resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544"
|
||||||
@ -14198,6 +14564,11 @@ webpack-sources@^3.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
||||||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||||
|
|
||||||
|
webpack-virtual-modules@^0.6.2:
|
||||||
|
version "0.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz#057faa9065c8acf48f24cb57ac0e77739ab9a7e8"
|
||||||
|
integrity sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==
|
||||||
|
|
||||||
webpack@^5.64.4:
|
webpack@^5.64.4:
|
||||||
version "5.93.0"
|
version "5.93.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5"
|
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5"
|
||||||
@ -14535,7 +14906,7 @@ workbox-window@6.6.1:
|
|||||||
"@types/trusted-types" "^2.0.2"
|
"@types/trusted-types" "^2.0.2"
|
||||||
workbox-core "6.6.1"
|
workbox-core "6.6.1"
|
||||||
|
|
||||||
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
|
"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
|
||||||
version "7.0.0"
|
version "7.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
@ -14553,6 +14924,15 @@ wrap-ansi@^6.2.0:
|
|||||||
string-width "^4.1.0"
|
string-width "^4.1.0"
|
||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
|
wrap-ansi@^7.0.0:
|
||||||
|
version "7.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
|
||||||
|
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
|
||||||
|
dependencies:
|
||||||
|
ansi-styles "^4.0.0"
|
||||||
|
string-width "^4.1.0"
|
||||||
|
strip-ansi "^6.0.0"
|
||||||
|
|
||||||
wrap-ansi@^8.1.0:
|
wrap-ansi@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"
|
||||||
|
Loading…
Reference in New Issue
Block a user