Compare commits

..

1 Commits

Author SHA1 Message Date
Adw8
162efe377c Import denom from registry-sdk 2024-08-01 12:07:05 +05:30
47 changed files with 1026 additions and 2254 deletions

View File

@ -2,4 +2,4 @@ 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-sapo.laconic.com REACT_APP_LACONICD_RPC_URL=https://laconicd.laconic.com

View File

@ -1,17 +0,0 @@
{
"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 }]
}
}

View File

@ -1 +0,0 @@
yarn lint

View File

@ -2,52 +2,52 @@
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( config.plugins.push(
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"], Buffer: ["buffer", "Buffer"],
})
)
config.module.rules.push({
test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/,
type: 'asset/resource'
}) })
)
config.module.rules.push({ config.resolve.fallback = {
test: /\.(jpg|png|woff|woff2|eot|ttf|svg)$/, crypto: require.resolve("crypto-browserify"),
type: 'asset/resource' stream: require.resolve("stream-browserify"),
}) http: require.resolve('stream-http'),
https: require.resolve('https-browserify'),
url: false
}
config.resolve.fallback = { config.resolve.alias['react-native$'] = require.resolve('react-native-web');
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;
return config;
}; };

View File

@ -1,6 +1,6 @@
{ {
"name": "web-wallet", "name": "web-wallet",
"version": "0.1.2", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@cerc-io/registry-sdk": "^0.2.5", "@cerc-io/registry-sdk": "^0.2.5",
@ -13,8 +13,6 @@
"@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",
@ -51,8 +49,6 @@
"start": "react-app-rewired start", "start": "react-app-rewired start",
"build": "react-app-rewired build", "build": "react-app-rewired build",
"test": "react-app-rewired test", "test": "react-app-rewired test",
"lint": "eslint .",
"prepare": "husky",
"eject": "react-scripts eject" "eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
@ -79,14 +75,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",
"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"
} }

View File

@ -1,18 +0,0 @@
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;

View File

@ -1,10 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,24 +1,21 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<head> <meta charset="utf-8" />
<meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" />
<meta name="theme-color" content="#000000" /> <meta
<meta name="description" content="Laconic Wallet Web App" /> name="description"
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> content="Laconic Wallet Web App"
<link rel="preconnect" href="https://fonts.googleapis.com" /> />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<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.
@ -27,58 +24,12 @@
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>
<style> </head>
#app { <body>
background-color: #0f0f0f; <noscript>You need to enable JavaScript to run this app.</noscript>
} <div id="root"></div>
<!--
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.
@ -88,6 +39,5 @@
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>

View File

@ -21,5 +21,5 @@
"start_url": ".", "start_url": ".",
"display": "standalone", "display": "standalone",
"theme_color": "#000000", "theme_color": "#000000",
"background_color": "#0f0f0f" "background_color": "#ffffff"
} }

View File

@ -1,45 +1,43 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"; import React, { useCallback, useEffect, useState } from 'react';
import { Button, Snackbar, Surface, Text } from "react-native-paper"; import { Button, Snackbar, 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 { 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 { COSMOS, EIP155 } 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";
const Stack = createStackNavigator<StackParamsList>(); const Stack = createStackNavigator<StackParamsList>();
const App = (): React.JSX.Element => { const App = (): React.JSX.Element => {
const navigation = useNavigation<StackNavigationProp<StackParamsList>>(); const navigation =
useNavigation<StackNavigationProp<StackParamsList>>();
const { web3wallet, setActiveSessions } = useWalletConnect(); const { web3wallet, setActiveSessions } = useWalletConnect();
const { accounts, setCurrentIndex } = useAccounts(); const { accounts, setCurrentIndex } = useAccounts();
@ -47,16 +45,16 @@ const App = (): React.JSX.Element => {
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;
} }
@ -76,11 +74,10 @@ 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 => networkData.networkId === selectedNetwork!.networkId,
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,
@ -101,13 +98,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({
@ -117,7 +114,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,
@ -125,7 +122,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),
@ -139,19 +136,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),
@ -161,7 +158,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,
@ -171,7 +168,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,
@ -180,7 +177,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,
@ -189,7 +186,7 @@ const App = (): React.JSX.Element => {
break; break;
default: default:
throw new Error("Invalid method"); throw new Error('Invalid method');
} }
}, },
[ [
@ -198,7 +195,7 @@ const App = (): React.JSX.Element => {
setSelectedNetwork, setSelectedNetwork,
setCurrentIndex, setCurrentIndex,
selectedNetwork, selectedNetwork,
web3wallet, web3wallet
], ],
); );
@ -208,20 +205,18 @@ 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);
}; };
}); });
const showWalletConnect = useMemo(() => accounts.length > 0, [accounts]);
return ( return (
<Surface style={styles.appSurface}> <>
<Stack.Navigator <Stack.Navigator
screenOptions={{ screenOptions={{
headerBackTitleVisible: true, headerBackTitleVisible: true,
@ -232,9 +227,7 @@ 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
header: () => ( headerTitle: () => <Text variant="titleLarge">Laconic Wallet</Text>,
<Header title="Wallet" showWalletConnect={showWalletConnect} />
),
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -242,7 +235,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
header: () => <Header title="Wallet" />, headerTitle: () => <Text variant="titleLarge">Sign Message</Text>,
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -250,7 +243,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
header: () => <Header title="Wallet" />, headerTitle: () => <Text variant="titleLarge">Sign Request</Text>,
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -261,11 +254,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={{
header: () => <Header title="Wallet" />, title: 'New session',
}} }}
/> />
<Stack.Screen <Stack.Screen
@ -278,9 +271,8 @@ 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>
), ),
@ -290,35 +282,28 @@ const App = (): React.JSX.Element => {
name="ApproveTransfer" name="ApproveTransfer"
component={ApproveTransfer} component={ApproveTransfer}
options={{ options={{
header: () => <Header title="Wallet" />, title: 'Approve transfer',
}} }}
/> />
<Stack.Screen <Stack.Screen
name="AddNetwork" name="AddNetwork"
component={AddNetwork} component={AddNetwork}
options={{ options={{
header: () => <Header title="Wallet" />, title: 'Add Network',
}} }}
/> />
<Stack.Screen <Stack.Screen
name="EditNetwork" name="EditNetwork"
component={EditNetwork} component={EditNetwork}
options={{ options={{
header: () => <Header title="Wallet" />, title: 'Edit Network',
}} }}
/> />
<Stack.Screen <Stack.Screen
name="ApproveTransaction" name="ApproveTransaction"
component={ApproveTransaction} component={ApproveTransaction}
options={{ options={{
header: () => <Header title="Wallet" />, title: 'Approve Transaction',
}}
/>
<Stack.Screen
name="WalletEmbed"
component={WalletEmbed}
options={{
header: () => <></>,
}} }}
/> />
</Stack.Navigator> </Stack.Navigator>
@ -332,11 +317,10 @@ 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> </>
); );
}; };

View File

@ -1,7 +1,9 @@
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 { Box, Stack, Typography } from "@mui/material"; import styles from '../styles/stylesheet';
interface AccountDetailsProps { interface AccountDetailsProps {
account: Account | undefined; account: Account | undefined;
@ -9,28 +11,20 @@ interface AccountDetailsProps {
const AccountDetails: React.FC<AccountDetailsProps> = ({ account }) => { const AccountDetails: React.FC<AccountDetailsProps> = ({ account }) => {
return ( return (
<Box sx={{ marginY: 4 }}> <View style={styles.accountContainer}>
<Stack spacing={1}> <Text variant="bodyLarge" selectable={true}>
<Stack flexDirection="row"> <Text style={styles.highlight}>Address: </Text>
<Typography color="secondary" sx={{ mr: 1 }}> {account?.address}
Address: </Text>
</Typography> <Text variant="bodyLarge" selectable={true}>
<Typography color="text.primary">{account?.address}</Typography> <Text style={styles.highlight}>Public Key: </Text>
</Stack> {account?.pubKey}
</Stack> </Text>
<Stack flexDirection="row"> <Text variant="bodyLarge">
<Typography color="secondary" sx={{ mr: 1 }}> <Text style={styles.highlight}>HD Path: </Text>
Public Key: {account?.hdPath}
</Typography> </Text>
<Typography color="text.primary">{account?.pubKey}</Typography> </View>
</Stack>
<Stack flexDirection="row">
<Typography color="secondary" sx={{ mr: 1 }}>
HD Path:
</Typography>
<Typography color="text.primary">{account?.hdPath}</Typography>
</Stack>
</Box>
); );
}; };

View File

@ -1,30 +1,22 @@
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 { List } from "react-native-paper"; import { Button, List, Text, useTheme } 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 HDPathDialog from "./HDPathDialog"; import styles from '../styles/stylesheet';
import AccountDetails from "./AccountDetails"; import HDPathDialog from './HDPathDialog';
import { useAccounts } from "../context/AccountsContext"; import AccountDetails from './AccountDetails';
import { useWalletConnect } from "../context/WalletConnectContext"; import { useAccounts } from '../context/AccountsContext';
import { useNetworks } from "../context/NetworksContext"; import { useWalletConnect } from '../context/WalletConnectContext';
import ConfirmDialog from "./ConfirmDialog"; import { useNetworks } from '../context/NetworksContext';
import { getNamespaces } from "../utils/wallet-connect/helpers"; import ConfirmDialog from './ConfirmDialog';
import ShowPKDialog from "./ShowPKDialog"; import { getNamespaces } from '../utils/wallet-connect/helpers';
import { setInternetCredentials } from "../utils/key-store"; import ShowPKDialog from './ShowPKDialog';
import { import { setInternetCredentials } from '../utils/key-store';
Accordion,
AccordionSummary,
Button,
Link,
Stack,
} from "@mui/material";
import { LoadingButton } from "@mui/lab";
const Accounts = () => { const Accounts = () => {
const navigation = const navigation =
@ -35,14 +27,16 @@ 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);
@ -66,6 +60,7 @@ const Accounts = () => {
networksData, networksData,
selectedNetwork!, selectedNetwork!,
accounts, accounts,
currentIndex,
); );
if (!updatedNamespaces) { if (!updatedNamespaces) {
@ -94,7 +89,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}`}
@ -107,12 +102,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),
); );
@ -131,74 +126,94 @@ const Accounts = () => {
updateAccounts={updateAccounts} updateAccounts={updateAccounts}
pathCode={pathCode} pathCode={pathCode}
/> />
<Accordion expanded={expanded} onClick={handlePress}> <List.Accordion
<AccordionSummary title={`Account ${currentIndex + 1}`}
expandIcon={<ExpandMoreIcon />} expanded={expanded}
>{`Account ${currentIndex + 1}`}</AccordionSummary> onPress={handlePress}>
{renderAccountItems()} {renderAccountItems()}
</Accordion> </List.Accordion>
<Stack direction="row" spacing={3} sx={{ mt: 4 }}>
<LoadingButton
variant="contained"
onClick={addAccountHandler}
loading={isAccountCreating}
disabled={isAccountCreating}
>
{isAccountCreating ? "Adding" : "Add Account"}
</LoadingButton>
<View style={styles.addAccountButton}>
<Button <Button
variant="contained" mode="contained"
onClick={() => { onPress={addAccountHandler}
loading={isAccountCreating}
disabled={isAccountCreating}>
{isAccountCreating ? 'Adding' : 'Add Account'}
</Button>
</View>
<View style={styles.addAccountButton}>
<Button
mode="contained"
onPress={() => {
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>
</Stack> </View>
<AccountDetails account={accounts[currentIndex]} /> <AccountDetails account={accounts[currentIndex]} />
<Stack direction="row" spacing={4}> <View style={styles.linkContainer}>
<TouchableOpacity <View style={styles.signLink}>
onPress={() => {
navigation.navigate("SignMessage", {
selectedNamespace: selectedNetwork!.namespace,
selectedChainId: selectedNetwork!.chainId,
accountInfo: accounts[currentIndex],
});
}}
>
<Link>Sign Message</Link>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
navigation.navigate("AddNetwork");
}}
>
<Link>Add Network</Link>
</TouchableOpacity>
<TouchableOpacity
onPress={() => {
navigation.navigate("EditNetwork", {
selectedNetwork: selectedNetwork!,
});
}}
>
<Link>Edit Network</Link>
</TouchableOpacity>
{!selectedNetwork!.isDefault && (
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
setDeleteNetworkDialog(true); navigation.navigate('SignMessage', {
}} selectedNamespace: selectedNetwork!.namespace,
> selectedChainId: selectedNetwork!.chainId,
<Link>Delete Network</Link> accountInfo: accounts[currentIndex],
});
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Sign Message
</Text>
</TouchableOpacity> </TouchableOpacity>
</View>
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
navigation.navigate('AddNetwork');
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Add Network
</Text>
</TouchableOpacity>
</View>
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
navigation.navigate('EditNetwork', {
selectedNetwork: selectedNetwork!,
});
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Edit Network
</Text>
</TouchableOpacity>
</View>
{!selectedNetwork!.isDefault && (
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
setDeleteNetworkDialog(true);
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Delete Network
</Text>
</TouchableOpacity>
</View>
)} )}
<ConfirmDialog <ConfirmDialog
title="Delete Network" title="Delete Network"
@ -207,7 +222,7 @@ const Accounts = () => {
onConfirm={handleRemove} onConfirm={handleRemove}
/> />
<ShowPKDialog /> <ShowPKDialog />
</Stack> </View>
</View> </View>
</View> </View>
); );

View File

@ -1,7 +1,6 @@
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,
@ -11,15 +10,15 @@ const ConfirmDialog = ({
}: ResetDialogProps) => { }: ResetDialogProps) => {
return ( return (
<Dialog open={visible} onClose={hideDialog}> <Dialog open={visible} onClose={hideDialog}>
<DialogTitle style={{...styles.resetDialogTitle}} >{title}</DialogTitle> <DialogTitle>{title}</DialogTitle>
<DialogContent style={{...styles.resetDialogContent}}> <DialogContent>
<Typography>Are you sure?</Typography> <Typography variant="body1">Are you sure?</Typography>
</DialogContent> </DialogContent>
<DialogActions style={{...styles.resetDialogActionRow}}> <DialogActions>
<Button style={{...styles.buttonRed, ...styles.button}} color="error" onClick={onConfirm}> <Button color="error" onClick={onConfirm}>
Yes Yes
</Button> </Button>
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideDialog}>No</Button> <Button onClick={hideDialog}>No</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );

View File

@ -1,21 +0,0 @@
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>
);

View File

@ -1,8 +1,9 @@
import React from "react"; import { View } from 'react-native';
import { View } from "react-native"; import React from 'react';
import { LoadingButton } from "@mui/lab"; import { Button } from 'react-native-paper';
import { CreateWalletProps } from "../types"; import { CreateWalletProps } from '../types';
import styles from '../styles/stylesheet';
const CreateWallet = ({ const CreateWallet = ({
isWalletCreating, isWalletCreating,
@ -10,15 +11,14 @@ const CreateWallet = ({
}: CreateWalletProps) => { }: CreateWalletProps) => {
return ( return (
<View> <View>
<View> <View style={styles.createWalletContainer}>
<LoadingButton <Button
variant="contained" mode="contained"
loading={isWalletCreating} loading={isWalletCreating}
disabled={isWalletCreating} disabled={isWalletCreating}
onClick={createWalletHandler} onPress={createWalletHandler}>
> {isWalletCreating ? 'Creating' : 'Create Wallet'}
{isWalletCreating ? "Creating" : "CREATE WALLET"} </Button>
</LoadingButton>
</View> </View>
</View> </View>
); );

View File

@ -1,44 +1,28 @@
import React from "react"; import React from 'react';
import { import { Dialog, DialogActions, DialogContent, DialogTitle, Button, Typography } from '@mui/material';
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 copyMnemonic = () => { const DialogComponent = ({ visible, hideDialog, contentText }: CustomDialogProps) => {
navigator.clipboard.writeText(contentText); const words = contentText.split(' ');
};
return ( return (
<Dialog open={visible} onClose={hideDialog}> <Dialog open={visible} onClose={hideDialog}>
<DialogTitle style={{...styles.mnemonicTitle}}>Mnemonic</DialogTitle> <DialogTitle>Mnemonic</DialogTitle>
<DialogContent style={{...styles.mnemonicContainer}}> <DialogContent>
<Typography component="div"> <Typography variant="h6" component="div" style={{ color: 'rgba(0, 0, 0, 0.87)' }}>
Your mnemonic provides full access to your wallet and funds.<br/> Your mnemonic provides full access to your wallet and funds. Make sure to note it down.
Make sure to note it down.
</Typography> </Typography>
<Typography component="div" style={{ ...styles.mnemonicDialogWarning }}> <Typography variant="h6" component="div" style={{ ...styles.dialogWarning }}>
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 style={{...styles.mnemonicButtonRow}}> <DialogActions>
<Button style={{...styles.mnemonicButton}} onClick={copyMnemonic}>Copy</Button> <Button onClick={hideDialog}>Done</Button>
<Button style={{...styles.mnemonicButton}} onClick={hideDialog}>Done</Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
); );

View File

@ -7,7 +7,7 @@ import { GridViewProps } from '../types';
const GridView = ({ words }: GridViewProps) => { const GridView = ({ words }: GridViewProps) => {
return ( return (
<View style={styles.mnemonicGridContainer}> <View style={styles.gridContainer}>
{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>

View File

@ -1,12 +1,11 @@
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 { TextInput } from "react-native-paper"; import { Button, 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,
@ -22,19 +21,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, ''),
}); });
}; };
@ -51,7 +50,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);
} }
@ -64,15 +63,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}
/> />
@ -80,20 +79,19 @@ 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}>
<LoadingButton <Button
variant="contained" mode="contained"
onClick={createFromHDPathHandler} onPress={createFromHDPathHandler}
loading={isAccountCreating} loading={isAccountCreating}
disabled={isAccountCreating} disabled={isAccountCreating}>
> {isAccountCreating ? 'Adding' : 'Add Account'}
{isAccountCreating ? "Adding" : "Add Account"} </Button>
</LoadingButton>
</View> </View>
</ScrollView> </ScrollView>
); );

View File

@ -1,113 +0,0 @@
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>
);
};

View File

@ -1,84 +0,0 @@
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;

View File

@ -1,34 +0,0 @@
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>
);
};

View File

@ -1,51 +0,0 @@
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 };

View File

@ -1,12 +1,10 @@
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();
@ -21,19 +19,18 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
return ( return (
<View style={styles.networkDropdown}> <View style={styles.networkDropdown}>
<Accordion expanded={expanded} onClick={() => setExpanded(!expanded)}> <List.Accordion
<AccordionSummary expandIcon={<ExpandMoreIcon />}> title={selectedNetwork!.networkName}
{selectedNetwork!.networkName} expanded={expanded}
</AccordionSummary> onPress={() => setExpanded(!expanded)}>
{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)}
/> />
))} ))}
</Accordion> </List.Accordion>
</View> </View>
); );
}; };

View File

@ -19,7 +19,7 @@ const PairingModal = ({
setModalVisible, setModalVisible,
setToastVisible, setToastVisible,
}: PairingModalProps) => { }: PairingModalProps) => {
const { accounts } = useAccounts(); const { accounts, currentIndex } = useAccounts();
const { selectedNetwork, networksData } = useNetworks(); const { selectedNetwork, networksData } = useNetworks();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [chainError, setChainError] = useState(''); const [chainError, setChainError] = useState('');
@ -101,6 +101,7 @@ const PairingModal = ({
networksData, networksData,
selectedNetwork!, selectedNetwork!,
accounts, accounts,
currentIndex,
); );
setSupportedNamespaces(nameSpaces); setSupportedNamespaces(nameSpaces);
} catch (err) { } catch (err) {
@ -126,6 +127,7 @@ const PairingModal = ({
networksData, networksData,
selectedNetwork, selectedNetwork,
accounts, accounts,
currentIndex,
setCurrentProposal, setCurrentProposal,
setModalVisible, setModalVisible,
web3wallet web3wallet

View File

@ -1,15 +1,16 @@
import React, { useState } from "react"; import React, { useState } from 'react';
import { TouchableOpacity, View } from "react-native"; import { TouchableOpacity, View } from 'react-native';
import { Button, Link, Typography } from "@mui/material"; import { Button, 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();
@ -18,6 +19,8 @@ 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}`,
@ -38,9 +41,12 @@ const ShowPKDialog = () => {
<TouchableOpacity <TouchableOpacity
onPress={() => { onPress={() => {
setShowPKDialog(true); setShowPKDialog(true);
}} }}>
> <Text
<Link>Show Private Key</Link> variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Show Private Key
</Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<View> <View>
@ -60,8 +66,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}
@ -70,19 +76,24 @@ const ShowPKDialog = () => {
)} )}
<View> <View>
<Typography variant="body1" style={styles.dialogWarning}> <Typography variant="body1" style={styles.dialogWarning}>
Warning: Never disclose this key. Anyone with your private keys can steal <Typography component="span">
any assets held in your account. Warning:
</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 style={{...styles.buttonRed, ...styles.button}} onClick={handleShowPrivateKey}>Yes</Button> <Button onClick={handleShowPrivateKey} color="error">
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideShowPKDialog}>No</Button> Yes
</Button>
<Button onClick={hideShowPKDialog}>No</Button>
</> </>
) : ( ) : (
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideShowPKDialog}>Ok</Button> <Button onClick={hideShowPKDialog}>Ok</Button>
)} )}
</DialogActions> </DialogActions>
</Dialog> </Dialog>

View File

@ -7,12 +7,12 @@ const AccountsContext = createContext<{
setAccounts: (account: Account[]) => void; setAccounts: (account: Account[]) => void;
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);

View File

@ -13,14 +13,14 @@ 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);

View File

@ -1,141 +1,59 @@
import React from "react"; import React from 'react';
import ReactDOM from "react-dom/client"; import ReactDOM from 'react-dom/client';
import { import { PaperProvider, MD3LightTheme as DefaultTheme, } from 'react-native-paper';
PaperProvider, import { NavigationContainer } from '@react-navigation/native';
MD3DarkTheme as DefaultTheme, import { Platform } from 'react-native';
} from "react-native-paper"; import { Buffer } from 'buffer';
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: true, dark: false,
}; };
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(
<div id="app"> <PaperProvider theme={theme}>
<PaperProvider theme={theme}> <NetworksProvider>
<NetworksProvider> <AccountsProvider>
<AccountsProvider> <WalletConnectProvider>
<WalletConnectProvider> <NavigationContainer
<NavigationContainer linking={linking}
linking={linking} documentTitle={{
documentTitle={{ formatter: () =>
formatter: () => `Laconic | Wallet`, `Laconic Wallet`,
}} }}
theme={navigationTheme} >
> <React.Fragment>
<React.Fragment> {Platform.OS === 'web' ? (
{Platform.OS === "web" ? ( <style type="text/css">{`
<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}
<ThemeProvider theme={muiTheme}> <App />
<App /> </React.Fragment>
</ThemeProvider> </NavigationContainer>
</React.Fragment> </WalletConnectProvider>
</NavigationContainer> </AccountsProvider>
</WalletConnectProvider> </NetworksProvider>
</AccountsProvider> </PaperProvider>
</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

View File

@ -1,20 +1,21 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from 'react';
import { useForm, Controller, useWatch, FieldErrors } from "react-hook-form"; import { View } from 'react-native';
import { TextInput, HelperText } from "react-native-paper"; import { useForm, Controller, useWatch, FieldErrors } from 'react-hook-form';
import { TextInput, Button, HelperText } from 'react-native-paper';
import { HDNode } from "ethers/lib/utils"; import { HDNode } from 'ethers/lib/utils';
import { chains } from "chain-registry"; import { chains } from 'chain-registry';
import { useDebouncedCallback } from "use-debounce"; import { useDebouncedCallback } from 'use-debounce';
import { z } from "zod"; import { z } from 'zod';
import { NativeStackNavigationProp } from "@react-navigation/native-stack"; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
import { useNavigation } from "@react-navigation/native"; import { useNavigation } from '@react-navigation/native';
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from '@hookform/resolvers/zod';
import { StackParamsList } from "../types"; import { StackParamsList } from '../types';
import { SelectNetworkType } from "../components/SelectNetworkType"; import { SelectNetworkType } from '../components/SelectNetworkType';
import { storeNetworkData } from "../utils/accounts"; import { storeNetworkData } from '../utils/accounts';
import { useNetworks } from "../context/NetworksContext"; import { useNetworks } from '../context/NetworksContext';
import { import {
COSMOS, COSMOS,
EIP155, EIP155,
@ -22,16 +23,14 @@ import {
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 { getCosmosAccounts } from '../utils/accounts';
import ETH_CHAINS from "../assets/ethereum-chains.json"; import ETH_CHAINS from '../assets/ethereum-chains.json';
import { import {
getInternetCredentials, getInternetCredentials,
setInternetCredentials, setInternetCredentials,
} from "../utils/key-store"; } from '../utils/key-store';
import { Divider, Grid } from "@mui/material"; import styles from '../styles/stylesheet';
import { LoadingButton } from "@mui/lab";
import { Layout } from "../components/Layout";
const ethNetworkDataSchema = z.object({ const ethNetworkDataSchema = z.object({
chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
@ -40,7 +39,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 })
@ -55,7 +54,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 })
@ -86,13 +85,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) => {
@ -102,16 +101,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(
@ -120,14 +119,14 @@ 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(process.env.DEFAULT_GAS_PRICE),
@ -143,11 +142,11 @@ const AddNetwork = () => {
isDefault: false, isDefault: false,
}; };
const mnemonicServer = await getInternetCredentials("mnemonicServer"); const mnemonicServer = await getInternetCredentials('mnemonicServer');
const mnemonic = mnemonicServer; const mnemonic = mnemonicServer;
if (!mnemonic) { if (!mnemonic) {
throw new Error("Mnemonic not found"); throw new Error('Mnemonic not found');
} }
const hdNode = HDNode.fromMnemonic(mnemonic); const hdNode = HDNode.fromMnemonic(mnemonic);
@ -173,7 +172,7 @@ const AddNetwork = () => {
break; break;
default: default:
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}`;
@ -184,22 +183,22 @@ const AddNetwork = () => {
await Promise.all([ await Promise.all([
setInternetCredentials( setInternetCredentials(
`accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`, `accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`,
"_", '_',
accountInfo, accountInfo,
), ),
setInternetCredentials( setInternetCredentials(
`addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`, `addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`,
"_", '_',
"1", '1',
), ),
setInternetCredentials( setInternetCredentials(
`accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`, `accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`,
"_", '_',
"0", '0',
), ),
]); ]);
navigation.navigate("Home"); navigation.navigate('Home');
}, },
[navigation, namespace, setNetworksData], [navigation, namespace, setNetworksData],
); );
@ -213,237 +212,208 @@ const AddNetwork = () => {
}, [namespace, reset]); }, [namespace, reset]);
return ( return (
<Layout title="Add Network"> <View style={styles.appContainer}>
<SelectNetworkType updateNetworkType={updateNetworkType} /> <SelectNetworkType updateNetworkType={updateNetworkType} />
<Divider flexItem sx={{ my: 4 }} />
<Grid container spacing={2} sx={{ px: 1 }}> <Controller
<Grid item xs={6}> control={control}
name="chainId"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Chain ID"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.chainId?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
defaultValue=""
name="networkName"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.networkName?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
name="rpcUrl"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
defaultValue=""
name="blockExplorerUrl"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">
{errors.blockExplorerUrl?.message}
</HelperText>
</>
)}
/>
<Controller
control={control}
name="coinType"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Coin Type"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">{errors.coinType?.message}</HelperText>
</>
)}
/>
{namespace === EIP155 ? (
<Controller
control={control}
name="currencySymbol"
defaultValue=""
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Currency Symbol"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">
{
(errors as FieldErrors<z.infer<typeof ethNetworkDataSchema>>)
.currencySymbol?.message
}
</HelperText>
</>
)}
/>
) : (
<>
<Controller <Controller
control={control} control={control}
name="chainId" name="nativeDenom"
defaultValue="" defaultValue=""
render={({ field: { onChange, onBlur, value } }) => ( render={({ field: { onChange, onBlur, value } }) => (
<> <>
<TextInput <TextInput
mode="outlined" mode="outlined"
value={value} value={value}
label="Chain ID" label="Native Denom"
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="rpcUrl" name="addressPrefix"
defaultValue="" defaultValue=""
render={({ field: { onChange, onBlur, value } }) => ( render={({ field: { onChange, onBlur, value } }) => (
<> <>
<TextInput <TextInput
mode="outlined" mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value} value={value}
onChangeText={(textValue) => onChange(textValue)} label="Address Prefix"
/>
<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} onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)} 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="coinType" name="gasPrice"
defaultValue="" defaultValue=""
render={({ field: { onChange, onBlur, value } }) => ( render={({ field: { onChange, onBlur, value } }) => (
<> <>
<TextInput <TextInput
mode="outlined" mode="outlined"
value={value} value={value}
label="Coin Type" label="Gas Price"
onBlur={onBlur} onBlur={onBlur}
onChangeText={onChange} onChangeText={onChange}
/> />
<HelperText type="error">{errors.coinType?.message}</HelperText> <HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworkDataSchema>
>
).gasPrice?.message
}
</HelperText>
</> </>
)} )}
/> />
</Grid> </>
{namespace === EIP155 ? ( )}
<Grid item xs={12}> <Button
<Controller mode="contained"
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}
onClick={handleSubmit(submit)} style={styles.networksButton}
sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }} onPress={handleSubmit(submit)}>
> {isSubmitting ? 'Adding' : 'Submit'}
{isSubmitting ? "Adding" : "Submit"} </Button>
</LoadingButton> </View>
</Layout>
); );
}; };

View File

@ -1,24 +1,22 @@
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from 'react';
import { View } from "react-native"; import { View } from 'react-native';
import { Text, TextInput } from "react-native-paper"; import { Button, 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) {
@ -26,12 +24,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 (
<Layout title="Add Session"> <View style={styles.appContainer}>
<View style={styles.inputContainer}> <View style={styles.inputContainer}>
<Text variant="titleMedium">Enter WalletConnect URI</Text> <Text variant="titleMedium">Enter WalletConnect URI</Text>
<TextInput <TextInput
@ -43,13 +41,13 @@ const AddSession = () => {
style={styles.walletConnectUriText} style={styles.walletConnectUriText}
/> />
<Box sx={{ mt: 2 }}> <View style={styles.signButton}>
<Button variant="contained" onClick={pair}> <Button mode="contained" onPress={pair}>
Pair Session Pair Session
</Button> </Button>
</Box> </View>
</View> </View>
</Layout> </View>
); );
}; };
export default AddSession; export default AddSession;

View File

@ -1,14 +1,13 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, 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, EncodeObject } from '@cosmjs/proto-signing'; import { DirectSecp256k1Wallet } 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';
@ -41,6 +40,7 @@ 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,20 +67,6 @@ 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;
@ -152,7 +138,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
return; return;
} }
const gasEstimation = await cosmosStargateClient!.simulate( const gasEstimation = await cosmosStargateClient!.simulate(
signer, transactionMessage.value.participant!,
[transactionMessage], [transactionMessage],
MEMO, MEMO,
); );
@ -174,7 +160,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
} }
}; };
getCosmosGas(); getCosmosGas();
}, [cosmosStargateClient, transactionMessage, requestEventId, topic, web3wallet, signer]); }, [cosmosStargateClient, transactionMessage, requestEventId, topic, web3wallet]);
useEffect(() => { useEffect(() => {
const gasPrice = GasPrice.fromString( const gasPrice = GasPrice.fromString(
@ -253,13 +239,6 @@ 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}>
@ -285,7 +264,7 @@ const ApproveTransaction = ({ route }: ApproveTransactionProps) => {
</Text> </Text>
<View style={styles.messageBody}> <View style={styles.messageBody}>
<Text variant="bodyLarge"> <Text variant="bodyLarge">
{JSON.stringify(transactionMessage, replacer, 2)} {JSON.stringify(transactionMessage, null, 2)}
</Text> </Text>
</View> </View>
<> <>

View File

@ -1,28 +1,27 @@
import React, { useCallback } from "react"; import React, { useCallback } from 'react';
import { useForm, Controller, FieldErrors } from "react-hook-form"; import { View } from 'react-native';
import { TextInput, HelperText } from "react-native-paper"; import { useForm, Controller, FieldErrors } from 'react-hook-form';
import { z } from "zod"; import { TextInput, Button, HelperText, Text } from 'react-native-paper';
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 { retrieveNetworksData } from "../utils/accounts"; import styles from '../styles/stylesheet';
import { useNetworks } from "../context/NetworksContext"; import { retrieveNetworksData } from '../utils/accounts';
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
@ -32,7 +31,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({
@ -42,14 +41,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();
@ -68,7 +67,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),
}); });
@ -78,134 +77,121 @@ 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 (
<Layout title="Edit Network"> <View style={styles.appContainer}>
<Typography fontSize="1.5rem" fontWeight={500}> <View>
Edit {networkData?.networkName} details <Text style={styles.subHeading}>
</Typography> Edit {networkData?.networkName} details
<Divider flexItem sx={{ my: 4 }} /> </Text>
<Grid container spacing={2}> </View>
<Grid item xs={6}> <Controller
<Controller control={control}
control={control} defaultValue={networkData.networkName}
defaultValue={networkData.networkName} name="networkName"
name="networkName" render={({ field: { onChange, onBlur, value } }) => (
render={({ field: { onChange, onBlur, value } }) => ( <>
<> <TextInput
<TextInput mode="outlined"
mode="outlined" label="Network Name"
label="Network Name" value={value}
value={value} onBlur={onBlur}
onBlur={onBlur} onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{errors.networkName?.message}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="rpcUrl"
defaultValue={networkData.rpcUrl}
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={isCosmos ? 6 : 12}>
<Controller
control={control}
defaultValue={networkData.blockExplorerUrl}
name="blockExplorerUrl"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{errors.blockExplorerUrl?.message}
</HelperText>
</>
)}
/>
</Grid>
{isCosmos && (
<Grid item xs={6}>
<Controller
control={control}
name="gasPrice"
defaultValue={networkData.gasPrice}
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Gas Price"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworksFormDataSchema>
>
).gasPrice?.message
}
</HelperText>
</>
)}
/> />
</Grid> <HelperText type="error">{errors.networkName?.message}</HelperText>
</>
)} )}
</Grid> />
<LoadingButton <Controller
variant="contained" control={control}
name="rpcUrl"
defaultValue={networkData.rpcUrl}
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
<Controller
control={control}
defaultValue={networkData.blockExplorerUrl}
name="blockExplorerUrl"
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
/>
<HelperText type="error">
{errors.blockExplorerUrl?.message}
</HelperText>
</>
)}
/>
{networkData.namespace === COSMOS && (
<Controller
control={control}
name="gasPrice"
defaultValue={networkData.gasPrice}
render={({ field: { onChange, onBlur, value } }) => (
<>
<TextInput
mode="outlined"
value={value}
label="Gas Price"
onBlur={onBlur}
onChangeText={onChange}
/>
<HelperText type="error">
{
(
errors as FieldErrors<
z.infer<typeof cosmosNetworksFormDataSchema>
>
).gasPrice?.message
}
</HelperText>
</>
)}
/>
)}
<Button
mode="contained"
loading={isSubmitting} loading={isSubmitting}
disabled={isSubmitting} disabled={isSubmitting}
onClick={handleSubmit(submit)} style={styles.networksButton}
sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }} onPress={handleSubmit(submit)}>
> {isSubmitting ? 'Adding' : 'Submit'}
{isSubmitting ? "Adding" : "Submit"} </Button>
</LoadingButton> </View>
</Layout>
); );
}; };

View File

@ -1,43 +1,66 @@
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from 'react';
import { View, ActivityIndicator } from "react-native"; import { View, ActivityIndicator, Image } from 'react-native';
import { Text } from "react-native-paper"; import { Button, Text } from 'react-native-paper';
import { Button, Divider, Portal, Snackbar } from "@mui/material";
import { getSdkError } from "@walletconnect/utils"; import { NativeStackNavigationProp } from '@react-navigation/native-stack';
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 { NetworkDropdown } from "../components/NetworkDropdown"; import { DialogComponent } from '../components/Dialog';
import Accounts from "../components/Accounts"; import { NetworkDropdown } from '../components/NetworkDropdown';
import CreateWallet from "../components/CreateWallet"; import Accounts from '../components/Accounts';
import ConfirmDialog from "../components/ConfirmDialog"; import CreateWallet from '../components/CreateWallet';
import styles from "../styles/stylesheet"; import ConfirmDialog from '../components/ConfirmDialog';
import { useAccounts } from "../context/AccountsContext"; import styles from '../styles/stylesheet';
import { useWalletConnect } from "../context/WalletConnectContext"; import { useAccounts } from '../context/AccountsContext';
import { NetworksDataState } from "../types"; import { useWalletConnect } from '../context/WalletConnectContext';
import { useNetworks } from "../context/NetworksContext"; import { NetworksDataState, StackParamsList } from '../types';
import ImportWalletDialog from "../components/ImportWalletDialog"; import { useNetworks } from '../context/NetworksContext';
import { MnemonicDialog } from "../components/MnemonicDialog";
import { Container } from "../components/Container"; const WCLogo = () => {
import { IS_IMPORT_WALLET_ENABLED } from "../utils/constants"; return (
<Image
style={styles.walletConnectLogo}
source={require('../assets/WalletConnect-Icon-Blueberry.png')}
/>
);
};
const HomeScreen = () => { const HomeScreen = () => {
const { setAccounts, setCurrentIndex } = useAccounts(); const { accounts, 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 [importWalletDialog, setImportWalletDialog] = useState<boolean>(false); const [walletDialog, setWalletDialog] = 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 hideMnemonicDialog = () => setMnemonicDialog(false); const hideWalletDialog = () => setWalletDialog(false);
const hideResetDialog = () => setResetWalletDialog(false); const hideResetDialog = () => setResetWalletDialog(false);
const fetchAccounts = useCallback(async () => { const fetchAccounts = useCallback(async () => {
@ -60,27 +83,12 @@ const HomeScreen = () => {
const mnemonic = await createWallet(networksData); const mnemonic = await createWallet(networksData);
if (mnemonic) { if (mnemonic) {
fetchAccounts(); fetchAccounts();
setMnemonicDialog(true); setWalletDialog(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);
@ -91,10 +99,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({});
@ -106,7 +114,7 @@ const HomeScreen = () => {
setCurrentIndex, setCurrentIndex,
setNetworksData, setNetworksData,
setSelectedNetwork, setSelectedNetwork,
web3wallet, web3wallet
]); ]);
const updateNetwork = (networkData: NetworksDataState) => { const updateNetwork = (networkData: NetworksDataState) => {
@ -120,74 +128,46 @@ const HomeScreen = () => {
return ( return (
<View style={styles.appContainer}> <View style={styles.appContainer}>
<Container> {!isAccountsFetched ? (
{!isAccountsFetched ? ( <View style={styles.spinnerContainer}>
<View style={styles.spinnerContainer}> <Text style={styles.LoadingText}>Loading...</Text>
<Text style={styles.LoadingText}>Loading...</Text> <ActivityIndicator size="large" color="#0000ff" />
<ActivityIndicator size="large" color="#0000ff" /> </View>
) : isWalletCreated && selectedNetwork ? (
<>
<NetworkDropdown updateNetwork={updateNetwork} />
<View style={styles.accountComponent}>
<Accounts />
</View> </View>
) : isWalletCreated && selectedNetwork ? ( <View style={styles.resetContainer}>
<>
<NetworkDropdown updateNetwork={updateNetwork} />
<View style={styles.accountComponent}>
<Accounts />
</View>
<Divider flexItem sx={{ my: 4 }} />
<Button <Button
variant="contained" style={styles.resetButton}
onClick={() => { mode="contained"
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 )}
visible={mnemonicDialog} <DialogComponent
hideDialog={hideMnemonicDialog} visible={walletDialog}
contentText={phrase} hideDialog={hideWalletDialog}
/> contentText={phrase}
<ConfirmDialog />
title="Reset wallet" <ConfirmDialog
visible={resetWalletDialog} title="Reset wallet"
hideDialog={hideResetDialog} visible={resetWalletDialog}
onConfirm={confirmResetWallet} hideDialog={hideResetDialog}
/> onConfirm={confirmResetWallet}
</Container> />
<Portal>
<Snackbar
open={toastVisible}
autoHideDuration={3000}
message={invalidMnemonicError}
onClose={() => setToastVisible(false)}
anchorOrigin={{ horizontal: "center", vertical: "bottom" }}
ContentProps={{ style: { backgroundColor: "red", color: "white" } }}
/>
</Portal>
</View> </View>
); );
}; };

View File

@ -1,22 +1,22 @@
import React, { useState } from "react"; import React, { useState } from 'react';
import { Text, TextInput } from "react-native-paper"; import { View } from 'react-native';
import { Button, Divider, Stack } from "@mui/material"; import { Button, Text, TextInput } from 'react-native-paper';
import { NativeStackScreenProps } from "@react-navigation/native-stack"; import { NativeStackScreenProps } from '@react-navigation/native-stack';
import { StackParamsList } from "../types"; import { StackParamsList } from '../types';
import { signMessage } from "../utils/sign-message"; import styles from '../styles/stylesheet';
import AccountDetails from "../components/AccountDetails"; import { signMessage } from '../utils/sign-message';
import { Layout } from "../components/Layout"; import AccountDetails from '../components/AccountDetails';
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,30 +29,31 @@ const SignMessage = ({ route }: SignProps) => {
}; };
return ( return (
<Layout title="Sign Message"> <View style={styles.signPage}>
<Text variant="titleMedium"> <View style={styles.accountInfo}>
{account && `Account ${account.index + 1}`} <View>
</Text> <Text variant="titleMedium">
<AccountDetails account={account} /> {account && `Account ${account.index + 1}`}
</Text>
</View>
<View style={styles.accountContainer}>
<AccountDetails account={account} />
</View>
</View>
<Stack spacing={4}> <TextInput
<Divider flexItem /> mode="outlined"
<TextInput placeholder="Enter your message"
mode="outlined" onChangeText={text => setMessage(text)}
placeholder="Enter your message" value={message}
onChangeText={(text) => setMessage(text)} />
value={message}
/>
<Button <View style={styles.signButton}>
variant="contained" <Button mode="contained" onPress={signMessageHandler}>
onClick={signMessageHandler}
sx={{ width: "200px", px: 4, py: 1, mt: 2 }}
>
Sign Sign
</Button> </Button>
</Stack> </View>
</Layout> </View>
); );
}; };

View File

@ -1,12 +1,11 @@
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();
@ -14,7 +13,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);
@ -27,10 +26,12 @@ export default function WalletConnect() {
}, [web3wallet, setActiveSessions]); }, [web3wallet, setActiveSessions]);
return ( return (
<Layout title="Active Sessions"> <View>
{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
@ -43,7 +44,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>
@ -59,8 +60,7 @@ 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>
)} )}
</Layout> </View>
); );
} }

View File

@ -1,362 +0,0 @@
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 { createWallet, retrieveAccounts, retrieveSingleAccount } from '../utils/accounts';
import AccountDetails from '../components/AccountDetails';
import styles from '../styles/stylesheet';
import DataBox from '../components/DataBox';
import { getPathKey } from '../utils/misc';
import { useNetworks } from '../context/NetworksContext';
import TxErrorDialog from '../components/TxErrorDialog';
import { MEMO } from '../screens/ApproveTransfer';
import { Account, NetworksDataState } from '../types';
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 = useCallback(async (chainId: string): Promise<string[]> => {
const targetNetwork = networksData.find(network => network.chainId === chainId);
if (!targetNetwork) {
return [];
}
const accounts = await retrieveAccounts(targetNetwork);
if (!accounts || accounts.length === 0) {
return [];
}
return accounts.map(account => account.address);
}, [networksData]);
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.gt(amountBigNum);
};
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, event.origin);
};
window.addEventListener('message', handleGetAccounts);
return () => {
window.removeEventListener('message', handleGetAccounts);
};
}, [getAccountsData]);
useEffect(() => {
const handleCreateAccounts = async (event: MessageEvent) => {
if (event.data.type !== 'REQUEST_CREATE_OR_GET_ACCOUNTS') return;
let accountsData = await getAccountsData(event.data.chainId);
if (accountsData.length === 0) {
console.log("Accounts not found, creating wallet...");
await createWallet(networksData);
// Re-fetch newly created accounts
accountsData = await getAccountsData(event.data.chainId);
}
sendMessage(event.source as Window, 'WALLET_ACCOUNTS_DATA', accountsData, event.origin);
};
window.addEventListener('message', handleCreateAccounts);
return () => {
window.removeEventListener('message', handleCreateAccounts);
};
}, [networksData, getAccountsData]);
const handleTxRequested = useCallback(
async (event: MessageEvent) => {
try {
if (event.data.type !== 'REQUEST_TX') return;
txEventRef.current = event;
const { chainId, fromAddress, toAddress, amount } = event.data;
const network = networksData.find(net => net.chainId === chainId);
if (!network) {
console.error('Network not found');
throw new Error('Requested network not supported.');
}
const account = await retrieveSingleAccount(network.namespace, network.chainId, fromAddress);
if (!account) {
throw new Error('Account not found for the requested address.');
}
const cosmosPrivKey = (
await getPathKey(`${network.namespace}:${chainId}`, account.index)
).privKey;
const sender = await DirectSecp256k1Wallet.fromKey(
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
network.addressPrefix
);
const client = await SigningStargateClient.connectWithSigner(network.rpcUrl!, sender);
const balance = await client.getBalance(
account.address,
network.nativeDenom!.toLowerCase()
);
const sendMsg = {
typeUrl: '/cosmos.bank.v1beta1.MsgSend',
value: {
fromAddress: fromAddress,
toAddress: toAddress,
amount: [
{
amount: String(amount),
denom: network.nativeDenom!,
},
],
},
};
setTransactionDetails({
chainId,
fromAddress,
toAddress,
amount,
account,
balance: balance.amount,
requestedNetwork: network,
});
if (!checkSufficientFunds(amount, balance.amount)) {
console.log("Insufficient funds detected. Throwing error.");
throw new Error('Insufficient funds');
}
const gasEstimation = await client.simulate(fromAddress, [sendMsg], MEMO);
const gasLimit = String(
Math.round(gasEstimation * Number(process.env.REACT_APP_GAS_ADJUSTMENT))
);
setGasLimit(gasLimit);
const gasPrice = GasPrice.fromString(`${network.gasPrice}${network.nativeDenom}`);
const cosmosFees = calculateFee(Number(gasLimit), gasPrice);
setFees(cosmosFees.amount[0].amount);
setIsTxRequested(true);
} catch (error) {
if (!(error instanceof Error)) {
throw error;
}
setTxError(error.message);
}
}, [networksData]);
useEffect(() => {
window.addEventListener('message', handleTxRequested);
return () => window.removeEventListener('message', handleTxRequested);
}, [handleTxRequested]);
const acceptRequestHandler = async () => {
try {
setIsTxLoading(true);
if (!transactionDetails) {
throw new Error('Tx details not set');
}
const balanceBigNum = BigNumber.from(transactionDetails.balance);
const amountBigNum = BigNumber.from(String(transactionDetails.amount));
if (amountBigNum.gte(balanceBigNum)) {
throw new Error('Insufficient funds');
}
const cosmosPrivKey = (
await getPathKey(`${transactionDetails.requestedNetwork.namespace}:${transactionDetails.chainId}`, transactionDetails.account.index)
).privKey;
const sender = await DirectSecp256k1Wallet.fromKey(
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
transactionDetails.requestedNetwork.addressPrefix
);
const client = await SigningStargateClient.connectWithSigner(
transactionDetails.requestedNetwork.rpcUrl!,
sender
);
const fee = calculateFee(
Number(gasLimit),
GasPrice.fromString(`${transactionDetails.requestedNetwork.gasPrice}${transactionDetails.requestedNetwork.nativeDenom}`)
);
const txResult = await client.sendTokens(
transactionDetails.fromAddress,
transactionDetails.toAddress,
[{ amount: String(transactionDetails.amount), denom: transactionDetails.requestedNetwork.nativeDenom! }],
fee
);
const event = txEventRef.current;
if (event?.source) {
sendMessage(event.source as Window, 'TRANSACTION_RESPONSE', txResult.transactionHash, event.origin);
} else {
console.error('No event source available to send message');
}
} catch (error) {
if (!(error instanceof Error)) {
throw error;
}
setTxError(error.message);
} finally {
setIsTxLoading(false);
}
};
const rejectRequestHandler = () => {
const event = txEventRef.current;
setIsTxRequested(false);
setTransactionDetails(null);
if (event?.source) {
sendMessage(event.source as Window, 'TRANSACTION_RESPONSE', null, event.origin);
} else {
console.error('No event source available to send message');
}
};
return (
<>
{isTxRequested && transactionDetails ? (
<>
<ScrollView contentContainerStyle={styles.appContainer}>
<View style={styles.dataBoxContainer}>
<Text style={styles.dataBoxLabel}>From</Text>
<View style={styles.dataBox}>
<AccountDetails account={transactionDetails.account} />
</View>
</View>
<DataBox
label={`Balance (${transactionDetails.requestedNetwork.nativeDenom})`}
data={
transactionDetails.balance === '' ||
transactionDetails.balance === undefined
? 'Loading balance...'
: `${transactionDetails.balance}`
}
/>
<View style={styles.approveTransfer}>
<DataBox label="To" data={transactionDetails.toAddress} />
<DataBox
label={`Amount (${transactionDetails.requestedNetwork.nativeDenom})`}
data={transactionDetails.amount}
/>
<TextInput
mode="outlined"
label="Fee"
value={fees}
onChangeText={setFees}
style={styles.transactionFeesInput}
/>
<TextInput
mode="outlined"
label="Gas Limit"
value={gasLimit}
onChangeText={value =>
/^\d+$/.test(value) ? setGasLimit(value) : null
}
/>
</View>
</ScrollView>
<View style={styles.buttonContainer}>
<Button
mode="contained"
onPress={acceptRequestHandler}
loading={isTxLoading}
disabled={!transactionDetails.balance || !fees || isTxLoading}
>
{isTxLoading ? 'Processing' : 'Yes'}
</Button>
<Button
mode="contained"
onPress={rejectRequestHandler}
buttonColor="#B82B0D"
disabled={isTxLoading}
>
No
</Button>
</View>
</>
) : (
<View style={styles.spinnerContainer}>
<View style={{marginTop: 50}}></View>
<ActivityIndicator size="large" color="#0000ff" />
</View>
)}
<TxErrorDialog
error={txError!}
visible={!!txError}
hideDialog={() => {
setTxError(null)
if (window.parent) {
sendMessage(window.parent, 'TRANSACTION_RESPONSE', null, '*');
sendMessage(window.parent, 'closeIframe', null, '*');
}
}}
/>
</>
);
};

View File

@ -1,21 +1,22 @@
import { StyleSheet } from "react-native"; import { StyleSheet } from 'react-native';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
createWalletContainer: { createWalletContainer: {
marginTop: 20, marginTop: 20,
alignSelf: "center", width: 150,
marginBottom: 30, alignSelf: 'center',
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,
@ -23,32 +24,28 @@ 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,
@ -74,81 +71,33 @@ const styles = StyleSheet.create({
borderRadius: 10, borderRadius: 10,
}, },
dialogWarning: { dialogWarning: {
color: "#FFA3A8", color: 'red',
},
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: "30%", width: '25%',
margin: 4, margin: 8,
padding: 4, padding: 6,
borderRadius: 4, borderWidth: 1,
alignItems: "center", borderColor: '#ccc',
justifyContent: "flex-start", borderRadius: 8,
backgroundColor: "#29292E", alignItems: 'center',
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: "#FBFBFB", color: 'black',
fontSize: 18, fontSize: 18,
margin: 4, margin: 4,
}, },
@ -158,15 +107,15 @@ const styles = StyleSheet.create({
HDbuttonContainer: { HDbuttonContainer: {
marginTop: 20, marginTop: 20,
width: 200, width: 200,
alignSelf: "flex-start", alignSelf: 'center',
}, },
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,
}, },
@ -174,9 +123,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: {
@ -185,51 +134,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: "#0f0f0f", backgroundColor: 'white',
bottom: 0, bottom: 0,
}, },
modalOuterContainer: { flex: 1 }, modalOuterContainer: { flex: 1 },
@ -238,32 +187,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,
@ -276,48 +225,46 @@ 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: "white", color: 'black',
}, },
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: "white", color: 'black',
}, },
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,
@ -334,7 +281,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 },
@ -345,7 +292,7 @@ const styles = StyleSheet.create({
paddingVertical: 5, paddingVertical: 5,
}, },
transactionLabel: { transactionLabel: {
fontWeight: "700", fontWeight: '700',
padding: 8, padding: 8,
}, },
linkContainer: { linkContainer: {
@ -354,7 +301,7 @@ const styles = StyleSheet.create({
networksButton: { networksButton: {
marginTop: 12, marginTop: 12,
marginBottom: 20, marginBottom: 20,
}, }
}); });
export default styles; export default styles;

View File

@ -36,7 +36,6 @@ export type StackParamsList = {
requestEvent: Web3WalletTypes.SessionRequest; requestEvent: Web3WalletTypes.SessionRequest;
requestSessionData: SessionTypes.Struct; requestSessionData: SessionTypes.Struct;
}; };
WalletEmbed: undefined;
}; };
export type Account = { export type Account = {

View File

@ -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,23 +27,12 @@ import { COSMOS, EIP155 } from './constants';
const createWallet = async ( const createWallet = async (
networksData: NetworksDataState[], networksData: NetworksDataState[],
recoveryPhrase?: string,
): Promise<string> => { ): Promise<string> => {
const mnemonic = recoveryPhrase ? recoveryPhrase : utils.entropyToMnemonic(utils.randomBytes(16)); const mnemonic = utils.entropyToMnemonic(utils.randomBytes(16));
const hdNode = HDNode.fromMnemonic(mnemonic);
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic); await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
await createWalletFromMnemonic(networksData, hdNode, mnemonic); const hdNode = HDNode.fromMnemonic(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);
@ -64,7 +53,7 @@ const createWalletFromMnemonic = 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(
@ -84,6 +73,8 @@ const createWalletFromMnemonic = async (
), ),
]); ]);
} }
return mnemonic;
}; };
const addAccount = async ( const addAccount = async (
@ -206,9 +197,9 @@ const retrieveAccounts = async (
return; return;
} }
const loadedAccounts = await retrieveAccountsForNetwork( const loadedAccounts = await retrieveAccountsForNetwork(
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`, `${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
accountIndices, accountIndices,
) )
return loadedAccounts; return loadedAccounts;
}; };

View File

@ -1,32 +1,22 @@
import { DENOM } from '@cerc-io/registry-sdk'
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';
export const EIP155 = 'eip155'; export const EIP155 = 'eip155';
export const COSMOS = 'cosmos'; export const COSMOS = 'cosmos';
export const DEFAULT_NETWORKS = [ export const DEFAULT_NETWORKS = [
{
chainId: 'laconic-testnet-2',
networkName: 'laconicd testnet-2',
namespace: COSMOS,
rpcUrl: process.env.REACT_APP_LACONICD_RPC_URL!,
blockExplorerUrl: '',
nativeDenom: 'alnt',
addressPrefix: 'laconic',
coinType: '118',
gasPrice: '0.001',
isDefault: true,
},
{ {
chainId: 'laconic_9000-1', chainId: 'laconic_9000-1',
networkName: 'laconicd', networkName: 'laconicd',
namespace: COSMOS, namespace: COSMOS,
rpcUrl: "https://laconicd.laconic.com", rpcUrl: process.env.REACT_APP_LACONICD_RPC_URL!,
blockExplorerUrl: '', blockExplorerUrl: '',
nativeDenom: 'alnt', nativeDenom: DENOM,
addressPrefix: 'laconic', addressPrefix: 'laconic',
coinType: '118', coinType: '118',
gasPrice: '1', gasPrice: '1',
isDefault: false, isDefault: true,
}, },
{ {
chainId: '1', chainId: '1',
@ -58,5 +48,3 @@ 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;

View File

@ -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
} }

View File

@ -40,6 +40,7 @@ export const getNamespaces = async (
networksData: NetworksDataState[], networksData: NetworksDataState[],
selectedNetwork: NetworksDataState, selectedNetwork: NetworksDataState,
accounts: Account[], accounts: Account[],
currentIndex: number,
) => { ) => {
const namespaceChainId = `${selectedNetwork.namespace}:${selectedNetwork.chainId}`; const namespaceChainId = `${selectedNetwork.namespace}:${selectedNetwork.chainId}`;
@ -100,6 +101,23 @@ export const getNamespaces = async (
const requiredAddressesArray = await Promise.all(requiredAddressesPromise); const requiredAddressesArray = await Promise.all(requiredAddressesPromise);
const requiredAddresses = requiredAddressesArray.flat(); const requiredAddresses = requiredAddressesArray.flat();
let sortedAccounts = requiredAddresses;
// If selected network is included in chains requested from dApp,
// Put selected account as first account
if (walletConnectChains.includes(namespaceChainId)) {
const currentAddresses = requiredAddresses.filter(address =>
address.includes(namespaceChainId),
);
sortedAccounts = [
currentAddresses[currentIndex],
...currentAddresses.filter((address, index) => index !== currentIndex),
...requiredAddresses.filter(
address => !currentAddresses.includes(address),
),
];
}
// construct namespace object // construct namespace object
const newNamespaces = { const newNamespaces = {
eip155: { eip155: {
@ -115,7 +133,7 @@ export const getNamespaces = async (
...(optionalNamespaces.eip155?.events ?? []), ...(optionalNamespaces.eip155?.events ?? []),
...(requiredNamespaces.eip155?.events ?? []), ...(requiredNamespaces.eip155?.events ?? []),
], ],
accounts: requiredAddresses.filter(account => account.includes(EIP155)), accounts: sortedAccounts.filter(account => account.includes(EIP155)),
}, },
cosmos: { cosmos: {
chains: walletConnectChains.filter(chain => chain.includes(COSMOS)), chains: walletConnectChains.filter(chain => chain.includes(COSMOS)),
@ -129,12 +147,18 @@ export const getNamespaces = async (
...(optionalNamespaces.cosmos?.events ?? []), ...(optionalNamespaces.cosmos?.events ?? []),
...(requiredNamespaces.cosmos?.events ?? []), ...(requiredNamespaces.cosmos?.events ?? []),
], ],
accounts: requiredAddresses.filter(account => account.includes(COSMOS)), accounts: sortedAccounts.filter(account => account.includes(COSMOS)),
}, },
}; };
return newNamespaces; return newNamespaces;
} else { } else {
// Set selected account as the first account in supported namespaces
const sortedAccounts = [
accounts[currentIndex],
...accounts.filter((account, index) => index !== currentIndex),
];
switch (selectedNetwork.namespace) { switch (selectedNetwork.namespace) {
case EIP155: case EIP155:
return { return {
@ -151,7 +175,7 @@ export const getNamespaces = async (
...(optionalNamespaces.eip155?.events ?? []), ...(optionalNamespaces.eip155?.events ?? []),
...(requiredNamespaces.eip155?.events ?? []), ...(requiredNamespaces.eip155?.events ?? []),
], ],
accounts: accounts.map(ethAccount => { accounts: sortedAccounts.map(ethAccount => {
return `${namespaceChainId}:${ethAccount.address}`; return `${namespaceChainId}:${ethAccount.address}`;
}), }),
}, },
@ -176,7 +200,7 @@ export const getNamespaces = async (
...(optionalNamespaces.cosmos?.events ?? []), ...(optionalNamespaces.cosmos?.events ?? []),
...(requiredNamespaces.cosmos?.events ?? []), ...(requiredNamespaces.cosmos?.events ?? []),
], ],
accounts: accounts.map(cosmosAccount => { accounts: sortedAccounts.map(cosmosAccount => {
return `${namespaceChainId}:${cosmosAccount.address}`; return `${namespaceChainId}:${cosmosAccount.address}`;
}), }),
}, },

View File

@ -7,7 +7,7 @@ import { getSdkError } from '@walletconnect/utils';
import { import {
SigningStargateClient, SigningStargateClient,
StdFee, StdFee,
MsgSendEncodeObject MsgSendEncodeObject,
} from '@cosmjs/stargate'; } from '@cosmjs/stargate';
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';
@ -97,18 +97,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);

View File

@ -6,7 +6,6 @@ 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}

View File

@ -18,13 +18,4 @@ REACT_APP_GAS_ADJUSTMENT=$CERC_GAS_ADJUSTMENT \
REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL \ REACT_APP_LACONICD_RPC_URL=$CERC_LACONICD_RPC_URL \
yarn build yarn build
# Define the directory and file path
FILE_PATH="/app/build/.well-known/walletconnect.txt"
# Create the directory if it doesn't exist
mkdir -p "$(dirname "$FILE_PATH")"
# Write verification code to the file
echo "$WALLET_CONNECT_VERIFY_CODE" > "$FILE_PATH"
# Serve build dir
http-server --proxy http://localhost:80? -p 80 /app/build http-server --proxy http://localhost:80? -p 80 /app/build

View File

@ -51,9 +51,6 @@ Instructions for running the `laconic-wallet-web` using [laconic-so](https://git
# 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=

222
yarn.lock
View File

@ -1746,14 +1746,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.4.0": "@eslint-community/eslint-utils@^4.2.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.5.1", "@eslint-community/regexpp@^4.6.1": "@eslint-community/regexpp@^4.4.0", "@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,33 +2125,6 @@
"@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"
@ -2584,44 +2557,11 @@
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"
@ -2649,15 +2589,6 @@
"@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"
@ -2668,16 +2599,6 @@
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"
@ -2692,37 +2613,11 @@
csstype "^3.1.3" csstype "^3.1.3"
prop-types "^15.8.1" prop-types "^15.8.1"
"@mui/system@^5.16.5": "@mui/types@^7.2.15":
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"
@ -3939,7 +3834,7 @@
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.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": "@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":
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==
@ -4078,7 +3973,7 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/semver@^7.3.12", "@types/semver@^7.5.0": "@types/semver@^7.3.12":
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==
@ -4180,23 +4075,6 @@
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"
@ -4214,17 +4092,6 @@
"@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"
@ -4233,14 +4100,6 @@
"@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"
@ -4251,26 +4110,11 @@
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"
@ -4284,20 +4128,6 @@
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"
@ -4312,19 +4142,6 @@
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"
@ -4333,14 +4150,6 @@
"@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"
@ -7292,7 +7101,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.33.2: eslint-plugin-react@^7.27.1:
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==
@ -8514,11 +8323,6 @@ 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"
@ -8565,7 +8369,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.4: ignore@^5.2.0:
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==
@ -10570,13 +10374,6 @@ 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@^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"
@ -13849,11 +13646,6 @@ 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"