From 21b749d9a45d156fc22331faba86b5ef9041d945 Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:42:12 +0530 Subject: [PATCH 01/64] Add QR scanner for walletconnect (#36) * Add page for scanning qr code * Refactor code * Ask for permission to use camera * Change Qr to QR * Seperate imports * QR instead of Qr --------- Co-authored-by: Adw8 --- App.tsx | 8 ++ android/app/src/main/AndroidManifest.xml | 1 + android/gradle.properties | 2 + components/Accounts.tsx | 11 +++ components/QRScanner.tsx | 107 +++++++++++++++++++++++ package.json | 1 + types.ts | 1 + yarn.lock | 5 ++ 8 files changed, 136 insertions(+) create mode 100644 components/QRScanner.tsx diff --git a/App.tsx b/App.tsx index e3a81b1..9bdcb74 100644 --- a/App.tsx +++ b/App.tsx @@ -7,6 +7,7 @@ import SignMessage from './components/SignMessage'; import HomeScreen from './components/HomeScreen'; import SignRequest from './components/SignRequest'; import InvalidPath from './components/InvalidPath'; +import QRScanner from './components/QRScanner'; import { StackParamsList } from './types'; @@ -58,6 +59,13 @@ const App = (): React.JSX.Element => { headerBackVisible: false, }} /> + ); diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 4558d99..fa040e2 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,6 +1,7 @@ + + + { + navigation.navigate('QRScanner'); + }}> + + Connect Wallet + + diff --git a/components/QRScanner.tsx b/components/QRScanner.tsx new file mode 100644 index 0000000..bd85501 --- /dev/null +++ b/components/QRScanner.tsx @@ -0,0 +1,107 @@ +import React, { useEffect, useState } from 'react'; +import { Alert, StyleSheet, View, AppState } from 'react-native'; +import { Text, Button } from 'react-native-paper'; +import { + Camera, + useCameraDevice, + useCameraPermission, + useCodeScanner, +} from 'react-native-vision-camera'; + +import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; + +import { StackParamsList } from '../types'; + +const QRScanner = () => { + const navigation = + useNavigation>(); + + const { hasPermission, requestPermission } = useCameraPermission(); + + const [isActive, setIsActive] = useState(AppState.currentState === 'active'); + const device = useCameraDevice('back'); + const [isScanning, setScanning] = useState(true); + const codeScanner = useCodeScanner({ + codeTypes: ['qr'], + onCodeScanned: codes => { + if (isScanning) { + codes.forEach(code => { + if (code.value) { + Alert.alert(`Scanned: ${code.value}`, undefined, [ + { + text: 'OK', + onPress: () => { + navigation.navigate('Laconic'); + }, + }, + ]); + + setScanning(false); + } + }); + } + }, + }); + + useEffect(() => { + const handleAppStateChange = (newState: string) => { + setIsActive(newState === 'active'); + }; + + AppState.addEventListener('change', handleAppStateChange); + + if (!hasPermission) { + requestPermission(); + } + }, [hasPermission, isActive, requestPermission]); + + if (!hasPermission) { + return ( + + No Camera Permission! + + + ); + } + + if (!device) { + return ( + + No Camera Selected! + + ); + } + + return ( + + {isActive ? ( + + ) : ( + No Camera Selected! + )} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + camera: { + width: 400, + height: 500, + borderRadius: 50, + overflow: 'hidden', + }, +}); + +export default QRScanner; diff --git a/package.json b/package.json index c17f2b3..8528009 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "react-native-safe-area-context": "^4.9.0", "react-native-screens": "^3.29.0", "react-native-vector-icons": "^10.0.3", + "react-native-vision-camera": "^3.9.0", "text-encoding-polyfill": "^0.6.7" }, "devDependencies": { diff --git a/types.ts b/types.ts index 3fafac2..43b38ad 100644 --- a/types.ts +++ b/types.ts @@ -5,6 +5,7 @@ export type StackParamsList = { | { network: string; address: string; message: string } | undefined; InvalidPath: undefined; + QRScanner: undefined; }; export type Account = { diff --git a/yarn.lock b/yarn.lock index 432e0ec..c35962f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6604,6 +6604,11 @@ react-native-vector-icons@^10.0.3: prop-types "^15.7.2" yargs "^16.1.1" +react-native-vision-camera@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/react-native-vision-camera/-/react-native-vision-camera-3.9.0.tgz#6a96a6cbad53c2db84d671c388b250327681a553" + integrity sha512-q0HejFTS56s5DXWHZhlWLLZMKn/8OXALrqET+FySPIKskwYEdJ5rOV2aDlD6hlo67qCNFGUIMZWGFFc9L2os1g== + react-native@0.73.3: version "0.73.3" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.73.3.tgz#aae18b4c6da84294c1f8e1d6446b46c887bf087c" -- 2.45.2 From 150f10b91f7f91fbed5f3e44c53b47156bed1368 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Tue, 5 Mar 2024 19:20:31 +0530 Subject: [PATCH 02/64] Connect wallet to a dapp using WalletConnect (#38) * Connect with dapp using WalletConnect * Pair dapp with wallet * Sign message taken from dapp and return the signature * Add todos * Move wallet connect functions to seperate screen * Change ui * Change ui for wc modals * Add styles * Remove border radius at the bottom * Make review changes * Add dependancy to useEffect * Move pairing modal methods --------- Co-authored-by: Adw8 --- App.tsx | 73 +- components/Accounts.tsx | 2 +- components/InvalidPath.tsx | 4 +- components/PairingModal.tsx | 118 ++ components/SignModal.tsx | 92 ++ components/WalletConnect.tsx | 42 + package.json | 9 +- styles/stylesheet.js | 56 +- types.ts | 21 + utils/wallet-connect/EIP155Lib.ts | 147 +++ utils/wallet-connect/EIP155Requests.ts | 72 ++ utils/wallet-connect/EIP155Wallet.ts | 64 + utils/wallet-connect/Helpers.ts | 106 ++ utils/wallet-connect/WalletConnectUtils.tsx | 59 + yarn.lock | 1153 ++++++++++++++++++- 15 files changed, 1995 insertions(+), 23 deletions(-) create mode 100644 components/PairingModal.tsx create mode 100644 components/SignModal.tsx create mode 100644 components/WalletConnect.tsx create mode 100644 utils/wallet-connect/EIP155Lib.ts create mode 100644 utils/wallet-connect/EIP155Requests.ts create mode 100644 utils/wallet-connect/EIP155Wallet.ts create mode 100644 utils/wallet-connect/Helpers.ts create mode 100644 utils/wallet-connect/WalletConnectUtils.tsx diff --git a/App.tsx b/App.tsx index 9bdcb74..f9cc566 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,6 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; +import { SignClientTypes } from '@walletconnect/types'; import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; @@ -8,12 +9,62 @@ import HomeScreen from './components/HomeScreen'; import SignRequest from './components/SignRequest'; import InvalidPath from './components/InvalidPath'; import QRScanner from './components/QRScanner'; +import PairingModal from './components/PairingModal'; +import SignModal from './components/SignModal'; +import WalletConnect from './components/WalletConnect'; import { StackParamsList } from './types'; +import useInitialization, { + web3wallet, +} from './utils/wallet-connect/WalletConnectUtils'; +import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; const Stack = createNativeStackNavigator(); const App = (): React.JSX.Element => { + const [modalVisible, setModalVisible] = useState(false); + //TODO: Remove any + const [currentProposal, setCurrentProposal] = useState< + SignClientTypes.EventArguments['session_proposal'] | undefined + >(); + const [requestSession, setRequestSession] = useState(); + const [requestEventData, setRequestEventData] = useState(); + const [signModalVisible, setSignModalVisible] = useState(false); + + useInitialization(); + + const onSessionProposal = useCallback( + (proposal: SignClientTypes.EventArguments['session_proposal']) => { + setModalVisible(true); + setCurrentProposal(proposal); + }, + [], + ); + + const onSessionRequest = useCallback( + async (requestEvent: SignClientTypes.EventArguments['session_request']) => { + const { topic, params } = requestEvent; + const { request } = params; + const requestSessionData = + web3wallet.engine.signClient.session.get(topic); + + switch (request.method) { + case EIP155_SIGNING_METHODS.ETH_SIGN: + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: + setRequestSession(requestSessionData); + setRequestEventData(requestEvent); + setSignModalVisible(true); + return; + } + }, + [], + ); + useEffect(() => { + web3wallet?.on('session_proposal', onSessionProposal); + web3wallet?.on('session_request', onSessionRequest); + //TODO: Investigate dependancies + }); + const linking = { prefixes: ['https://www.laconic-wallet.com'], config: { @@ -59,6 +110,13 @@ const App = (): React.JSX.Element => { headerBackVisible: false, }} /> + { }} /> + + + ); }; diff --git a/components/Accounts.tsx b/components/Accounts.tsx index 3159ca6..bea18df 100644 --- a/components/Accounts.tsx +++ b/components/Accounts.tsx @@ -117,7 +117,7 @@ const Accounts = ({ { - navigation.navigate('QRScanner'); + navigation.navigate('WalletConnect'); }}> { useNavigation>(); return ( - The signature request was invalid. + + The signature request was invalid. + + + + + + + + ); +}; + +export default PairingModal; diff --git a/components/SignModal.tsx b/components/SignModal.tsx new file mode 100644 index 0000000..6b96cc2 --- /dev/null +++ b/components/SignModal.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { Button, Text } from 'react-native-paper'; +import { Image, Modal, View } from 'react-native'; + +import { getSignParamsMessage } from '../utils/wallet-connect/Helpers'; +import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; +import { + approveEIP155Request, + rejectEIP155Request, +} from '../utils/wallet-connect/EIP155Requests'; + +import styles from '../styles/stylesheet'; +import { SignModalProps } from '../types'; + +const SignModal = ({ + visible, + setModalVisible, + requestEvent, + requestSession, +}: SignModalProps) => { + if (!requestEvent || !requestSession) { + return null; + } + + const chainID = requestEvent?.params?.chainId?.toUpperCase(); + const message = getSignParamsMessage(requestEvent?.params?.request?.params); + + const requestName = requestSession?.peer?.metadata?.name; + const requestIcon = requestSession?.peer?.metadata?.icons[0]; + const requestURL = requestSession?.peer?.metadata?.url; + + const { topic } = requestEvent; + + const onApprove = async () => { + if (requestEvent) { + const response = await approveEIP155Request(requestEvent); + await web3wallet.respondSessionRequest({ + topic, + response, + }); + setModalVisible(false); + } + }; + + const onReject = async () => { + if (requestEvent) { + const response = rejectEIP155Request(requestEvent); + await web3wallet.respondSessionRequest({ + topic, + response, + }); + setModalVisible(false); + } + }; + + return ( + + + + Sign this message? + + + + {requestName} + {requestURL} + + + {message} + + Chains: {chainID} + + + + + + + + + + ); +}; + +export default SignModal; diff --git a/components/WalletConnect.tsx b/components/WalletConnect.tsx new file mode 100644 index 0000000..69fe4ae --- /dev/null +++ b/components/WalletConnect.tsx @@ -0,0 +1,42 @@ +import React, { useState } from 'react'; +import { View } from 'react-native'; +import { Button, TextInput } from 'react-native-paper'; + +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useNavigation } from '@react-navigation/native'; + +import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils'; +import styles from '../styles/stylesheet'; +import { StackParamsList } from '../types'; + +const WalletConnect = () => { + const [currentWCURI, setCurrentWCURI] = useState(''); + + const navigation = + useNavigation>(); + + const pair = async () => { + const pairing = await web3WalletPair({ uri: currentWCURI }); + navigation.navigate('Laconic'); + return pairing; + }; + + return ( + + + + + + + + ); +}; + +export default WalletConnect; diff --git a/package.json b/package.json index 8528009..1c1e103 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,15 @@ "@cosmjs/amino": "^0.32.2", "@cosmjs/crypto": "^0.32.2", "@ethersproject/shims": "^5.7.0", + "@json-rpc-tools/utils": "^1.7.6", + "@react-native-async-storage/async-storage": "^1.22.3", + "@react-native-community/netinfo": "^11.3.1", "@react-navigation/native": "^6.1.10", "@react-navigation/native-stack": "^6.9.18", - "ethers": "5", + "@walletconnect/react-native-compat": "^2.11.2", + "@walletconnect/web3wallet": "^1.10.2", + "ethers": "5.7.2", + "fast-text-encoding": "^1.0.6", "metro-react-native-babel-preset": "^0.77.0", "patch-package": "^8.0.0", "postinstall-postinstall": "^2.1.0", @@ -43,6 +49,7 @@ "@react-native/typescript-config": "0.73.1", "@types/react": "^18.2.6", "@types/react-test-renderer": "^18.0.0", + "@walletconnect/jsonrpc-types": "^1.0.3", "babel-jest": "^29.6.3", "babel-plugin-module-resolver": "^5.0.0", "eslint": "^8.19.0", diff --git a/styles/stylesheet.js b/styles/stylesheet.js index 3a77e36..86313e1 100644 --- a/styles/stylesheet.js +++ b/styles/stylesheet.js @@ -131,12 +131,66 @@ const styles = StyleSheet.create({ justifyContent: 'center', padding: 20, }, - messageText: { + invalidMessageText: { color: 'black', fontSize: 16, textAlign: 'center', marginBottom: 20, }, + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + }, + modalContentContainer: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + borderRadius: 34, + borderBottomStartRadius: 0, + borderBottomEndRadius: 0, + borderWidth: 1, + width: '100%', + height: '50%', + position: 'absolute', + backgroundColor: 'white', + bottom: 0, + }, + dappLogo: { + width: 50, + height: 50, + borderRadius: 8, + marginVertical: 16, + }, + space: { + width: 50, + }, + flexRow: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + marginTop: 20, + paddingHorizontal: 16, + }, + marginVertical8: { + marginVertical: 8, + textAlign: 'center', + }, + subHeading: { + textAlign: 'center', + fontWeight: '600', + }, + centerText: { + textAlign: 'center', + }, + messageBody: { + borderWidth: 1, + borderRadius: 10, + paddingVertical: 10, + paddingHorizontal: 10, + marginVertical: 20, + }, }); export default styles; diff --git a/types.ts b/types.ts index 43b38ad..4134eee 100644 --- a/types.ts +++ b/types.ts @@ -1,3 +1,5 @@ +import { SignClientTypes } from '@walletconnect/types'; + export type StackParamsList = { Laconic: undefined; SignMessage: { selectedNetwork: string; accountInfo: Account } | undefined; @@ -6,6 +8,7 @@ export type StackParamsList = { | undefined; InvalidPath: undefined; QRScanner: undefined; + WalletConnect: undefined; }; export type Account = { @@ -83,3 +86,21 @@ export type PathState = { secondNumber: string; thirdNumber: string; }; + +export interface PairingModalProps { + visible: boolean; + setModalVisible: (arg1: boolean) => void; + currentProposal: + | SignClientTypes.EventArguments['session_proposal'] + | undefined; + setCurrentProposal: ( + arg1: SignClientTypes.EventArguments['session_proposal'] | undefined, + ) => void; +} + +export interface SignModalProps { + visible: boolean; + setModalVisible: (arg1: boolean) => void; + requestSession: any; + requestEvent: SignClientTypes.EventArguments['session_request'] | undefined; +} diff --git a/utils/wallet-connect/EIP155Lib.ts b/utils/wallet-connect/EIP155Lib.ts new file mode 100644 index 0000000..038f3b8 --- /dev/null +++ b/utils/wallet-connect/EIP155Lib.ts @@ -0,0 +1,147 @@ +// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a + +import { providers, Wallet } from 'ethers'; + +/** + * Types + */ +interface IInitArgs { + mnemonic?: string; +} + +/** + * Library + */ +export default class EIP155Lib { + wallet: Wallet; + + constructor(wallet: Wallet) { + this.wallet = wallet; + } + + static init({ mnemonic }: IInitArgs) { + const wallet = mnemonic + ? Wallet.fromMnemonic(mnemonic) + : Wallet.createRandom(); + + return new EIP155Lib(wallet); + } + + getMnemonic() { + return this.wallet.mnemonic.phrase; + } + + getAddress() { + return this.wallet.address; + } + + signMessage(message: string) { + return this.wallet.signMessage(message); + } + + _signTypedData(domain: any, types: any, data: any) { + return this.wallet._signTypedData(domain, types, data); + } + + connect(provider: providers.JsonRpcProvider) { + return this.wallet.connect(provider); + } + + signTransaction(transaction: providers.TransactionRequest) { + return this.wallet.signTransaction(transaction); + } +} + +/** + * @desc Reference list of eip155 chains + * @url https://chainlist.org + */ + +/** + * Types + */ +export type TEIP155Chain = keyof typeof EIP155_CHAINS; + +/** + * Chains + */ +export const EIP155_MAINNET_CHAINS = { + 'eip155:1': { + chainId: 1, + name: 'Ethereum', + logo: '/chain-logos/eip155-1.png', + rgb: '99, 125, 234', + rpc: 'https://cloudflare-eth.com/', + }, + 'eip155:43114': { + chainId: 43114, + name: 'Avalanche C-Chain', + logo: '/chain-logos/eip155-43113.png', + rgb: '232, 65, 66', + rpc: 'https://api.avax.network/ext/bc/C/rpc', + }, + 'eip155:137': { + chainId: 137, + name: 'Polygon', + logo: '/chain-logos/eip155-137.png', + rgb: '130, 71, 229', + rpc: 'https://polygon-rpc.com/', + }, + 'eip155:10': { + chainId: 10, + name: 'Optimism', + logo: '/chain-logos/eip155-10.png', + rgb: '235, 0, 25', + rpc: 'https://mainnet.optimism.io', + }, +}; + +export const EIP155_TEST_CHAINS = { + 'eip155:5': { + chainId: 5, + name: 'Ethereum Goerli', + logo: '/chain-logos/eip155-1.png', + rgb: '99, 125, 234', + rpc: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', + }, + 'eip155:43113': { + chainId: 43113, + name: 'Avalanche Fuji', + logo: '/chain-logos/eip155-43113.png', + rgb: '232, 65, 66', + rpc: 'https://api.avax-test.network/ext/bc/C/rpc', + }, + 'eip155:80001': { + chainId: 80001, + name: 'Polygon Mumbai', + logo: '/chain-logos/eip155-137.png', + rgb: '130, 71, 229', + rpc: 'https://matic-mumbai.chainstacklabs.com', + }, + 'eip155:420': { + chainId: 420, + name: 'Optimism Goerli', + logo: '/chain-logos/eip155-10.png', + rgb: '235, 0, 25', + rpc: 'https://goerli.optimism.io', + }, +}; + +export const EIP155_CHAINS = { + ...EIP155_MAINNET_CHAINS, + ...EIP155_TEST_CHAINS, +}; + +/** + * Methods + */ +export const EIP155_SIGNING_METHODS = { + PERSONAL_SIGN: 'personal_sign', + ETH_SIGN: 'eth_sign', + ETH_SIGN_TRANSACTION: 'eth_signTransaction', + ETH_SIGN_TYPED_DATA: 'eth_signTypedData', + ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3', + ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4', + ETH_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction', + ETH_SEND_TRANSACTION: 'eth_sendTransaction', +}; diff --git a/utils/wallet-connect/EIP155Requests.ts b/utils/wallet-connect/EIP155Requests.ts new file mode 100644 index 0000000..3c033c5 --- /dev/null +++ b/utils/wallet-connect/EIP155Requests.ts @@ -0,0 +1,72 @@ +// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a +import { + EIP155_CHAINS, + EIP155_SIGNING_METHODS, + TEIP155Chain, +} from './EIP155Lib'; +import { eip155Wallets } from './EIP155Wallet'; +import { + getSignParamsMessage, + getSignTypedDataParamsData, + getWalletAddressFromParams, +} from './Helpers'; +import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'; +import { SignClientTypes } from '@walletconnect/types'; +import { getSdkError } from '@walletconnect/utils'; +import { providers } from 'ethers'; +import { currentETHAddress } from './WalletConnectUtils'; + +export async function approveEIP155Request( + requestEvent: SignClientTypes.EventArguments['session_request'], +) { + const { params, id } = requestEvent; + const { chainId, request } = params; + const wallet = + eip155Wallets[getWalletAddressFromParams([currentETHAddress], params)]; + + switch (request.method) { + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: + case EIP155_SIGNING_METHODS.ETH_SIGN: + const message = getSignParamsMessage(request.params); + const signedMessage = await wallet.signMessage(message); + return formatJsonRpcResult(id, signedMessage); + + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA: + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3: + case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4: + const { + domain, + types, + message: data, + } = getSignTypedDataParamsData(request.params); + // https://github.com/ethers-io/ethers.js/issues/687#issuecomment-714069471 + delete types.EIP712Domain; + const signedData = await wallet._signTypedData(domain, types, data); + return formatJsonRpcResult(id, signedData); + + case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: + const provider = new providers.JsonRpcProvider( + EIP155_CHAINS[chainId as TEIP155Chain].rpc, + ); + const sendTransaction = request.params[0]; + const connectedWallet = wallet.connect(provider); + const { hash } = await connectedWallet.sendTransaction(sendTransaction); + return formatJsonRpcResult(id, hash); + + case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: + const signTransaction = request.params[0]; + const signature = await wallet.signTransaction(signTransaction); + return formatJsonRpcResult(id, signature); + + default: + throw new Error(getSdkError('INVALID_METHOD').message); + } +} + +export function rejectEIP155Request( + request: SignClientTypes.EventArguments['session_request'], +) { + const { id } = request; + + return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message); +} diff --git a/utils/wallet-connect/EIP155Wallet.ts b/utils/wallet-connect/EIP155Wallet.ts new file mode 100644 index 0000000..9f4c187 --- /dev/null +++ b/utils/wallet-connect/EIP155Wallet.ts @@ -0,0 +1,64 @@ +// https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a +// TODO: check and remove if not used + +import AsyncStorage from '@react-native-async-storage/async-storage'; +import EIP155Lib from './EIP155Lib'; + +export let wallet1: EIP155Lib; +export let wallet2: EIP155Lib; +export let eip155Wallets: Record; +export let eip155Addresses: string[]; + +export let address1: string; +let address2: string; + +/** + * Utilities + */ +export const setLocalStorage = async (mnemonic: any) => { + try { + const value = await AsyncStorage.setItem('EIP155_MNEMONIC_1', mnemonic); + if (value !== null) { + return value; + } + } catch (e) { + console.log('setLocalStorage Error:', e); + } +}; + +export const getLocalStorage = async () => { + try { + const value = await AsyncStorage.getItem('EIP155_MNEMONIC_1'); + if (value !== null) { + return value; + } + } catch (e) { + console.log('getLocalStorage Error:', e); + } +}; + +// Function to create or restore a wallet +export async function createOrRestoreEIP155Wallet() { + let mnemonic1 = await getLocalStorage(); + + if (mnemonic1) { + wallet1 = EIP155Lib.init({ mnemonic: mnemonic1 }); + } else { + wallet1 = EIP155Lib.init({}); + } + + // @notice / Warning!!! : This is a test wallet, do not use it for real transactions + setLocalStorage(wallet1?.getMnemonic()); + address1 = wallet1.getAddress(); + + eip155Wallets = { + [address1]: wallet1, + [address2]: wallet2, + }; + eip155Addresses = Object.keys(eip155Wallets); + + return { + eip155Wallets, + eip155Addresses, + }; +} diff --git a/utils/wallet-connect/Helpers.ts b/utils/wallet-connect/Helpers.ts new file mode 100644 index 0000000..e8ace04 --- /dev/null +++ b/utils/wallet-connect/Helpers.ts @@ -0,0 +1,106 @@ +// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a + +import { EIP155_CHAINS, TEIP155Chain } from './EIP155Lib'; +import { utils } from 'ethers'; + +/** + * Truncates string (in the middle) via given lenght value + */ +export function truncate(value: string, length: number) { + if (value?.length <= length) { + return value; + } + + const separator = '...'; + const stringLength = length - separator.length; + const frontLength = Math.ceil(stringLength / 2); + const backLength = Math.floor(stringLength / 2); + + return ( + value.substring(0, frontLength) + + separator + + value.substring(value.length - backLength) + ); +} + +/** + * Converts hex to utf8 string if it is valid bytes + */ +export function convertHexToUtf8(value: string) { + if (utils.isHexString(value)) { + return utils.toUtf8String(value); + } + + return value; +} + +/** + * Gets message from various signing request methods by filtering out + * a value that is not an address (thus is a message). + * If it is a hex string, it gets converted to utf8 string + */ +export function getSignParamsMessage(params: string[]) { + const message = params.filter(p => !utils.isAddress(p))[0]; + + return convertHexToUtf8(message); +} + +/** + * Gets data from various signTypedData request methods by filtering out + * a value that is not an address (thus is data). + * If data is a string convert it to object + */ +export function getSignTypedDataParamsData(params: string[]) { + const data = params.filter(p => !utils.isAddress(p))[0]; + + if (typeof data === 'string') { + return JSON.parse(data); + } + + return data; +} + +/** + * Get our address from params checking if params string contains one + * of our wallet addresses + */ +export function getWalletAddressFromParams(addresses: string[], params: any) { + const paramsString = JSON.stringify(params); + let address = ''; + + addresses.forEach(addr => { + if (paramsString.includes(addr)) { + address = addr; + } + }); + + return address; +} + +/** + * Check if chain is part of EIP155 standard + */ +export function isEIP155Chain(chain: string) { + return chain.includes('eip155'); +} + +/** + * Check if chain is part of COSMOS standard + */ +export function isCosmosChain(chain: string) { + return chain.includes('cosmos'); +} + +/** + * Check if chain is part of SOLANA standard + */ +export function isSolanaChain(chain: string) { + return chain.includes('solana'); +} + +/** + * Formats chainId to its name + */ +export function formatChainName(chainId: string) { + return EIP155_CHAINS[chainId as TEIP155Chain]?.name ?? chainId; +} diff --git a/utils/wallet-connect/WalletConnectUtils.tsx b/utils/wallet-connect/WalletConnectUtils.tsx new file mode 100644 index 0000000..03c4839 --- /dev/null +++ b/utils/wallet-connect/WalletConnectUtils.tsx @@ -0,0 +1,59 @@ +import '@walletconnect/react-native-compat'; +import '@ethersproject/shims'; + +import { Core } from '@walletconnect/core'; +import { ICore } from '@walletconnect/types'; +import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet'; + +export let web3wallet: IWeb3Wallet; +export let core: ICore; +export let currentETHAddress: string; + +import { useState, useCallback, useEffect } from 'react'; +import { createOrRestoreEIP155Wallet } from './EIP155Wallet'; + +async function createWeb3Wallet() { + const { eip155Addresses } = await createOrRestoreEIP155Wallet(); + currentETHAddress = eip155Addresses[0]; + + // TODO: Move to dotenv + const ENV_PROJECT_ID = 'c97365bf9f06d12a7488de36240b0ff4'; + const core = new Core({ + projectId: ENV_PROJECT_ID, + }); + + web3wallet = await Web3Wallet.init({ + core, + metadata: { + name: 'Web3Wallet React Native Tutorial', + description: 'ReactNative Web3Wallet', + url: 'https://walletconnect.com/', + icons: ['https://avatars.githubusercontent.com/u/37784886'], + }, + }); +} + +export default function useInitialization() { + const [initialized, setInitialized] = useState(false); + + const onInitialize = useCallback(async () => { + try { + await createWeb3Wallet(); + setInitialized(true); + } catch (err: unknown) { + console.log('Error for initializing', err); + } + }, []); + + useEffect(() => { + if (!initialized) { + onInitialize(); + } + }, [initialized, onInitialize]); + + return initialized; +} + +export async function web3WalletPair(params: { uri: string }) { + return await web3wallet.core.pairing.pair({ uri: params.uri }); +} diff --git a/yarn.lock b/yarn.lock index c35962f..d36347a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1629,6 +1629,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@ioredis/commands@^1.1.1": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" + integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== + "@isaacs/ttlcache@^1.4.1": version "1.4.1" resolved "https://registry.yarnpkg.com/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz#21fb23db34e9b6220c6ba023a0118a2dd3461ea2" @@ -1900,6 +1905,21 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@json-rpc-tools/types@^1.7.6": + version "1.7.6" + resolved "https://registry.yarnpkg.com/@json-rpc-tools/types/-/types-1.7.6.tgz#5abd5fde01364a130c46093b501715bcce5bdc0e" + integrity sha512-nDSqmyRNEqEK9TZHtM15uNnDljczhCUdBmRhpNZ95bIPKEDQ+nTDmGMFd2lLin3upc5h2VVVd9tkTDdbXUhDIQ== + dependencies: + keyvaluestorage-interface "^1.0.0" + +"@json-rpc-tools/utils@^1.7.6": + version "1.7.6" + resolved "https://registry.yarnpkg.com/@json-rpc-tools/utils/-/utils-1.7.6.tgz#67f04987dbaa2e7adb6adff1575367b75a9a9ba1" + integrity sha512-HjA8x/U/Q78HRRe19yh8HVKoZ+Iaoo3YZjakJYxR+rw52NHo6jM+VE9b8+7ygkCFXl/EHID5wh/MkXaE/jGyYw== + dependencies: + "@json-rpc-tools/types" "^1.7.6" + "@pedrouid/environment" "^1.0.1" + "@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1": version "5.1.1-v1" resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz#dbf733a965ca47b1973177dc0bb6c889edcfb129" @@ -1933,6 +1953,110 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@parcel/watcher-android-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz#c2c19a3c442313ff007d2d7a9c2c1dd3e1c9ca84" + integrity sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg== + +"@parcel/watcher-darwin-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz#c817c7a3b4f3a79c1535bfe54a1c2818d9ffdc34" + integrity sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA== + +"@parcel/watcher-darwin-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz#1a3f69d9323eae4f1c61a5f480a59c478d2cb020" + integrity sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg== + +"@parcel/watcher-freebsd-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz#0d67fef1609f90ba6a8a662bc76a55fc93706fc8" + integrity sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w== + +"@parcel/watcher-linux-arm-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz#ce5b340da5829b8e546bd00f752ae5292e1c702d" + integrity sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA== + +"@parcel/watcher-linux-arm64-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz#6d7c00dde6d40608f9554e73998db11b2b1ff7c7" + integrity sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA== + +"@parcel/watcher-linux-arm64-musl@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz#bd39bc71015f08a4a31a47cd89c236b9d6a7f635" + integrity sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA== + +"@parcel/watcher-linux-x64-glibc@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz#0ce29966b082fb6cdd3de44f2f74057eef2c9e39" + integrity sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg== + +"@parcel/watcher-linux-x64-musl@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz#d2ebbf60e407170bb647cd6e447f4f2bab19ad16" + integrity sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ== + +"@parcel/watcher-wasm@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-wasm/-/watcher-wasm-2.4.1.tgz#c4353e4fdb96ee14389856f7f6f6d21b7dcef9e1" + integrity sha512-/ZR0RxqxU/xxDGzbzosMjh4W6NdYFMqq2nvo2b8SLi7rsl/4jkL8S5stIikorNkdR50oVDvqb/3JT05WM+CRRA== + dependencies: + is-glob "^4.0.3" + micromatch "^4.0.5" + napi-wasm "^1.1.0" + +"@parcel/watcher-win32-arm64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz#eb4deef37e80f0b5e2f215dd6d7a6d40a85f8adc" + integrity sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg== + +"@parcel/watcher-win32-ia32@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz#94fbd4b497be39fd5c8c71ba05436927842c9df7" + integrity sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw== + +"@parcel/watcher-win32-x64@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz#4bf920912f67cae5f2d264f58df81abfea68dadf" + integrity sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A== + +"@parcel/watcher@^2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.4.1.tgz#a50275151a1bb110879c6123589dba90c19f1bf8" + integrity sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA== + dependencies: + detect-libc "^1.0.3" + is-glob "^4.0.3" + micromatch "^4.0.5" + node-addon-api "^7.0.0" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.4.1" + "@parcel/watcher-darwin-arm64" "2.4.1" + "@parcel/watcher-darwin-x64" "2.4.1" + "@parcel/watcher-freebsd-x64" "2.4.1" + "@parcel/watcher-linux-arm-glibc" "2.4.1" + "@parcel/watcher-linux-arm64-glibc" "2.4.1" + "@parcel/watcher-linux-arm64-musl" "2.4.1" + "@parcel/watcher-linux-x64-glibc" "2.4.1" + "@parcel/watcher-linux-x64-musl" "2.4.1" + "@parcel/watcher-win32-arm64" "2.4.1" + "@parcel/watcher-win32-ia32" "2.4.1" + "@parcel/watcher-win32-x64" "2.4.1" + +"@pedrouid/environment@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@pedrouid/environment/-/environment-1.0.1.tgz#858f0f8a057340e0b250398b75ead77d6f4342ec" + integrity sha512-HaW78NszGzRZd9SeoI3JD11JqY+lubnaOx7Pewj5pfjqWXOEATpeKIFb9Z4t2WBUK2iryiXX3lzWwmYWgUL0Ug== + +"@react-native-async-storage/async-storage@^1.22.3": + version "1.22.3" + resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.22.3.tgz#ad490236a9eda8ac68cffc12c738f20b1815464e" + integrity sha512-Ov3wjuqxHd62tLYfgTjxj77YRYWra3A4Fi8uICIPcePgNO2WkS5B0ADXt9e/JLzSCNqVlXCq4Fir/gHmZTU9ww== + dependencies: + merge-options "^3.0.4" + "@react-native-community/cli-clean@12.3.2": version "12.3.2" resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-12.3.2.tgz#d4f1730c3d22d816b4d513d330d5f3896a3f5921" @@ -2086,6 +2210,11 @@ prompts "^2.4.2" semver "^7.5.2" +"@react-native-community/netinfo@^11.3.1": + version "11.3.1" + resolved "https://registry.yarnpkg.com/@react-native-community/netinfo/-/netinfo-11.3.1.tgz#4539371a2f4bd402d932031be82c2449ed63a1a5" + integrity sha512-UBnJxyV0b7i9Moa97Av+HKho1ByzX0DtbJXzUQS5E3xhQs6P2D/Os0iw3ouy7joY1TVd6uIhplPbr7l1SJNaNQ== + "@react-native/assets-registry@0.73.1": version "0.73.1" resolved "https://registry.yarnpkg.com/@react-native/assets-registry/-/assets-registry-0.73.1.tgz#e2a6b73b16c183a270f338dc69c36039b3946e85" @@ -2347,6 +2476,140 @@ dependencies: "@sinonjs/commons" "^3.0.0" +"@stablelib/aead@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/aead/-/aead-1.0.1.tgz#c4b1106df9c23d1b867eb9b276d8f42d5fc4c0c3" + integrity sha512-q39ik6sxGHewqtO0nP4BuSe3db5G1fEJE8ukvngS2gLkBXyy6E7pLubhbYgnkDFv6V8cWaxcE4Xn0t6LWcJkyg== + +"@stablelib/binary@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/binary/-/binary-1.0.1.tgz#c5900b94368baf00f811da5bdb1610963dfddf7f" + integrity sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q== + dependencies: + "@stablelib/int" "^1.0.1" + +"@stablelib/bytes@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/bytes/-/bytes-1.0.1.tgz#0f4aa7b03df3080b878c7dea927d01f42d6a20d8" + integrity sha512-Kre4Y4kdwuqL8BR2E9hV/R5sOrUj6NanZaZis0V6lX5yzqC3hBuVSDXUIBqQv/sCpmuWRiHLwqiT1pqqjuBXoQ== + +"@stablelib/chacha20poly1305@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/chacha20poly1305/-/chacha20poly1305-1.0.1.tgz#de6b18e283a9cb9b7530d8767f99cde1fec4c2ee" + integrity sha512-MmViqnqHd1ymwjOQfghRKw2R/jMIGT3wySN7cthjXCBdO+qErNPUBnRzqNpnvIwg7JBCg3LdeCZZO4de/yEhVA== + dependencies: + "@stablelib/aead" "^1.0.1" + "@stablelib/binary" "^1.0.1" + "@stablelib/chacha" "^1.0.1" + "@stablelib/constant-time" "^1.0.1" + "@stablelib/poly1305" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/chacha@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/chacha/-/chacha-1.0.1.tgz#deccfac95083e30600c3f92803a3a1a4fa761371" + integrity sha512-Pmlrswzr0pBzDofdFuVe1q7KdsHKhhU24e8gkEwnTGOmlC7PADzLVxGdn2PoNVBBabdg0l/IfLKg6sHAbTQugg== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/constant-time@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/constant-time/-/constant-time-1.0.1.tgz#bde361465e1cf7b9753061b77e376b0ca4c77e35" + integrity sha512-tNOs3uD0vSJcK6z1fvef4Y+buN7DXhzHDPqRLSXUel1UfqMB1PWNsnnAezrKfEwTLpN0cGH2p9NNjs6IqeD0eg== + +"@stablelib/ed25519@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@stablelib/ed25519/-/ed25519-1.0.3.tgz#f8fdeb6f77114897c887bb6a3138d659d3f35996" + integrity sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg== + dependencies: + "@stablelib/random" "^1.0.2" + "@stablelib/sha512" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/hash@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hash/-/hash-1.0.1.tgz#3c944403ff2239fad8ebb9015e33e98444058bc5" + integrity sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg== + +"@stablelib/hkdf@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hkdf/-/hkdf-1.0.1.tgz#b4efd47fd56fb43c6a13e8775a54b354f028d98d" + integrity sha512-SBEHYE16ZXlHuaW5RcGk533YlBj4grMeg5TooN80W3NpcHRtLZLLXvKyX0qcRFxf+BGDobJLnwkvgEwHIDBR6g== + dependencies: + "@stablelib/hash" "^1.0.1" + "@stablelib/hmac" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/hmac@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hmac/-/hmac-1.0.1.tgz#3d4c1b8cf194cb05d28155f0eed8a299620a07ec" + integrity sha512-V2APD9NSnhVpV/QMYgCVMIYKiYG6LSqw1S65wxVoirhU/51ACio6D4yDVSwMzuTJXWZoVHbDdINioBwKy5kVmA== + dependencies: + "@stablelib/constant-time" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/int@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/int/-/int-1.0.1.tgz#75928cc25d59d73d75ae361f02128588c15fd008" + integrity sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w== + +"@stablelib/keyagreement@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/keyagreement/-/keyagreement-1.0.1.tgz#4612efb0a30989deb437cd352cee637ca41fc50f" + integrity sha512-VKL6xBwgJnI6l1jKrBAfn265cspaWBPAPEc62VBQrWHLqVgNRE09gQ/AnOEyKUWrrqfD+xSQ3u42gJjLDdMDQg== + dependencies: + "@stablelib/bytes" "^1.0.1" + +"@stablelib/poly1305@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/poly1305/-/poly1305-1.0.1.tgz#93bfb836c9384685d33d70080718deae4ddef1dc" + integrity sha512-1HlG3oTSuQDOhSnLwJRKeTRSAdFNVB/1djy2ZbS35rBSJ/PFqx9cf9qatinWghC2UbfOYD8AcrtbUQl8WoxabA== + dependencies: + "@stablelib/constant-time" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/random@^1.0.1", "@stablelib/random@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@stablelib/random/-/random-1.0.2.tgz#2dece393636489bf7e19c51229dd7900eddf742c" + integrity sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/sha256@1.0.1", "@stablelib/sha256@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/sha256/-/sha256-1.0.1.tgz#77b6675b67f9b0ea081d2e31bda4866297a3ae4f" + integrity sha512-GIIH3e6KH+91FqGV42Kcj71Uefd/QEe7Dy42sBTeqppXV95ggCcxLTk39bEr+lZfJmp+ghsR07J++ORkRELsBQ== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/sha512@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/sha512/-/sha512-1.0.1.tgz#6da700c901c2c0ceacbd3ae122a38ac57c72145f" + integrity sha512-13gl/iawHV9zvDKciLo1fQ8Bgn2Pvf7OV6amaRVKiq3pjQ3UmEpXxWiAfV8tYjUpeZroBxtyrwtdooQT/i3hzw== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/wipe@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/wipe/-/wipe-1.0.1.tgz#d21401f1d59ade56a62e139462a97f104ed19a36" + integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg== + +"@stablelib/x25519@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@stablelib/x25519/-/x25519-1.0.3.tgz#13c8174f774ea9f3e5e42213cbf9fc68a3c7b7fd" + integrity sha512-KnTbKmUhPhHavzobclVJQG5kuivH+qDLpe84iRqX3CLrKp881cF160JvXJ+hjn1aMyCwYOKeIZefIH/P5cJoRw== + dependencies: + "@stablelib/keyagreement" "^1.0.1" + "@stablelib/random" "^1.0.2" + "@stablelib/wipe" "^1.0.1" + "@types/babel__core@^7.1.14": version "7.20.5" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.5.tgz#3df15f27ba85319caa07ba08d0721889bb39c017" @@ -2567,6 +2830,244 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== +"@walletconnect/auth-client@2.1.2": + version "2.1.2" + resolved "https://registry.yarnpkg.com/@walletconnect/auth-client/-/auth-client-2.1.2.tgz#cee304fb0cdca76f6bf4aafac96ef9301862a7e8" + integrity sha512-ubJLn+vGb8sTdBFX6xAh4kjR5idrtS3RBngQWaJJJpEPBQmxMb8pM2q0FIRs8Is4K6jKy+uEhusMV+7ZBmTzjw== + dependencies: + "@ethersproject/hash" "^5.7.0" + "@ethersproject/transactions" "^5.7.0" + "@stablelib/random" "^1.0.2" + "@stablelib/sha256" "^1.0.1" + "@walletconnect/core" "^2.10.1" + "@walletconnect/events" "^1.0.1" + "@walletconnect/heartbeat" "^1.2.1" + "@walletconnect/jsonrpc-utils" "^1.0.8" + "@walletconnect/logger" "^2.0.1" + "@walletconnect/time" "^1.0.2" + "@walletconnect/utils" "^2.10.1" + events "^3.3.0" + isomorphic-unfetch "^3.1.0" + +"@walletconnect/core@2.11.2", "@walletconnect/core@^2.10.1": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-2.11.2.tgz#35286be92c645fa461fecc0dfe25de9f076fca8f" + integrity sha512-bB4SiXX8hX3/hyBfVPC5gwZCXCl+OPj+/EDVM71iAO3TDsh78KPbrVAbDnnsbHzZVHlsMohtXX3j5XVsheN3+g== + dependencies: + "@walletconnect/heartbeat" "1.2.1" + "@walletconnect/jsonrpc-provider" "1.0.13" + "@walletconnect/jsonrpc-types" "1.0.3" + "@walletconnect/jsonrpc-utils" "1.0.8" + "@walletconnect/jsonrpc-ws-connection" "1.0.14" + "@walletconnect/keyvaluestorage" "^1.1.1" + "@walletconnect/logger" "^2.0.1" + "@walletconnect/relay-api" "^1.0.9" + "@walletconnect/relay-auth" "^1.0.4" + "@walletconnect/safe-json" "^1.0.2" + "@walletconnect/time" "^1.0.2" + "@walletconnect/types" "2.11.2" + "@walletconnect/utils" "2.11.2" + events "^3.3.0" + isomorphic-unfetch "3.1.0" + lodash.isequal "4.5.0" + uint8arrays "^3.1.0" + +"@walletconnect/environment@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/environment/-/environment-1.0.1.tgz#1d7f82f0009ab821a2ba5ad5e5a7b8ae3b214cd7" + integrity sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg== + dependencies: + tslib "1.14.1" + +"@walletconnect/events@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/events/-/events-1.0.1.tgz#2b5f9c7202019e229d7ccae1369a9e86bda7816c" + integrity sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ== + dependencies: + keyvaluestorage-interface "^1.0.0" + tslib "1.14.1" + +"@walletconnect/heartbeat@1.2.1", "@walletconnect/heartbeat@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@walletconnect/heartbeat/-/heartbeat-1.2.1.tgz#afaa3a53232ae182d7c9cff41c1084472d8f32e9" + integrity sha512-yVzws616xsDLJxuG/28FqtZ5rzrTA4gUjdEMTbWB5Y8V1XHRmqq4efAxCw5ie7WjbXFSUyBHaWlMR+2/CpQC5Q== + dependencies: + "@walletconnect/events" "^1.0.1" + "@walletconnect/time" "^1.0.2" + tslib "1.14.1" + +"@walletconnect/jsonrpc-provider@1.0.13": + version "1.0.13" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.13.tgz#9a74da648d015e1fffc745f0c7d629457f53648b" + integrity sha512-K73EpThqHnSR26gOyNEL+acEex3P7VWZe6KE12ZwKzAt2H4e5gldZHbjsu2QR9cLeJ8AXuO7kEMOIcRv1QEc7g== + dependencies: + "@walletconnect/jsonrpc-utils" "^1.0.8" + "@walletconnect/safe-json" "^1.0.2" + tslib "1.14.1" + +"@walletconnect/jsonrpc-types@1.0.3", "@walletconnect/jsonrpc-types@^1.0.2", "@walletconnect/jsonrpc-types@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.3.tgz#65e3b77046f1a7fa8347ae02bc1b841abe6f290c" + integrity sha512-iIQ8hboBl3o5ufmJ8cuduGad0CQm3ZlsHtujv9Eu16xq89q+BG7Nh5VLxxUgmtpnrePgFkTwXirCTkwJH1v+Yw== + dependencies: + keyvaluestorage-interface "^1.0.0" + tslib "1.14.1" + +"@walletconnect/jsonrpc-utils@1.0.8", "@walletconnect/jsonrpc-utils@^1.0.6", "@walletconnect/jsonrpc-utils@^1.0.8": + version "1.0.8" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz#82d0cc6a5d6ff0ecc277cb35f71402c91ad48d72" + integrity sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw== + dependencies: + "@walletconnect/environment" "^1.0.1" + "@walletconnect/jsonrpc-types" "^1.0.3" + tslib "1.14.1" + +"@walletconnect/jsonrpc-ws-connection@1.0.14": + version "1.0.14" + resolved "https://registry.yarnpkg.com/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.14.tgz#eec700e74766c7887de2bd76c91a0206628732aa" + integrity sha512-Jsl6fC55AYcbkNVkwNM6Jo+ufsuCQRqViOQ8ZBPH9pRREHH9welbBiszuTLqEJiQcO/6XfFDl6bzCJIkrEi8XA== + dependencies: + "@walletconnect/jsonrpc-utils" "^1.0.6" + "@walletconnect/safe-json" "^1.0.2" + events "^3.3.0" + ws "^7.5.1" + +"@walletconnect/keyvaluestorage@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz#dd2caddabfbaf80f6b8993a0704d8b83115a1842" + integrity sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA== + dependencies: + "@walletconnect/safe-json" "^1.0.1" + idb-keyval "^6.2.1" + unstorage "^1.9.0" + +"@walletconnect/logger@2.0.1", "@walletconnect/logger@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/logger/-/logger-2.0.1.tgz#7f489b96e9a1ff6bf3e58f0fbd6d69718bf844a8" + integrity sha512-SsTKdsgWm+oDTBeNE/zHxxr5eJfZmE9/5yp/Ku+zJtcTAjELb3DXueWkDXmE9h8uHIbJzIb5wj5lPdzyrjT6hQ== + dependencies: + pino "7.11.0" + tslib "1.14.1" + +"@walletconnect/react-native-compat@^2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/react-native-compat/-/react-native-compat-2.11.2.tgz#bd0d3ab77589050fb4cb750261e257455c09af46" + integrity sha512-Ozz0vPyWDBIKQfjHEwt3OKK6EslDMaTUVpgOaGfZjQ8gw/vuOnYOybn1IpgAQjuPfIQSkPebtE36dxlJyGPzOg== + dependencies: + events "3.3.0" + fast-text-encoding "^1.0.6" + react-native-url-polyfill "^2.0.0" + +"@walletconnect/relay-api@^1.0.9": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@walletconnect/relay-api/-/relay-api-1.0.9.tgz#f8c2c3993dddaa9f33ed42197fc9bfebd790ecaf" + integrity sha512-Q3+rylJOqRkO1D9Su0DPE3mmznbAalYapJ9qmzDgK28mYF9alcP3UwG/og5V7l7CFOqzCLi7B8BvcBUrpDj0Rg== + dependencies: + "@walletconnect/jsonrpc-types" "^1.0.2" + tslib "1.14.1" + +"@walletconnect/relay-auth@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@walletconnect/relay-auth/-/relay-auth-1.0.4.tgz#0b5c55c9aa3b0ef61f526ce679f3ff8a5c4c2c7c" + integrity sha512-kKJcS6+WxYq5kshpPaxGHdwf5y98ZwbfuS4EE/NkQzqrDFm5Cj+dP8LofzWvjrrLkZq7Afy7WrQMXdLy8Sx7HQ== + dependencies: + "@stablelib/ed25519" "^1.0.2" + "@stablelib/random" "^1.0.1" + "@walletconnect/safe-json" "^1.0.1" + "@walletconnect/time" "^1.0.2" + tslib "1.14.1" + uint8arrays "^3.0.0" + +"@walletconnect/safe-json@^1.0.1", "@walletconnect/safe-json@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@walletconnect/safe-json/-/safe-json-1.0.2.tgz#7237e5ca48046e4476154e503c6d3c914126fa77" + integrity sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA== + dependencies: + tslib "1.14.1" + +"@walletconnect/sign-client@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/sign-client/-/sign-client-2.11.2.tgz#855609653855f0d23b0502cdbdcf43402e34c459" + integrity sha512-MfBcuSz2GmMH+P7MrCP46mVE5qhP0ZyWA0FyIH6/WuxQ6G+MgKsGfaITqakpRPsykWOJq8tXMs3XvUPDU413OQ== + dependencies: + "@walletconnect/core" "2.11.2" + "@walletconnect/events" "^1.0.1" + "@walletconnect/heartbeat" "1.2.1" + "@walletconnect/jsonrpc-utils" "1.0.8" + "@walletconnect/logger" "^2.0.1" + "@walletconnect/time" "^1.0.2" + "@walletconnect/types" "2.11.2" + "@walletconnect/utils" "2.11.2" + events "^3.3.0" + +"@walletconnect/time@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@walletconnect/time/-/time-1.0.2.tgz#6c5888b835750ecb4299d28eecc5e72c6d336523" + integrity sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g== + dependencies: + tslib "1.14.1" + +"@walletconnect/types@2.11.2": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-2.11.2.tgz#d0359dd4106fcaa1634241a00428d3ea08d0d3c7" + integrity sha512-p632MFB+lJbip2cvtXPBQslpUdiw1sDtQ5y855bOlAGquay+6fZ4h1DcDePeKQDQM3P77ax2a9aNPZxV6y/h1Q== + dependencies: + "@walletconnect/events" "^1.0.1" + "@walletconnect/heartbeat" "1.2.1" + "@walletconnect/jsonrpc-types" "1.0.3" + "@walletconnect/keyvaluestorage" "^1.1.1" + "@walletconnect/logger" "^2.0.1" + events "^3.3.0" + +"@walletconnect/utils@2.11.2", "@walletconnect/utils@^2.10.1": + version "2.11.2" + resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-2.11.2.tgz#dee0f19adf5e38543612cbe9fa4de7ed28eb7e85" + integrity sha512-LyfdmrnZY6dWqlF4eDrx5jpUwsB2bEPjoqR5Z6rXPiHJKUOdJt7az+mNOn5KTSOlRpd1DmozrBrWr+G9fFLYVw== + dependencies: + "@stablelib/chacha20poly1305" "1.0.1" + "@stablelib/hkdf" "1.0.1" + "@stablelib/random" "^1.0.2" + "@stablelib/sha256" "1.0.1" + "@stablelib/x25519" "^1.0.3" + "@walletconnect/relay-api" "^1.0.9" + "@walletconnect/safe-json" "^1.0.2" + "@walletconnect/time" "^1.0.2" + "@walletconnect/types" "2.11.2" + "@walletconnect/window-getters" "^1.0.1" + "@walletconnect/window-metadata" "^1.0.1" + detect-browser "5.3.0" + query-string "7.1.3" + uint8arrays "^3.1.0" + +"@walletconnect/web3wallet@^1.10.2": + version "1.10.2" + resolved "https://registry.yarnpkg.com/@walletconnect/web3wallet/-/web3wallet-1.10.2.tgz#5887642773e6e1b88d1bfb159a00ec45b31f762a" + integrity sha512-FbWsJwhihppl6poJ0+0WCkjXZDVdb11KJiS/AJt+qyNheIKQ7z6NfCnCnnKPBEpNNoUPclcFo4b/BmdFo2YlMw== + dependencies: + "@walletconnect/auth-client" "2.1.2" + "@walletconnect/core" "2.11.2" + "@walletconnect/jsonrpc-provider" "1.0.13" + "@walletconnect/jsonrpc-utils" "1.0.8" + "@walletconnect/logger" "2.0.1" + "@walletconnect/sign-client" "2.11.2" + "@walletconnect/types" "2.11.2" + "@walletconnect/utils" "2.11.2" + +"@walletconnect/window-getters@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/window-getters/-/window-getters-1.0.1.tgz#f36d1c72558a7f6b87ecc4451fc8bd44f63cbbdc" + integrity sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q== + dependencies: + tslib "1.14.1" + +"@walletconnect/window-metadata@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@walletconnect/window-metadata/-/window-metadata-1.0.1.tgz#2124f75447b7e989e4e4e1581d55d25bc75f7be5" + integrity sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA== + dependencies: + "@walletconnect/window-getters" "^1.0.1" + tslib "1.14.1" + "@yarnpkg/lockfile@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" @@ -2592,7 +3093,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.11.3, acorn@^8.8.2, acorn@^8.9.0: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -2662,7 +3163,7 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== -anymatch@^3.0.3: +anymatch@^3.0.3, anymatch@^3.1.3, anymatch@~3.1.2: version "3.1.3" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== @@ -2800,6 +3301,11 @@ at-least-node@^1.0.0: resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" integrity sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + available-typed-arrays@^1.0.5, available-typed-arrays@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz#ac812d8ce5a6b976d738e1c45f08d0b00bc7d725" @@ -2927,6 +3433,11 @@ bech32@1.1.4, bech32@^1.1.4: resolved "https://registry.yarnpkg.com/bech32/-/bech32-1.1.4.tgz#e38c9f37bf179b8eb16ae3a772b40c356d4832e9" integrity sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ== +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -2961,7 +3472,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== @@ -3054,7 +3565,7 @@ buffer-xor@^1.0.3: resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" integrity sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ== -buffer@^5.5.0: +buffer@^5.4.3, buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== @@ -3137,6 +3648,21 @@ char-regex@^1.0.2: resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== +chokidar@^3.5.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + chrome-launcher@^0.15.2: version "0.15.2" resolved "https://registry.yarnpkg.com/chrome-launcher/-/chrome-launcher-0.15.2.tgz#4e6404e32200095fdce7f6a1e1004f9bd36fa5da" @@ -3177,6 +3703,13 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" +citty@^0.1.5, citty@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/citty/-/citty-0.1.6.tgz#0f7904da1ed4625e1a9ea7e0fa780981aab7c5e4" + integrity sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ== + dependencies: + consola "^3.2.3" + cjs-module-lexer@^1.0.0: version "1.2.3" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107" @@ -3194,6 +3727,15 @@ cli-spinners@^2.5.0: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== +clipboardy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-4.0.0.tgz#e73ced93a76d19dd379ebf1f297565426dffdca1" + integrity sha512-5mOlNS0mhX0707P2I0aZ2V/cmHUEO/fL7VFLqszkhUsxt7RwnmrInf/eEQKlf5GzvYeHIjT+Ov1HRfNmymlG0w== + dependencies: + execa "^8.0.1" + is-wsl "^3.1.0" + is64bit "^2.0.0" + cliui@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" @@ -3235,6 +3777,11 @@ clone@^1.0.2: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg== +cluster-key-slot@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" + integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== + co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3345,11 +3892,21 @@ connect@^3.6.5: parseurl "~1.3.3" utils-merge "1.0.1" +consola@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/consola/-/consola-3.2.3.tgz#0741857aa88cfa0d6fd53f1cff0375136e98502f" + integrity sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-es@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.0.0.tgz#4759684af168dfc54365b2c2dda0a8d7ee1e4865" + integrity sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ== + core-js-compat@^3.31.0, core-js-compat@^3.34.0: version "3.35.1" resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.35.1.tgz#215247d7edb9e830efa4218ff719beb2803555e2" @@ -3425,6 +3982,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +crossws@^0.2.0, crossws@^0.2.2: + version "0.2.4" + resolved "https://registry.yarnpkg.com/crossws/-/crossws-0.2.4.tgz#82a8b518bff1018ab1d21ced9e35ffbe1681ad03" + integrity sha512-DAxroI2uSOgUKLz00NX6A8U/8EE3SZHmIND+10jkVSaypvyt57J5JEOxAQOL6lQxyzi/wZbTIwssU1uy69h5Vg== + crypto-browserify@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -3521,11 +4083,21 @@ define-properties@^1.1.3, define-properties@^1.2.0, define-properties@^1.2.1: has-property-descriptors "^1.0.0" object-keys "^1.1.1" +defu@^6.1.3, defu@^6.1.4: + version "6.1.4" + resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" + integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== + denodeify@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" integrity sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg== +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -3548,11 +4120,26 @@ des.js@^1.0.0: inherits "^2.0.1" minimalistic-assert "^1.0.0" +destr@^2.0.1, destr@^2.0.2, destr@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449" + integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ== + destroy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-browser@5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/detect-browser/-/detect-browser-5.3.0.tgz#9705ef2bddf46072d0f7265a1fe300e36fe7ceca" + integrity sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w== + +detect-libc@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg== + detect-newline@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" @@ -3593,6 +4180,16 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +duplexify@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" + integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== + dependencies: + end-of-stream "^1.4.1" + inherits "^2.0.3" + readable-stream "^3.1.1" + stream-shift "^1.0.0" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -3631,6 +4228,13 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + envinfo@^7.10.0: version "7.11.1" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.11.1.tgz#2ffef77591057081b0129a8fd8cf6118da1b94e1" @@ -3970,7 +4574,7 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== -ethers@5: +ethers@5.7.2: version "5.7.2" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== @@ -4011,7 +4615,7 @@ event-target-shim@^5.0.0, event-target-shim@^5.0.1: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -events@^3.3.0: +events@3.3.0, events@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -4039,6 +4643,21 @@ execa@^5.0.0, execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +execa@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-8.0.1.tgz#51f6a5943b580f963c3ca9c6321796db8cc39b8c" + integrity sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg== + dependencies: + cross-spawn "^7.0.3" + get-stream "^8.0.1" + human-signals "^5.0.0" + is-stream "^3.0.0" + merge-stream "^2.0.0" + npm-run-path "^5.1.0" + onetime "^6.0.0" + signal-exit "^4.1.0" + strip-final-newline "^3.0.0" + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -4091,6 +4710,16 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-redact@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634" + integrity sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ== + +fast-text-encoding@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz#0aa25f7f638222e3396d72bf936afcf1d42d6867" + integrity sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w== + fast-xml-parser@^4.0.12, fast-xml-parser@^4.2.4: version "4.3.4" resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.4.tgz#385cc256ad7bbc57b91515a38a22502a9e1fca0d" @@ -4256,7 +4885,7 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@^2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== @@ -4307,11 +4936,21 @@ get-package-type@^0.1.0: resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== +get-port-please@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/get-port-please/-/get-port-please-3.1.2.tgz#502795e56217128e4183025c89a48c71652f4e49" + integrity sha512-Gxc29eLs1fbn6LQ4jSU4vXjlwyZhF5HsGuMAa7gqBP4Rw4yxxltyDUuF5MBclFzDTXO+ACchGQoeela4DSfzdQ== + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== +get-stream@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-8.0.1.tgz#def9dfd71742cd7754a7761ed43749a27d02eca2" + integrity sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA== + get-symbol-description@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6" @@ -4320,7 +4959,7 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -4405,6 +5044,22 @@ graphemer@^1.4.0: resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== +h3@^1.10.2, h3@^1.8.2: + version "1.11.1" + resolved "https://registry.yarnpkg.com/h3/-/h3-1.11.1.tgz#e9414ae6f2a076a345ea07256b320edb29bab9f7" + integrity sha512-AbaH6IDnZN6nmbnJOH72y3c5Wwh9P97soSVdGSBbcDACRdkC0FEWf25pzx4f/NuOCK6quHmW18yF2Wx+G4Zi1A== + dependencies: + cookie-es "^1.0.0" + crossws "^0.2.2" + defu "^6.1.4" + destr "^2.0.3" + iron-webcrypto "^1.0.0" + ohash "^1.1.3" + radix3 "^1.1.0" + ufo "^1.4.0" + uncrypto "^0.1.3" + unenv "^1.9.0" + has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" @@ -4531,11 +5186,26 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-shutdown@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/http-shutdown/-/http-shutdown-1.2.2.tgz#41bc78fc767637c4c95179bc492f312c0ae64c5f" + integrity sha512-S9wWkJ/VSY9/k4qcjG318bqJNruzE4HySUhFYknwmu6LBP97KLLfwNf+n4V1BHurvFNkSKLFnK/RsuUnRTf9Vw== + human-signals@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== +human-signals@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-5.0.0.tgz#42665a284f9ae0dade3ba41ebc37eb4b852f3a28" + integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== + +idb-keyval@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-6.2.1.tgz#94516d625346d16f56f3b33855da11bfded2db33" + integrity sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg== + ieee754@^1.1.13, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" @@ -4611,11 +5281,31 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +ioredis@^5.3.2: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.3.2.tgz#9139f596f62fc9c72d873353ac5395bcf05709f7" + integrity sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA== + dependencies: + "@ioredis/commands" "^1.1.1" + cluster-key-slot "^1.1.0" + debug "^4.3.4" + denque "^2.1.0" + lodash.defaults "^4.2.0" + lodash.isarguments "^3.1.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.1.0" + ip@^1.1.5: version "1.1.8" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== +iron-webcrypto@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/iron-webcrypto/-/iron-webcrypto-1.0.0.tgz#e3b689c0c61b434a0a4cb82d0aeabbc8b672a867" + integrity sha512-anOK1Mktt8U1Xi7fCM3RELTuYbnFikQY5VtrDj7kPgpejV7d43tWKhzgioO0zpkazLEL/j/iayRqnJhrGfqUsg== + is-array-buffer@^3.0.2, is-array-buffer@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" @@ -4648,6 +5338,13 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" @@ -4685,6 +5382,11 @@ is-docker@^2.0.0: resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== +is-docker@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" + integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -4719,13 +5421,20 @@ is-generator-function@^1.0.10: dependencies: has-tostringtag "^1.0.0" -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== dependencies: is-extglob "^2.1.1" +is-inside-container@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" + integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== + dependencies: + is-docker "^3.0.0" + is-interactive@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" @@ -4758,6 +5467,11 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" @@ -4790,6 +5504,11 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== +is-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-3.0.0.tgz#e6bfd7aa6bef69f4f472ce9bb681e3e57b4319ac" + integrity sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA== + is-string@^1.0.5, is-string@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd" @@ -4848,6 +5567,20 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: dependencies: is-docker "^2.0.0" +is-wsl@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" + integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== + dependencies: + is-inside-container "^1.0.0" + +is64bit@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is64bit/-/is64bit-2.0.0.tgz#198c627cbcb198bbec402251f88e5e1a51236c07" + integrity sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw== + dependencies: + system-architecture "^0.1.0" + isarray@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723" @@ -4868,6 +5601,14 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== +isomorphic-unfetch@3.1.0, isomorphic-unfetch@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f" + integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q== + dependencies: + node-fetch "^2.6.1" + unfetch "^4.2.0" + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -5290,6 +6031,11 @@ jest@^29.6.3: import-local "^3.0.2" jest-cli "^29.7.0" +jiti@^1.21.0: + version "1.21.0" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d" + integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== + joi@^17.2.1: version "17.12.1" resolved "https://registry.yarnpkg.com/joi/-/joi-17.12.1.tgz#3347ecf4cd3301962d42191c021b165eef1f395b" @@ -5411,6 +6157,11 @@ json5@^2.1.1, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +jsonc-parser@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" + integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -5449,6 +6200,11 @@ keyv@^4.5.3: dependencies: json-buffer "3.0.1" +keyvaluestorage-interface@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz#13ebdf71f5284ad54be94bd1ad9ed79adad515ff" + integrity sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g== + kind-of@^6.0.2: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -5504,6 +6260,30 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +listhen@^1.5.5: + version "1.7.2" + resolved "https://registry.yarnpkg.com/listhen/-/listhen-1.7.2.tgz#66b81740692269d5d8cafdc475020f2fc51afbae" + integrity sha512-7/HamOm5YD9Wb7CFgAZkKgVPA96WwhcTQoqtm2VTZGVbVVn3IWKRBTgrU7cchA3Q8k9iCsG8Osoi9GX4JsGM9g== + dependencies: + "@parcel/watcher" "^2.4.1" + "@parcel/watcher-wasm" "^2.4.1" + citty "^0.1.6" + clipboardy "^4.0.0" + consola "^3.2.3" + crossws "^0.2.0" + defu "^6.1.4" + get-port-please "^3.1.2" + h3 "^1.10.2" + http-shutdown "^1.2.2" + jiti "^1.21.0" + mlly "^1.6.1" + node-forge "^1.3.1" + pathe "^1.1.2" + std-env "^3.7.0" + ufo "^1.4.0" + untun "^0.1.3" + uqr "^0.1.2" + locate-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" @@ -5531,6 +6311,21 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.isarguments@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" + integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== + +lodash.isequal@4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -5570,6 +6365,11 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lru-cache@^10.0.2: + version "10.2.0" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" + integrity sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -5625,6 +6425,13 @@ memoize-one@^5.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== +merge-options@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/merge-options/-/merge-options-3.0.4.tgz#84709c2aa2a4b24c1981f66c179fe5565cc6dbb7" + integrity sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ== + dependencies: + is-plain-obj "^2.1.0" + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -5871,7 +6678,7 @@ metro@0.80.5, metro@^0.80.3: ws "^7.5.1" yargs "^17.6.2" -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -5909,11 +6716,21 @@ mime@^2.4.1: resolved "https://registry.yarnpkg.com/mime/-/mime-2.6.0.tgz#a2a682a95cd4d0cb1d6257e28f83da7e35800367" integrity sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg== +mime@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" + integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== + mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +mimic-fn@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" + integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -5955,6 +6772,21 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mlly@^1.2.0, mlly@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/mlly/-/mlly-1.6.1.tgz#0983067dc3366d6314fc5e12712884e6978d028f" + integrity sha512-vLgaHvaeunuOXHSmEbZ9izxPx3USsk8KCQ8iC+aTlp5sKRSoZvwhHh5L9VbKSaVC6sJDqbyohIS76E2VmHIPAA== + dependencies: + acorn "^8.11.3" + pathe "^1.1.2" + pkg-types "^1.0.3" + ufo "^1.3.2" + +mri@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" + integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -5970,11 +6802,21 @@ ms@2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +multiformats@^9.4.2: + version "9.9.0" + resolved "https://registry.yarnpkg.com/multiformats/-/multiformats-9.9.0.tgz#c68354e7d21037a8f1f8833c8ccd68618e8f1d37" + integrity sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg== + nanoid@^3.1.23: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +napi-wasm@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/napi-wasm/-/napi-wasm-1.1.0.tgz#bbe617823765ae9c1bc12ff5942370eae7b2ba4e" + integrity sha512-lHwIAJbmLSjF9VDRm9GoVOy9AGp3aIvkjv+Kvz9h16QR3uSVYH78PNQUnT2U4X53mhlnV2M7wrhibQ3GHicDmg== + natural-compare-lite@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" @@ -6005,6 +6847,11 @@ node-abort-controller@^3.1.1: resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== +node-addon-api@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-7.1.0.tgz#71f609369379c08e251c558527a107107b5e0fdb" + integrity sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g== + node-dir@^0.1.17: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -6012,13 +6859,23 @@ node-dir@^0.1.17: dependencies: minimatch "^3.0.2" -node-fetch@^2.2.0, node-fetch@^2.6.0: +node-fetch-native@^1.4.0, node-fetch-native@^1.4.1, node-fetch-native@^1.6.1: + version "1.6.2" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.2.tgz#f439000d972eb0c8a741b65dcda412322955e1c6" + integrity sha512-69mtXOFZ6hSkYiXAVB5SqaRvrbITC/NPyqv7yuu/qw0nmgPyYbIMYYNIDhNtwPrzk0ptrimrLz/hhjvm4w5Z+w== + +node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.1: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" +node-forge@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" + integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== + node-int64@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" @@ -6034,7 +6891,7 @@ node-stream-zip@^1.9.1: resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== -normalize-path@^3.0.0: +normalize-path@^3.0.0, normalize-path@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== @@ -6046,6 +6903,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +npm-run-path@^5.1.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-5.3.0.tgz#e23353d0ebb9317f174e93417e4a4d82d0249e9f" + integrity sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ== + dependencies: + path-key "^4.0.0" + nullthrows@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -6116,6 +6980,25 @@ object.values@^1.1.6: define-properties "^1.2.0" es-abstract "^1.22.1" +ofetch@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.3.3.tgz#588cb806a28e5c66c2c47dd8994f9059a036d8c0" + integrity sha512-s1ZCMmQWXy4b5K/TW9i/DtiN8Ku+xCiHcjQ6/J/nDdssirrQNOoB165Zu8EqLMA2lln1JUth9a0aW9Ap2ctrUg== + dependencies: + destr "^2.0.1" + node-fetch-native "^1.4.0" + ufo "^1.3.0" + +ohash@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.3.tgz#f12c3c50bfe7271ce3fd1097d42568122ccdcf07" + integrity sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw== + +on-exit-leak-free@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" + integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -6135,7 +7018,7 @@ on-headers@~1.0.2: resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== -once@^1.3.0: +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -6149,6 +7032,13 @@ onetime@^5.1.0, onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" +onetime@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-6.0.0.tgz#7c24c18ed1fd2e9bca4bd26806a33613c77d34b4" + integrity sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ== + dependencies: + mimic-fn "^4.0.0" + open@^6.2.0: version "6.4.0" resolved "https://registry.yarnpkg.com/open/-/open-6.4.0.tgz#5c13e96d0dc894686164f18965ecfe889ecfc8a9" @@ -6318,6 +7208,11 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-key@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-4.0.0.tgz#295588dc3aee64154f877adb9d780b81c554bf18" + integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== + path-parse@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" @@ -6328,6 +7223,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathe@^1.1.0, pathe@^1.1.1, pathe@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" + integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== + pbkdf2@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" @@ -6344,7 +7244,7 @@ picocolors@^1.0.0: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== -picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -6354,6 +7254,36 @@ pify@^4.0.1: resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g== +pino-abstract-transport@v0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz#4b54348d8f73713bfd14e3dc44228739aa13d9c0" + integrity sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ== + dependencies: + duplexify "^4.1.2" + split2 "^4.0.0" + +pino-std-serializers@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz#1791ccd2539c091ae49ce9993205e2cd5dbba1e2" + integrity sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q== + +pino@7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-7.11.0.tgz#0f0ea5c4683dc91388081d44bff10c83125066f6" + integrity sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.0.0" + on-exit-leak-free "^0.2.0" + pino-abstract-transport v0.5.0 + pino-std-serializers "^4.0.0" + process-warning "^1.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.1.0" + safe-stable-stringify "^2.1.0" + sonic-boom "^2.2.1" + thread-stream "^0.15.1" + pirates@^4.0.4, pirates@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9" @@ -6373,6 +7303,15 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-types@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-1.0.3.tgz#988b42ab19254c01614d13f4f65a2cfc7880f868" + integrity sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A== + dependencies: + jsonc-parser "^3.2.0" + mlly "^1.2.0" + pathe "^1.1.0" + pkg-up@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5" @@ -6426,6 +7365,11 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process-warning@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" + integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== + promise@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/promise/-/promise-8.3.0.tgz#8cb333d1edeb61ef23869fbb8a4ea0279ab60e0a" @@ -6462,7 +7406,7 @@ public-encrypt@^4.0.0: randombytes "^2.0.1" safe-buffer "^5.1.2" -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -6472,7 +7416,7 @@ pure-rand@^6.0.0: resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7" integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA== -query-string@^7.1.3: +query-string@7.1.3, query-string@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/query-string/-/query-string-7.1.3.tgz#a1cf90e994abb113a325804a972d98276fe02328" integrity sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg== @@ -6494,6 +7438,16 @@ queue@6.0.2: dependencies: inherits "~2.0.3" +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + +radix3@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/radix3/-/radix3-1.1.0.tgz#9745df67a49c522e94a33d0a93cf743f104b6e0d" + integrity sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A== + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -6596,6 +7550,13 @@ react-native-screens@^3.29.0: react-freeze "^1.0.0" warn-once "^0.1.0" +react-native-url-polyfill@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-2.0.0.tgz#db714520a2985cff1d50ab2e66279b9f91ffd589" + integrity sha512-My330Do7/DvKnEvwQc0WdcBnFPploYKp9CYlefDXzIdEaA+PAhDYllkvGeEroEzvc4Kzzj2O4yVdz8v6fjRvhA== + dependencies: + whatwg-url-without-unicode "8.0.0-3" + react-native-vector-icons@^10.0.3: version "10.0.3" resolved "https://registry.yarnpkg.com/react-native-vector-icons/-/react-native-vector-icons-10.0.3.tgz#369824a3b17994b2cd65edbaa32dbf9540d49678" @@ -6687,7 +7648,7 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" -readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.5.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -6709,6 +7670,13 @@ readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + readline@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/readline/-/readline-1.3.0.tgz#c580d77ef2cfc8752b132498060dc9793a7ac01c" @@ -6719,6 +7687,11 @@ readonly-date@^1.0.0: resolved "https://registry.yarnpkg.com/readonly-date/-/readonly-date-1.0.0.tgz#5af785464d8c7d7c40b9d738cbde8c646f97dcd9" integrity sha512-tMKIV7hlk0h4mO3JTmmVuIlJVXjKk3Sep9Bf5OH0O+758ruuVkUy2J9SttDLm91IEX/WHlXPSpxMGjPj4beMIQ== +real-require@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381" + integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg== + recast@^0.21.0: version "0.21.5" resolved "https://registry.yarnpkg.com/recast/-/recast-0.21.5.tgz#e8cd22bb51bcd6130e54f87955d33a2b2e57b495" @@ -6729,6 +7702,18 @@ recast@^0.21.0: source-map "~0.6.1" tslib "^2.0.1" +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== + dependencies: + redis-errors "^1.0.0" + reflect.getprototypeof@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.5.tgz#e0bd28b597518f16edaf9c0e292c631eb13e0674" @@ -6937,6 +7922,11 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.2.2" is-regex "^1.1.4" +safe-stable-stringify@^2.1.0: + version "2.4.3" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" + integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== + safer-buffer@^2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -7088,6 +8078,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -7119,6 +8114,13 @@ slice-ansi@^2.0.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +sonic-boom@^2.2.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" + integrity sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg== + dependencies: + atomic-sleep "^1.0.0" + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" @@ -7155,6 +8157,11 @@ split-on-first@^1.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== +split2@^4.0.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -7179,6 +8186,11 @@ stacktrace-parser@^0.1.10: dependencies: type-fest "^0.7.1" +standard-as-callback@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" + integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== + statuses@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" @@ -7189,6 +8201,11 @@ statuses@~1.5.0: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA== +std-env@^3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" + integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== + stream-browserify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" @@ -7197,6 +8214,11 @@ stream-browserify@^3.0.0: inherits "~2.0.4" readable-stream "^3.5.0" +stream-shift@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.3.tgz#85b8fab4d71010fc3ba8772e8046cc49b8a3864b" + integrity sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ== + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" @@ -7304,6 +8326,11 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-final-newline@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-3.0.0.tgz#52894c313fbff318835280aed60ff71ebf12b8fd" + integrity sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw== + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -7345,6 +8372,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +system-architecture@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d" + integrity sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA== + temp-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" @@ -7386,6 +8418,13 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thread-stream@^0.15.1: + version "0.15.2" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" + integrity sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA== + dependencies: + real-require "^0.1.0" + throat@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" @@ -7433,7 +8472,7 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== -tslib@^1.8.1: +tslib@1.14.1, tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -7521,6 +8560,18 @@ typescript@5.0.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.0.4.tgz#b217fd20119bd61a94d4011274e0ab369058da3b" integrity sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw== +ufo@^1.3.0, ufo@^1.3.1, ufo@^1.3.2, ufo@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.4.0.tgz#39845b31be81b4f319ab1d99fd20c56cac528d32" + integrity sha512-Hhy+BhRBleFjpJ2vchUNN40qgkh0366FWJGqVLYBHev0vpHTrXSA0ryT+74UiW6KWsldNurQMKGqCm1M2zBciQ== + +uint8arrays@^3.0.0, uint8arrays@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/uint8arrays/-/uint8arrays-3.1.1.tgz#2d8762acce159ccd9936057572dade9459f65ae0" + integrity sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg== + dependencies: + multiformats "^9.4.2" + unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -7531,11 +8582,32 @@ unbox-primitive@^1.0.2: has-symbols "^1.0.3" which-boxed-primitive "^1.0.2" +uncrypto@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/uncrypto/-/uncrypto-0.1.3.tgz#e1288d609226f2d02d8d69ee861fa20d8348ef2b" + integrity sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q== + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +unenv@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.9.0.tgz#469502ae85be1bd3a6aa60f810972b1a904ca312" + integrity sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g== + dependencies: + consola "^3.2.3" + defu "^6.1.3" + mime "^3.0.0" + node-fetch-native "^1.6.1" + pathe "^1.1.1" + +unfetch@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/unfetch/-/unfetch-4.2.0.tgz#7e21b0ef7d363d8d9af0fb929a5555f6ef97a3be" + integrity sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -7574,6 +8646,32 @@ unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +unstorage@^1.9.0: + version "1.10.1" + resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.10.1.tgz#bf8cc00a406e40a6293e893da9807057d95875b0" + integrity sha512-rWQvLRfZNBpF+x8D3/gda5nUCQL2PgXy2jNG4U7/Rc9BGEv9+CAJd0YyGCROUBKs9v49Hg8huw3aih5Bf5TAVw== + dependencies: + anymatch "^3.1.3" + chokidar "^3.5.3" + destr "^2.0.2" + h3 "^1.8.2" + ioredis "^5.3.2" + listhen "^1.5.5" + lru-cache "^10.0.2" + mri "^1.2.0" + node-fetch-native "^1.4.1" + ofetch "^1.3.3" + ufo "^1.3.1" + +untun@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/untun/-/untun-0.1.3.tgz#5d10dee37a3a5737ff03d158be877dae0a0e58a6" + integrity sha512-4luGP9LMYszMRZwsvyUd9MrxgEGZdZuZgpVQHEEX0lCYFESasVRvZd0EYpCkOIbJKHMuv0LskpXc/8Un+MJzEQ== + dependencies: + citty "^0.1.5" + consola "^3.2.3" + pathe "^1.1.1" + update-browserslist-db@^1.0.13: version "1.0.13" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" @@ -7582,6 +8680,11 @@ update-browserslist-db@^1.0.13: escalade "^3.1.1" picocolors "^1.0.0" +uqr@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/uqr/-/uqr-0.1.2.tgz#5c6cd5dcff9581f9bb35b982cb89e2c483a41d7d" + integrity sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA== + uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -7647,11 +8750,25 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" + integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== + whatwg-fetch@^3.0.0: version "3.6.20" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== +whatwg-url-without-unicode@8.0.0-3: + version "8.0.0-3" + resolved "https://registry.yarnpkg.com/whatwg-url-without-unicode/-/whatwg-url-without-unicode-8.0.0-3.tgz#ab6df4bf6caaa6c85a59f6e82c026151d4bb376b" + integrity sha512-HoKuzZrUlgpz35YO27XgD28uh/WJH4B0+3ttFqRo//lmq+9T/mIOJ6kqmINI9HpUpz1imRC/nR/lxKpJiv0uig== + dependencies: + buffer "^5.4.3" + punycode "^2.1.1" + webidl-conversions "^5.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" -- 2.45.2 From 276cb3695a6a5f8124d97c8f730037fe8ae094f9 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Wed, 6 Mar 2024 11:08:02 +0530 Subject: [PATCH 03/64] Use existing ethereum accounts while connecting wallet with dapp (#39) * Use existing accounts while pairing with dapp * Listen for events emitted from dapp on every render * Handle review changes --- App.tsx | 49 +++++++++++----- components/PairingModal.tsx | 8 +-- components/SignModal.tsx | 6 +- types.ts | 2 + utils/wallet-connect/EIP155Requests.ts | 57 ++++-------------- utils/wallet-connect/EIP155Wallet.ts | 64 --------------------- utils/wallet-connect/Helpers.ts | 18 +++++- utils/wallet-connect/WalletConnectUtils.tsx | 13 +++-- 8 files changed, 80 insertions(+), 137 deletions(-) delete mode 100644 utils/wallet-connect/EIP155Wallet.ts diff --git a/App.tsx b/App.tsx index f9cc566..4e2147c 100644 --- a/App.tsx +++ b/App.tsx @@ -12,16 +12,18 @@ import QRScanner from './components/QRScanner'; import PairingModal from './components/PairingModal'; import SignModal from './components/SignModal'; import WalletConnect from './components/WalletConnect'; - import { StackParamsList } from './types'; import useInitialization, { web3wallet, } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; +import { retrieveAccounts } from './utils/accounts'; const Stack = createNativeStackNavigator(); const App = (): React.JSX.Element => { + useInitialization(); + const [modalVisible, setModalVisible] = useState(false); //TODO: Remove any const [currentProposal, setCurrentProposal] = useState< @@ -30,8 +32,7 @@ const App = (): React.JSX.Element => { const [requestSession, setRequestSession] = useState(); const [requestEventData, setRequestEventData] = useState(); const [signModalVisible, setSignModalVisible] = useState(false); - - useInitialization(); + const [currentEthAddresses, setCurrentEthAddresses] = useState([]); const onSessionProposal = useCallback( (proposal: SignClientTypes.EventArguments['session_proposal']) => { @@ -65,6 +66,20 @@ const App = (): React.JSX.Element => { //TODO: Investigate dependancies }); + useEffect(() => { + const fetchEthAccounts = async () => { + const { ethLoadedAccounts } = await retrieveAccounts(); + + if (ethLoadedAccounts) { + const ethAddreses = ethLoadedAccounts.map(account => account.address); + setCurrentEthAddresses(ethAddreses); + } + }; + + fetchEthAccounts(); + // TODO: Use context to maintain accounts state + }, [modalVisible]); + const linking = { prefixes: ['https://www.laconic-wallet.com'], config: { @@ -125,19 +140,23 @@ const App = (): React.JSX.Element => { }} /> - - + <> + + + ); }; diff --git a/components/PairingModal.tsx b/components/PairingModal.tsx index 0a89247..5d575a7 100644 --- a/components/PairingModal.tsx +++ b/components/PairingModal.tsx @@ -4,16 +4,14 @@ import { Button, Text } from 'react-native-paper'; import { PairingModalProps } from '../types'; import styles from '../styles/stylesheet'; -import { - currentETHAddress, - web3wallet, -} from '../utils/wallet-connect/WalletConnectUtils'; +import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { SessionTypes } from '@walletconnect/types'; import { getSdkError } from '@walletconnect/utils'; const PairingModal = ({ visible, + currentEthAddresses, currentProposal, setCurrentProposal, setModalVisible, @@ -32,7 +30,7 @@ const PairingModal = ({ Object.keys(requiredNamespaces).forEach(key => { const accounts: string[] = []; requiredNamespaces[key].chains!.map((chain: any) => { - [currentETHAddress].map(acc => accounts.push(`${chain}:${acc}`)); + currentEthAddresses.map(acc => accounts.push(`${chain}:${acc}`)); }); namespaces[key] = { diff --git a/components/SignModal.tsx b/components/SignModal.tsx index 6b96cc2..1c6eab9 100644 --- a/components/SignModal.tsx +++ b/components/SignModal.tsx @@ -17,6 +17,7 @@ const SignModal = ({ setModalVisible, requestEvent, requestSession, + currentEthAddresses, }: SignModalProps) => { if (!requestEvent || !requestSession) { return null; @@ -33,7 +34,10 @@ const SignModal = ({ const onApprove = async () => { if (requestEvent) { - const response = await approveEIP155Request(requestEvent); + const response = await approveEIP155Request( + requestEvent, + currentEthAddresses, + ); await web3wallet.respondSessionRequest({ topic, response, diff --git a/types.ts b/types.ts index 4134eee..38dcc0c 100644 --- a/types.ts +++ b/types.ts @@ -90,6 +90,7 @@ export type PathState = { export interface PairingModalProps { visible: boolean; setModalVisible: (arg1: boolean) => void; + currentEthAddresses: string[]; currentProposal: | SignClientTypes.EventArguments['session_proposal'] | undefined; @@ -103,4 +104,5 @@ export interface SignModalProps { setModalVisible: (arg1: boolean) => void; requestSession: any; requestEvent: SignClientTypes.EventArguments['session_request'] | undefined; + currentEthAddresses: string[]; } diff --git a/utils/wallet-connect/EIP155Requests.ts b/utils/wallet-connect/EIP155Requests.ts index 3c033c5..e46df18 100644 --- a/utils/wallet-connect/EIP155Requests.ts +++ b/utils/wallet-connect/EIP155Requests.ts @@ -1,63 +1,30 @@ // Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a -import { - EIP155_CHAINS, - EIP155_SIGNING_METHODS, - TEIP155Chain, -} from './EIP155Lib'; -import { eip155Wallets } from './EIP155Wallet'; -import { - getSignParamsMessage, - getSignTypedDataParamsData, - getWalletAddressFromParams, -} from './Helpers'; + import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'; import { SignClientTypes } from '@walletconnect/types'; import { getSdkError } from '@walletconnect/utils'; -import { providers } from 'ethers'; -import { currentETHAddress } from './WalletConnectUtils'; + +import { EIP155_SIGNING_METHODS } from './EIP155Lib'; +import { getSignParamsMessage, getAccountNumberFromParams } from './Helpers'; +import { signEthMessage } from '../sign-message'; export async function approveEIP155Request( requestEvent: SignClientTypes.EventArguments['session_request'], + currentEthAddresses: string[], ) { const { params, id } = requestEvent; - const { chainId, request } = params; - const wallet = - eip155Wallets[getWalletAddressFromParams([currentETHAddress], params)]; + const { request } = params; + const counterId = await getAccountNumberFromParams( + currentEthAddresses, + params, + ); switch (request.method) { case EIP155_SIGNING_METHODS.PERSONAL_SIGN: - case EIP155_SIGNING_METHODS.ETH_SIGN: const message = getSignParamsMessage(request.params); - const signedMessage = await wallet.signMessage(message); + const signedMessage = await signEthMessage(message, counterId); return formatJsonRpcResult(id, signedMessage); - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA: - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V3: - case EIP155_SIGNING_METHODS.ETH_SIGN_TYPED_DATA_V4: - const { - domain, - types, - message: data, - } = getSignTypedDataParamsData(request.params); - // https://github.com/ethers-io/ethers.js/issues/687#issuecomment-714069471 - delete types.EIP712Domain; - const signedData = await wallet._signTypedData(domain, types, data); - return formatJsonRpcResult(id, signedData); - - case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - const provider = new providers.JsonRpcProvider( - EIP155_CHAINS[chainId as TEIP155Chain].rpc, - ); - const sendTransaction = request.params[0]; - const connectedWallet = wallet.connect(provider); - const { hash } = await connectedWallet.sendTransaction(sendTransaction); - return formatJsonRpcResult(id, hash); - - case EIP155_SIGNING_METHODS.ETH_SIGN_TRANSACTION: - const signTransaction = request.params[0]; - const signature = await wallet.signTransaction(signTransaction); - return formatJsonRpcResult(id, signature); - default: throw new Error(getSdkError('INVALID_METHOD').message); } diff --git a/utils/wallet-connect/EIP155Wallet.ts b/utils/wallet-connect/EIP155Wallet.ts deleted file mode 100644 index 9f4c187..0000000 --- a/utils/wallet-connect/EIP155Wallet.ts +++ /dev/null @@ -1,64 +0,0 @@ -// https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a -// TODO: check and remove if not used - -import AsyncStorage from '@react-native-async-storage/async-storage'; -import EIP155Lib from './EIP155Lib'; - -export let wallet1: EIP155Lib; -export let wallet2: EIP155Lib; -export let eip155Wallets: Record; -export let eip155Addresses: string[]; - -export let address1: string; -let address2: string; - -/** - * Utilities - */ -export const setLocalStorage = async (mnemonic: any) => { - try { - const value = await AsyncStorage.setItem('EIP155_MNEMONIC_1', mnemonic); - if (value !== null) { - return value; - } - } catch (e) { - console.log('setLocalStorage Error:', e); - } -}; - -export const getLocalStorage = async () => { - try { - const value = await AsyncStorage.getItem('EIP155_MNEMONIC_1'); - if (value !== null) { - return value; - } - } catch (e) { - console.log('getLocalStorage Error:', e); - } -}; - -// Function to create or restore a wallet -export async function createOrRestoreEIP155Wallet() { - let mnemonic1 = await getLocalStorage(); - - if (mnemonic1) { - wallet1 = EIP155Lib.init({ mnemonic: mnemonic1 }); - } else { - wallet1 = EIP155Lib.init({}); - } - - // @notice / Warning!!! : This is a test wallet, do not use it for real transactions - setLocalStorage(wallet1?.getMnemonic()); - address1 = wallet1.getAddress(); - - eip155Wallets = { - [address1]: wallet1, - [address2]: wallet2, - }; - eip155Addresses = Object.keys(eip155Wallets); - - return { - eip155Wallets, - eip155Addresses, - }; -} diff --git a/utils/wallet-connect/Helpers.ts b/utils/wallet-connect/Helpers.ts index e8ace04..fb082b7 100644 --- a/utils/wallet-connect/Helpers.ts +++ b/utils/wallet-connect/Helpers.ts @@ -1,5 +1,6 @@ // Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a +import { retrieveAccounts } from '../accounts'; import { EIP155_CHAINS, TEIP155Chain } from './EIP155Lib'; import { utils } from 'ethers'; @@ -64,7 +65,10 @@ export function getSignTypedDataParamsData(params: string[]) { * Get our address from params checking if params string contains one * of our wallet addresses */ -export function getWalletAddressFromParams(addresses: string[], params: any) { +export async function getAccountNumberFromParams( + addresses: string[], + params: any, +) { const paramsString = JSON.stringify(params); let address = ''; @@ -74,7 +78,17 @@ export function getWalletAddressFromParams(addresses: string[], params: any) { } }); - return address; + const { ethLoadedAccounts } = await retrieveAccounts(); + + const currentAccount = ethLoadedAccounts!.find( + account => account.address === address, + ); + + if (!currentAccount) { + throw new Error('Account with given adress not found'); + } + + return currentAccount.counterId; } /** diff --git a/utils/wallet-connect/WalletConnectUtils.tsx b/utils/wallet-connect/WalletConnectUtils.tsx index 03c4839..800fead 100644 --- a/utils/wallet-connect/WalletConnectUtils.tsx +++ b/utils/wallet-connect/WalletConnectUtils.tsx @@ -7,14 +7,17 @@ import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet'; export let web3wallet: IWeb3Wallet; export let core: ICore; -export let currentETHAddress: string; +export let currentETHAddresses: string[]; +export let currentCosmosAddresses: string[]; import { useState, useCallback, useEffect } from 'react'; -import { createOrRestoreEIP155Wallet } from './EIP155Wallet'; +import { retrieveAccounts } from '../accounts'; -async function createWeb3Wallet() { - const { eip155Addresses } = await createOrRestoreEIP155Wallet(); - currentETHAddress = eip155Addresses[0]; +export async function createWeb3Wallet() { + const { ethLoadedAccounts } = await retrieveAccounts(); + currentETHAddresses = ethLoadedAccounts + ? ethLoadedAccounts.map(ethAccount => ethAccount.address) + : []; // TODO: Move to dotenv const ENV_PROJECT_ID = 'c97365bf9f06d12a7488de36240b0ff4'; -- 2.45.2 From 19e39281a6e5f7e71b4d4290252815c1d650b3c2 Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:16:00 +0530 Subject: [PATCH 04/64] Add functionality to scan QR while connecting wallet (#40) * Connect with dapp using WalletConnect * Pair dapp with wallet * Sign message taken from dapp and return the signature * Add todos * Move wallet connect functions to seperate screen * Change ui for wc modals * Make review changes * Add dependancy to useEffect * Move pairing modal methods * Integrate QR in walletconnect page * Move styles * Add currentEthAddresses * Handle review changes --------- Co-authored-by: Shreerang Kale Co-authored-by: Adw8 --- App.tsx | 38 ++++++-------- components/PairingModal.tsx | 8 +-- components/WalletConnect.tsx | 96 +++++++++++++++++++++++++++++------- styles/stylesheet.js | 11 +++++ types.ts | 1 - 5 files changed, 107 insertions(+), 47 deletions(-) diff --git a/App.tsx b/App.tsx index 4e2147c..3927687 100644 --- a/App.tsx +++ b/App.tsx @@ -8,7 +8,6 @@ import SignMessage from './components/SignMessage'; import HomeScreen from './components/HomeScreen'; import SignRequest from './components/SignRequest'; import InvalidPath from './components/InvalidPath'; -import QRScanner from './components/QRScanner'; import PairingModal from './components/PairingModal'; import SignModal from './components/SignModal'; import WalletConnect from './components/WalletConnect'; @@ -132,31 +131,22 @@ const App = (): React.JSX.Element => { title: 'Connect Wallet', }} /> - + - <> - - - + ); }; diff --git a/components/PairingModal.tsx b/components/PairingModal.tsx index 5d575a7..63bf114 100644 --- a/components/PairingModal.tsx +++ b/components/PairingModal.tsx @@ -2,17 +2,17 @@ import React from 'react'; import { Image, View, Modal } from 'react-native'; import { Button, Text } from 'react-native-paper'; +import { SessionTypes } from '@walletconnect/types'; +import { getSdkError } from '@walletconnect/utils'; + import { PairingModalProps } from '../types'; import styles from '../styles/stylesheet'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; -import { SessionTypes } from '@walletconnect/types'; -import { getSdkError } from '@walletconnect/utils'; - const PairingModal = ({ visible, - currentEthAddresses, currentProposal, + currentEthAddresses, setCurrentProposal, setModalVisible, }: PairingModalProps) => { diff --git a/components/WalletConnect.tsx b/components/WalletConnect.tsx index 69fe4ae..67e6786 100644 --- a/components/WalletConnect.tsx +++ b/components/WalletConnect.tsx @@ -1,42 +1,102 @@ -import React, { useState } from 'react'; -import { View } from 'react-native'; -import { Button, TextInput } from 'react-native-paper'; +import React, { useEffect, useState } from 'react'; +import { AppState, View } from 'react-native'; +import { Button, Text, TextInput } from 'react-native-paper'; +import { + Camera, + useCameraDevice, + useCameraPermission, + useCodeScanner, +} from 'react-native-vision-camera'; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useNavigation } from '@react-navigation/native'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils'; import styles from '../styles/stylesheet'; import { StackParamsList } from '../types'; const WalletConnect = () => { - const [currentWCURI, setCurrentWCURI] = useState(''); - const navigation = useNavigation>(); + const { hasPermission, requestPermission } = useCameraPermission(); + const device = useCameraDevice('back'); + + const [currentWCURI, setCurrentWCURI] = useState(''); + const [isActive, setIsActive] = useState(AppState.currentState === 'active'); + const [isScanning, setScanning] = useState(true); + + const codeScanner = useCodeScanner({ + codeTypes: ['qr'], + onCodeScanned: codes => { + if (isScanning) { + codes.forEach(code => { + if (code.value) { + setCurrentWCURI(code.value); + setScanning(false); + } + }); + } + }, + }); + const pair = async () => { const pairing = await web3WalletPair({ uri: currentWCURI }); navigation.navigate('Laconic'); return pairing; }; + useEffect(() => { + const handleAppStateChange = (newState: string) => { + setIsActive(newState === 'active'); + }; + + AppState.addEventListener('change', handleAppStateChange); + + if (!hasPermission) { + requestPermission(); + } + }, [hasPermission, isActive, requestPermission]); + return ( - + {!hasPermission || !device ? ( + + {!hasPermission ? 'No Camera Permission!' : 'No Camera Selected!'} + + ) : ( + <> + + {isActive ? ( + + ) : ( + No Camera Selected! + )} + - - - + + + + + + + + + )} ); }; - export default WalletConnect; diff --git a/styles/stylesheet.js b/styles/stylesheet.js index 86313e1..fdf7338 100644 --- a/styles/stylesheet.js +++ b/styles/stylesheet.js @@ -191,6 +191,17 @@ const styles = StyleSheet.create({ paddingHorizontal: 10, marginVertical: 20, }, + cameraContainer: { + justifyContent: 'center', + alignItems: 'center', + }, + inputContainer: { + marginTop: 20, + }, + camera: { + width: 400, + height: 400, + }, }); export default styles; diff --git a/types.ts b/types.ts index 38dcc0c..36aed1b 100644 --- a/types.ts +++ b/types.ts @@ -7,7 +7,6 @@ export type StackParamsList = { | { network: string; address: string; message: string } | undefined; InvalidPath: undefined; - QRScanner: undefined; WalletConnect: undefined; }; -- 2.45.2 From a4e0dc54061d8da28a7e342e29f2d62327632b61 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:44:13 +0530 Subject: [PATCH 05/64] Use context for maintaining accounts state (#41) * Use context for maintaining accounts state * Remove custom hook from context --- App.tsx | 31 ++++++++++++------------ components/Accounts.tsx | 27 ++++++++++++++++++--- components/HomeScreen.tsx | 33 ++++---------------------- components/SignModal.tsx | 6 ++++- context/AccountsContext.tsx | 25 +++++++++++++++++++ index.js | 5 +++- types.ts | 5 ---- utils/wallet-connect/EIP155Requests.ts | 3 +++ utils/wallet-connect/Helpers.ts | 10 ++++---- 9 files changed, 87 insertions(+), 58 deletions(-) create mode 100644 context/AccountsContext.tsx diff --git a/App.tsx b/App.tsx index 3927687..b304288 100644 --- a/App.tsx +++ b/App.tsx @@ -1,4 +1,10 @@ -import React, { useCallback, useEffect, useState } from 'react'; +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; import { SignClientTypes } from '@walletconnect/types'; import { NavigationContainer } from '@react-navigation/native'; @@ -16,13 +22,15 @@ import useInitialization, { web3wallet, } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; -import { retrieveAccounts } from './utils/accounts'; +import { AccountsContext } from './context/AccountsContext'; const Stack = createNativeStackNavigator(); const App = (): React.JSX.Element => { useInitialization(); + const { accounts } = useContext(AccountsContext); + const [modalVisible, setModalVisible] = useState(false); //TODO: Remove any const [currentProposal, setCurrentProposal] = useState< @@ -31,7 +39,6 @@ const App = (): React.JSX.Element => { const [requestSession, setRequestSession] = useState(); const [requestEventData, setRequestEventData] = useState(); const [signModalVisible, setSignModalVisible] = useState(false); - const [currentEthAddresses, setCurrentEthAddresses] = useState([]); const onSessionProposal = useCallback( (proposal: SignClientTypes.EventArguments['session_proposal']) => { @@ -65,19 +72,13 @@ const App = (): React.JSX.Element => { //TODO: Investigate dependancies }); - useEffect(() => { - const fetchEthAccounts = async () => { - const { ethLoadedAccounts } = await retrieveAccounts(); + const currentEthAddresses = useMemo(() => { + if (accounts.ethAccounts.length > 0) { + return accounts.ethAccounts.map(account => account.address); + } - if (ethLoadedAccounts) { - const ethAddreses = ethLoadedAccounts.map(account => account.address); - setCurrentEthAddresses(ethAddreses); - } - }; - - fetchEthAccounts(); - // TODO: Use context to maintain accounts state - }, [modalVisible]); + return []; + }, [accounts]); const linking = { prefixes: ['https://www.laconic-wallet.com'], diff --git a/components/Accounts.tsx b/components/Accounts.tsx index bea18df..30d6086 100644 --- a/components/Accounts.tsx +++ b/components/Accounts.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useContext, useState } from 'react'; import { ScrollView, TouchableOpacity, View } from 'react-native'; import { Button, List, Text, useTheme } from 'react-native-paper'; @@ -10,16 +10,18 @@ import { addAccount } from '../utils/accounts'; import styles from '../styles/stylesheet'; import HDPathDialog from './HDPathDialog'; import AccountDetails from './AccountDetails'; +import { AccountsContext } from '../context/AccountsContext'; const Accounts = ({ network, - accounts, - updateAccounts, currentIndex, updateIndex: updateId, }: AccountsProps) => { const navigation = useNavigation>(); + + const { accounts, setAccounts } = useContext(AccountsContext); + const [expanded, setExpanded] = useState(false); const [isAccountCreating, setIsAccountCreating] = useState(false); const [hdDialog, setHdDialog] = useState(false); @@ -28,6 +30,25 @@ const Accounts = ({ const handlePress = () => setExpanded(!expanded); + const updateAccounts = (account: Account) => { + switch (network) { + case 'eth': + setAccounts({ + ...accounts, + ethAccounts: [...accounts.ethAccounts, account], + }); + break; + case 'cosmos': + setAccounts({ + ...accounts, + cosmosAccounts: [...accounts.cosmosAccounts, account], + }); + break; + default: + console.error('Select a valid network!'); + } + }; + const addAccountHandler = async () => { setIsAccountCreating(true); const newAccount = await addAccount(network); diff --git a/components/HomeScreen.tsx b/components/HomeScreen.tsx index 39e7362..c1deb4a 100644 --- a/components/HomeScreen.tsx +++ b/components/HomeScreen.tsx @@ -1,17 +1,19 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { View, ActivityIndicator } from 'react-native'; import { Button, Text } from 'react-native-paper'; import { createWallet, resetWallet, retrieveAccounts } from '../utils/accounts'; import { DialogComponent } from './Dialog'; import { NetworkDropdown } from './NetworkDropdown'; -import { Account, AccountsState } from '../types'; import Accounts from './Accounts'; import CreateWallet from './CreateWallet'; import ResetWalletDialog from './ResetWalletDialog'; import styles from '../styles/stylesheet'; +import { AccountsContext } from '../context/AccountsContext'; const HomeScreen = () => { + const { accounts, setAccounts } = useContext(AccountsContext); + const [isWalletCreated, setIsWalletCreated] = useState(false); const [isWalletCreating, setIsWalletCreating] = useState(false); const [walletDialog, setWalletDialog] = useState(false); @@ -20,10 +22,6 @@ const HomeScreen = () => { const [currentIndex, setCurrentIndex] = useState(0); const [isAccountsFetched, setIsAccountsFetched] = useState(false); const [phrase, setPhrase] = useState(''); - const [accounts, setAccounts] = useState({ - ethAccounts: [], - cosmosAccounts: [], - }); const hideWalletDialog = () => setWalletDialog(false); const hideResetDialog = () => setResetWalletDialog(false); @@ -64,25 +62,6 @@ const HomeScreen = () => { setCurrentIndex(index); }; - const updateAccounts = (account: Account) => { - switch (network) { - case 'eth': - setAccounts({ - ...accounts, - ethAccounts: [...accounts.ethAccounts, account], - }); - break; - case 'cosmos': - setAccounts({ - ...accounts, - cosmosAccounts: [...accounts.cosmosAccounts, account], - }); - break; - default: - console.error('Select a valid network!'); - } - }; - useEffect(() => { const fetchAccounts = async () => { if (isAccountsFetched) { @@ -104,7 +83,7 @@ const HomeScreen = () => { }; fetchAccounts(); - }, [isAccountsFetched]); + }, [isAccountsFetched, setAccounts]); return ( @@ -122,10 +101,8 @@ const HomeScreen = () => { diff --git a/components/SignModal.tsx b/components/SignModal.tsx index 1c6eab9..4c4d70f 100644 --- a/components/SignModal.tsx +++ b/components/SignModal.tsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useContext } from 'react'; import { Button, Text } from 'react-native-paper'; import { Image, Modal, View } from 'react-native'; @@ -11,6 +11,7 @@ import { import styles from '../styles/stylesheet'; import { SignModalProps } from '../types'; +import { AccountsContext } from '../context/AccountsContext'; const SignModal = ({ visible, @@ -19,6 +20,8 @@ const SignModal = ({ requestSession, currentEthAddresses, }: SignModalProps) => { + const { accounts } = useContext(AccountsContext); + if (!requestEvent || !requestSession) { return null; } @@ -37,6 +40,7 @@ const SignModal = ({ const response = await approveEIP155Request( requestEvent, currentEthAddresses, + accounts.ethAccounts, ); await web3wallet.respondSessionRequest({ topic, diff --git a/context/AccountsContext.tsx b/context/AccountsContext.tsx new file mode 100644 index 0000000..642769f --- /dev/null +++ b/context/AccountsContext.tsx @@ -0,0 +1,25 @@ +import React, { createContext, useState } from 'react'; + +import { AccountsState } from '../types'; + +const AccountsContext = createContext<{ + accounts: AccountsState; + setAccounts: (account: AccountsState) => void; +}>({ + accounts: { ethAccounts: [], cosmosAccounts: [] }, + setAccounts: () => {}, +}); + +const AccountsProvider = ({ children }: { children: any }) => { + const [accounts, setAccounts] = useState({ + ethAccounts: [], + cosmosAccounts: [], + }); + return ( + + {children} + + ); +}; + +export { AccountsContext, AccountsProvider }; diff --git a/index.js b/index.js index 8a143d6..25ceda7 100644 --- a/index.js +++ b/index.js @@ -4,12 +4,15 @@ import { AppRegistry } from 'react-native'; import { PaperProvider } from 'react-native-paper'; import App from './App'; +import { AccountsProvider } from './context/AccountsContext'; import { name as appName } from './app.json'; export default function Main() { return ( - + + + ); } diff --git a/types.ts b/types.ts index 36aed1b..f294a82 100644 --- a/types.ts +++ b/types.ts @@ -25,13 +25,8 @@ export type WalletDetails = { export type AccountsProps = { network: string; - accounts: { - ethAccounts: Account[]; - cosmosAccounts: Account[]; - }; currentIndex: number; updateIndex: (index: number) => void; - updateAccounts: (account: Account) => void; }; export type NetworkDropdownProps = { diff --git a/utils/wallet-connect/EIP155Requests.ts b/utils/wallet-connect/EIP155Requests.ts index e46df18..414c127 100644 --- a/utils/wallet-connect/EIP155Requests.ts +++ b/utils/wallet-connect/EIP155Requests.ts @@ -7,15 +7,18 @@ import { getSdkError } from '@walletconnect/utils'; import { EIP155_SIGNING_METHODS } from './EIP155Lib'; import { getSignParamsMessage, getAccountNumberFromParams } from './Helpers'; import { signEthMessage } from '../sign-message'; +import { Account } from '../../types'; export async function approveEIP155Request( requestEvent: SignClientTypes.EventArguments['session_request'], currentEthAddresses: string[], + ethAccounts: Account[], ) { const { params, id } = requestEvent; const { request } = params; const counterId = await getAccountNumberFromParams( currentEthAddresses, + ethAccounts, params, ); diff --git a/utils/wallet-connect/Helpers.ts b/utils/wallet-connect/Helpers.ts index fb082b7..5f777a9 100644 --- a/utils/wallet-connect/Helpers.ts +++ b/utils/wallet-connect/Helpers.ts @@ -1,9 +1,10 @@ // Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a -import { retrieveAccounts } from '../accounts'; -import { EIP155_CHAINS, TEIP155Chain } from './EIP155Lib'; import { utils } from 'ethers'; +import { Account } from '../../types'; +import { EIP155_CHAINS, TEIP155Chain } from './EIP155Lib'; + /** * Truncates string (in the middle) via given lenght value */ @@ -67,6 +68,7 @@ export function getSignTypedDataParamsData(params: string[]) { */ export async function getAccountNumberFromParams( addresses: string[], + ethAccounts: Account[], params: any, ) { const paramsString = JSON.stringify(params); @@ -78,9 +80,7 @@ export async function getAccountNumberFromParams( } }); - const { ethLoadedAccounts } = await retrieveAccounts(); - - const currentAccount = ethLoadedAccounts!.find( + const currentAccount = ethAccounts!.find( account => account.address === address, ); -- 2.45.2 From 05be6008ded82a3772c283860ede4618f11b2010 Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Thu, 7 Mar 2024 12:44:05 +0530 Subject: [PATCH 06/64] Use page for handling sign requests (#42) * Use sign request page instead of modal * Fix context * Remove multiple if statements * Change metadata * Remove sign modal * Make review changes * Remove state --------- Co-authored-by: Adw8 --- App.tsx | 52 +++++----- components/SignModal.tsx | 100 -------------------- components/SignRequest.tsx | 58 ++++++++++-- types.ts | 9 +- utils/RootNavigation.ts | 14 +++ utils/wallet-connect/EIP155Requests.ts | 15 +-- utils/wallet-connect/WalletConnectUtils.tsx | 8 +- 7 files changed, 107 insertions(+), 149 deletions(-) delete mode 100644 components/SignModal.tsx create mode 100644 utils/RootNavigation.ts diff --git a/App.tsx b/App.tsx index b304288..48b63d8 100644 --- a/App.tsx +++ b/App.tsx @@ -15,7 +15,6 @@ import HomeScreen from './components/HomeScreen'; import SignRequest from './components/SignRequest'; import InvalidPath from './components/InvalidPath'; import PairingModal from './components/PairingModal'; -import SignModal from './components/SignModal'; import WalletConnect from './components/WalletConnect'; import { StackParamsList } from './types'; import useInitialization, { @@ -23,6 +22,8 @@ import useInitialization, { } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; import { AccountsContext } from './context/AccountsContext'; +import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; +import { navigationRef, navigateTo } from './utils/RootNavigation'; const Stack = createNativeStackNavigator(); @@ -37,8 +38,13 @@ const App = (): React.JSX.Element => { SignClientTypes.EventArguments['session_proposal'] | undefined >(); const [requestSession, setRequestSession] = useState(); - const [requestEventData, setRequestEventData] = useState(); - const [signModalVisible, setSignModalVisible] = useState(false); + + const currentEthAddresses = useMemo(() => { + if (accounts.ethAccounts.length > 0) { + return accounts.ethAccounts.map(account => account.address); + } + return []; + }, [accounts]); const onSessionProposal = useCallback( (proposal: SignClientTypes.EventArguments['session_proposal']) => { @@ -52,6 +58,8 @@ const App = (): React.JSX.Element => { async (requestEvent: SignClientTypes.EventArguments['session_request']) => { const { topic, params } = requestEvent; const { request } = params; + const address = request.params[1]; + const message = getSignParamsMessage(request.params); const requestSessionData = web3wallet.engine.signClient.session.get(topic); @@ -59,27 +67,31 @@ const App = (): React.JSX.Element => { case EIP155_SIGNING_METHODS.ETH_SIGN: case EIP155_SIGNING_METHODS.PERSONAL_SIGN: setRequestSession(requestSessionData); - setRequestEventData(requestEvent); - setSignModalVisible(true); + if (address && message) { + navigateTo('SignRequest', { + network: 'eth', + address, + message, + requestEvent, + requestSession, + }); + } return; } }, - [], + [requestSession], ); useEffect(() => { web3wallet?.on('session_proposal', onSessionProposal); web3wallet?.on('session_request', onSessionRequest); + + return () => { + web3wallet?.off('session_proposal', onSessionProposal); + web3wallet?.off('session_request', onSessionRequest); + }; //TODO: Investigate dependancies }); - const currentEthAddresses = useMemo(() => { - if (accounts.ethAccounts.length > 0) { - return accounts.ethAccounts.map(account => account.address); - } - - return []; - }, [accounts]); - const linking = { prefixes: ['https://www.laconic-wallet.com'], config: { @@ -92,7 +104,7 @@ const App = (): React.JSX.Element => { }; return ( - + { name="SignRequest" component={SignRequest} options={{ - title: 'Sign Message?', + title: 'Sign this message?', }} /> { setCurrentProposal={setCurrentProposal} currentEthAddresses={currentEthAddresses} /> - - ); }; diff --git a/components/SignModal.tsx b/components/SignModal.tsx deleted file mode 100644 index 4c4d70f..0000000 --- a/components/SignModal.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import React, { useContext } from 'react'; -import { Button, Text } from 'react-native-paper'; -import { Image, Modal, View } from 'react-native'; - -import { getSignParamsMessage } from '../utils/wallet-connect/Helpers'; -import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; -import { - approveEIP155Request, - rejectEIP155Request, -} from '../utils/wallet-connect/EIP155Requests'; - -import styles from '../styles/stylesheet'; -import { SignModalProps } from '../types'; -import { AccountsContext } from '../context/AccountsContext'; - -const SignModal = ({ - visible, - setModalVisible, - requestEvent, - requestSession, - currentEthAddresses, -}: SignModalProps) => { - const { accounts } = useContext(AccountsContext); - - if (!requestEvent || !requestSession) { - return null; - } - - const chainID = requestEvent?.params?.chainId?.toUpperCase(); - const message = getSignParamsMessage(requestEvent?.params?.request?.params); - - const requestName = requestSession?.peer?.metadata?.name; - const requestIcon = requestSession?.peer?.metadata?.icons[0]; - const requestURL = requestSession?.peer?.metadata?.url; - - const { topic } = requestEvent; - - const onApprove = async () => { - if (requestEvent) { - const response = await approveEIP155Request( - requestEvent, - currentEthAddresses, - accounts.ethAccounts, - ); - await web3wallet.respondSessionRequest({ - topic, - response, - }); - setModalVisible(false); - } - }; - - const onReject = async () => { - if (requestEvent) { - const response = rejectEIP155Request(requestEvent); - await web3wallet.respondSessionRequest({ - topic, - response, - }); - setModalVisible(false); - } - }; - - return ( - - - - Sign this message? - - - - {requestName} - {requestURL} - - - {message} - - Chains: {chainID} - - - - - - - - - - ); -}; - -export default SignModal; diff --git a/components/SignRequest.tsx b/components/SignRequest.tsx index ea735df..5d7f7ab 100644 --- a/components/SignRequest.tsx +++ b/components/SignRequest.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import { Alert, View } from 'react-native'; import { ActivityIndicator, Button, Text } from 'react-native-paper'; @@ -13,6 +13,11 @@ import AccountDetails from './AccountDetails'; import styles from '../styles/stylesheet'; import { signMessage } from '../utils/sign-message'; import { retrieveSingleAccount } from '../utils/accounts'; +import { + approveEIP155Request, + rejectEIP155Request, +} from '../utils/wallet-connect/EIP155Requests'; +import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; type SignRequestProps = NativeStackScreenProps; @@ -20,7 +25,7 @@ const SignRequest = ({ route }: SignRequestProps) => { const [account, setAccount] = useState(); const [message, setMessage] = useState(''); const [network, setNetwork] = useState(''); - const [loaded, setLoaded] = useState(false); + const [isLoading, setIsLoading] = useState(true); const navigation = useNavigation>(); @@ -36,7 +41,9 @@ const SignRequest = ({ route }: SignRequestProps) => { ); if (!requestAccount) { navigation.navigate('InvalidPath'); + return; } + if (requestAccount && requestAccount !== account) { setAccount(requestAccount); } @@ -66,7 +73,6 @@ const SignRequest = ({ route }: SignRequestProps) => { }; useEffect(() => { - setLoaded(true); if (route.path) { const sanitizedRoute = sanitizePath(route.path); sanitizedRoute && @@ -76,11 +82,28 @@ const SignRequest = ({ route }: SignRequestProps) => { route.params?.address, route.params?.message, ); + return; } - setLoaded(false); + route.params && + retrieveData( + route.params?.network, + route.params?.address, + route.params?.message, + ); + setIsLoading(false); }, [route]); - const signMessageHandler = async () => { + const handleEIP155Request = async () => { + const { requestEvent } = route.params || {}; + const response = await approveEIP155Request( + requestEvent, + account?.counterId, + ); + const { topic } = requestEvent; + await web3wallet.respondSessionRequest({ topic, response }); + }; + + const handleIntent = async () => { if (!account) { throw new Error('Account is not valid'); } @@ -91,17 +114,36 @@ const SignRequest = ({ route }: SignRequestProps) => { accountId: account.counterId, }); Alert.alert('Signature', signedMessage); - navigation.navigate('Laconic'); } }; + const signMessageHandler = async () => { + if (route.params?.requestEvent) { + await handleEIP155Request(); + } else { + await handleIntent(); + } + + navigation.navigate('Laconic'); + }; + const rejectRequestHandler = async () => { + if (route.params?.requestEvent) { + const response = rejectEIP155Request(route.params?.requestEvent); + const { topic } = route.params?.requestEvent; + await web3wallet.respondSessionRequest({ + topic, + response, + }); + } navigation.navigate('Laconic'); }; return ( <> - {!loaded ? ( + {isLoading ? ( + + ) : ( @@ -119,8 +161,6 @@ const SignRequest = ({ route }: SignRequestProps) => { - ) : ( - )} ); diff --git a/types.ts b/types.ts index f294a82..98e76da 100644 --- a/types.ts +++ b/types.ts @@ -4,7 +4,14 @@ export type StackParamsList = { Laconic: undefined; SignMessage: { selectedNetwork: string; accountInfo: Account } | undefined; SignRequest: - | { network: string; address: string; message: string } + | { + network: string; + address: string; + message: string; + // TODO: remove any + requestEvent?: any; + requestSession?: any; + } | undefined; InvalidPath: undefined; WalletConnect: undefined; diff --git a/utils/RootNavigation.ts b/utils/RootNavigation.ts new file mode 100644 index 0000000..10b50c5 --- /dev/null +++ b/utils/RootNavigation.ts @@ -0,0 +1,14 @@ +import { + CommonActions, + createNavigationContainerRef, +} from '@react-navigation/native'; + +import { StackParamsList } from '../types'; + +export const navigationRef = createNavigationContainerRef(); + +export function navigateTo(routeName: string, params?: object) { + if (navigationRef.isReady()) { + navigationRef.dispatch(CommonActions.navigate(routeName, params)); + } +} diff --git a/utils/wallet-connect/EIP155Requests.ts b/utils/wallet-connect/EIP155Requests.ts index 414c127..bdb207b 100644 --- a/utils/wallet-connect/EIP155Requests.ts +++ b/utils/wallet-connect/EIP155Requests.ts @@ -5,27 +5,20 @@ import { SignClientTypes } from '@walletconnect/types'; import { getSdkError } from '@walletconnect/utils'; import { EIP155_SIGNING_METHODS } from './EIP155Lib'; -import { getSignParamsMessage, getAccountNumberFromParams } from './Helpers'; +import { getSignParamsMessage } from './Helpers'; import { signEthMessage } from '../sign-message'; -import { Account } from '../../types'; export async function approveEIP155Request( requestEvent: SignClientTypes.EventArguments['session_request'], - currentEthAddresses: string[], - ethAccounts: Account[], + counterId: number | undefined, ) { const { params, id } = requestEvent; const { request } = params; - const counterId = await getAccountNumberFromParams( - currentEthAddresses, - ethAccounts, - params, - ); - switch (request.method) { case EIP155_SIGNING_METHODS.PERSONAL_SIGN: const message = getSignParamsMessage(request.params); - const signedMessage = await signEthMessage(message, counterId); + const signedMessage = + counterId && (await signEthMessage(message, counterId)); return formatJsonRpcResult(id, signedMessage); default: diff --git a/utils/wallet-connect/WalletConnectUtils.tsx b/utils/wallet-connect/WalletConnectUtils.tsx index 800fead..eb60607 100644 --- a/utils/wallet-connect/WalletConnectUtils.tsx +++ b/utils/wallet-connect/WalletConnectUtils.tsx @@ -28,10 +28,10 @@ export async function createWeb3Wallet() { web3wallet = await Web3Wallet.init({ core, metadata: { - name: 'Web3Wallet React Native Tutorial', - description: 'ReactNative Web3Wallet', - url: 'https://walletconnect.com/', - icons: ['https://avatars.githubusercontent.com/u/37784886'], + name: 'Laconic Wallet', + description: 'ReactNative Laconic Wallet', + url: 'https://wallet.laconic.com/', + icons: ['https://avatars.githubusercontent.com/u/92608123'], }, }); } -- 2.45.2 From d44d8a3092fb0fa00b2a0bd64ab187ad56eabb6a Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:28:12 +0530 Subject: [PATCH 07/64] UI changes for wallet-connect integration (#43) * Change button position * Add check for counterId * Display complete uri * Update readme --------- Co-authored-by: Adw8 --- README.md | 4 ++-- components/PairingModal.tsx | 8 ++++---- components/SignRequest.tsx | 18 ++++++++++-------- components/WalletConnect.tsx | 5 ++++- utils/wallet-connect/EIP155Requests.ts | 2 +- utils/wallet-connect/WalletConnectUtils.tsx | 2 +- 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index d136cee..3baa401 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ 6. Press `a` to run the application on android -## Setup for example-signer-app +## Setup for signature-requester-app 1. Clone the repository: @@ -106,7 +106,7 @@ 2. Enter the project directory: ``` - cd example-signer-app + cd signature-requester-app ``` 3. Install the dependancies diff --git a/components/PairingModal.tsx b/components/PairingModal.tsx index 63bf114..d29466c 100644 --- a/components/PairingModal.tsx +++ b/components/PairingModal.tsx @@ -99,13 +99,13 @@ const PairingModal = ({ - - + + diff --git a/components/SignRequest.tsx b/components/SignRequest.tsx index 5d7f7ab..2517f82 100644 --- a/components/SignRequest.tsx +++ b/components/SignRequest.tsx @@ -44,15 +44,16 @@ const SignRequest = ({ route }: SignRequestProps) => { return; } - if (requestAccount && requestAccount !== account) { + if (requestAccount !== account) { setAccount(requestAccount); } - if (requestMessage && requestMessage !== message) { + if (requestMessage !== message) { setMessage(decodeURIComponent(requestMessage)); } - if (requestNetwork && requestNetwork !== network) { + if (requestNetwork !== network) { setNetwork(requestNetwork); } + setIsLoading(false); }; const sanitizePath = (path: string) => { @@ -90,7 +91,6 @@ const SignRequest = ({ route }: SignRequestProps) => { route.params?.address, route.params?.message, ); - setIsLoading(false); }, [route]); const handleEIP155Request = async () => { @@ -142,7 +142,9 @@ const SignRequest = ({ route }: SignRequestProps) => { return ( <> {isLoading ? ( - + + + ) : ( @@ -150,15 +152,15 @@ const SignRequest = ({ route }: SignRequestProps) => { {message} + - )} diff --git a/components/WalletConnect.tsx b/components/WalletConnect.tsx index 67e6786..eeac98d 100644 --- a/components/WalletConnect.tsx +++ b/components/WalletConnect.tsx @@ -81,11 +81,14 @@ const WalletConnect = () => { + Enter WalletConnect URI diff --git a/utils/wallet-connect/EIP155Requests.ts b/utils/wallet-connect/EIP155Requests.ts index bdb207b..ff22243 100644 --- a/utils/wallet-connect/EIP155Requests.ts +++ b/utils/wallet-connect/EIP155Requests.ts @@ -18,7 +18,7 @@ export async function approveEIP155Request( case EIP155_SIGNING_METHODS.PERSONAL_SIGN: const message = getSignParamsMessage(request.params); const signedMessage = - counterId && (await signEthMessage(message, counterId)); + counterId !== undefined && (await signEthMessage(message, counterId)); return formatJsonRpcResult(id, signedMessage); default: diff --git a/utils/wallet-connect/WalletConnectUtils.tsx b/utils/wallet-connect/WalletConnectUtils.tsx index eb60607..6a37893 100644 --- a/utils/wallet-connect/WalletConnectUtils.tsx +++ b/utils/wallet-connect/WalletConnectUtils.tsx @@ -29,7 +29,7 @@ export async function createWeb3Wallet() { core, metadata: { name: 'Laconic Wallet', - description: 'ReactNative Laconic Wallet', + description: 'Laconic Wallet', url: 'https://wallet.laconic.com/', icons: ['https://avatars.githubusercontent.com/u/92608123'], }, -- 2.45.2 From 9ab8c2ce4f9d990b56f62cfd16f873fa4c8b095b Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Thu, 7 Mar 2024 16:02:35 +0530 Subject: [PATCH 08/64] Display details coming from dapp in sign request page (#44) * Add qr-code scanner button in homescreen header * Display dapp details on sign request page * Center details coming from dapp * Remove request event state from request context --- App.tsx | 18 ++++++---------- components/Accounts.tsx | 17 +++------------ components/HomeScreen.tsx | 30 +++++++++++++++++++++++--- components/SignRequest.tsx | 28 +++++++++++++++++++++--- context/AccountsContext.tsx | 9 ++++++-- context/RequestContext.tsx | 30 ++++++++++++++++++++++++++ index.js | 5 ++++- package.json | 1 + styles/stylesheet.js | 4 ++++ utils/wallet-connect/EIP155Requests.ts | 5 ++--- yarn.lock | 15 +++++++++++++ 11 files changed, 125 insertions(+), 37 deletions(-) create mode 100644 context/RequestContext.tsx diff --git a/App.tsx b/App.tsx index 48b63d8..716d145 100644 --- a/App.tsx +++ b/App.tsx @@ -1,10 +1,4 @@ -import React, { - useCallback, - useContext, - useEffect, - useMemo, - useState, -} from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { SignClientTypes } from '@walletconnect/types'; import { NavigationContainer } from '@react-navigation/native'; @@ -21,23 +15,25 @@ import useInitialization, { web3wallet, } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; -import { AccountsContext } from './context/AccountsContext'; +import { useAccounts } from './context/AccountsContext'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; import { navigationRef, navigateTo } from './utils/RootNavigation'; +import { useRequests } from './context/RequestContext'; const Stack = createNativeStackNavigator(); const App = (): React.JSX.Element => { useInitialization(); - const { accounts } = useContext(AccountsContext); + const { accounts } = useAccounts(); + + const { requestSession, setRequestSession } = useRequests(); const [modalVisible, setModalVisible] = useState(false); //TODO: Remove any const [currentProposal, setCurrentProposal] = useState< SignClientTypes.EventArguments['session_proposal'] | undefined >(); - const [requestSession, setRequestSession] = useState(); const currentEthAddresses = useMemo(() => { if (accounts.ethAccounts.length > 0) { @@ -79,7 +75,7 @@ const App = (): React.JSX.Element => { return; } }, - [requestSession], + [requestSession, setRequestSession], ); useEffect(() => { web3wallet?.on('session_proposal', onSessionProposal); diff --git a/components/Accounts.tsx b/components/Accounts.tsx index 30d6086..4cc06d3 100644 --- a/components/Accounts.tsx +++ b/components/Accounts.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useState } from 'react'; +import React, { useState } from 'react'; import { ScrollView, TouchableOpacity, View } from 'react-native'; import { Button, List, Text, useTheme } from 'react-native-paper'; @@ -10,7 +10,7 @@ import { addAccount } from '../utils/accounts'; import styles from '../styles/stylesheet'; import HDPathDialog from './HDPathDialog'; import AccountDetails from './AccountDetails'; -import { AccountsContext } from '../context/AccountsContext'; +import { useAccounts } from '../context/AccountsContext'; const Accounts = ({ network, @@ -20,7 +20,7 @@ const Accounts = ({ const navigation = useNavigation>(); - const { accounts, setAccounts } = useContext(AccountsContext); + const { accounts, setAccounts } = useAccounts(); const [expanded, setExpanded] = useState(false); const [isAccountCreating, setIsAccountCreating] = useState(false); @@ -135,17 +135,6 @@ const Accounts = ({ Sign Message - - { - navigation.navigate('WalletConnect'); - }}> - - Connect Wallet - - diff --git a/components/HomeScreen.tsx b/components/HomeScreen.tsx index c1deb4a..9be6809 100644 --- a/components/HomeScreen.tsx +++ b/components/HomeScreen.tsx @@ -1,6 +1,10 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { View, ActivityIndicator } from 'react-native'; import { Button, Text } from 'react-native-paper'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; + +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useNavigation } from '@react-navigation/native'; import { createWallet, resetWallet, retrieveAccounts } from '../utils/accounts'; import { DialogComponent } from './Dialog'; @@ -9,10 +13,30 @@ import Accounts from './Accounts'; import CreateWallet from './CreateWallet'; import ResetWalletDialog from './ResetWalletDialog'; import styles from '../styles/stylesheet'; -import { AccountsContext } from '../context/AccountsContext'; +import { useAccounts } from '../context/AccountsContext'; +import { StackParamsList } from '../types'; const HomeScreen = () => { - const { accounts, setAccounts } = useContext(AccountsContext); + const { accounts, setAccounts } = useAccounts(); + + const navigation = + useNavigation>(); + + useEffect(() => { + if (accounts.ethAccounts.length > 0) { + navigation.setOptions({ + headerRight: () => ( + + ), + }); + } else { + navigation.setOptions({ + headerRight: undefined, + }); + } + }, [navigation, accounts]); const [isWalletCreated, setIsWalletCreated] = useState(false); const [isWalletCreating, setIsWalletCreating] = useState(false); diff --git a/components/SignRequest.tsx b/components/SignRequest.tsx index 2517f82..9280cd8 100644 --- a/components/SignRequest.tsx +++ b/components/SignRequest.tsx @@ -1,5 +1,5 @@ -import React, { useContext, useEffect, useState } from 'react'; -import { Alert, View } from 'react-native'; +import React, { useEffect, useState } from 'react'; +import { Alert, Image, View } from 'react-native'; import { ActivityIndicator, Button, Text } from 'react-native-paper'; import { useNavigation } from '@react-navigation/native'; @@ -18,10 +18,17 @@ import { rejectEIP155Request, } from '../utils/wallet-connect/EIP155Requests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; +import { useRequests } from '../context/RequestContext'; type SignRequestProps = NativeStackScreenProps; const SignRequest = ({ route }: SignRequestProps) => { + const { requestSession } = useRequests(); + + const requestName = requestSession?.peer?.metadata?.name; + const requestIcon = requestSession?.peer?.metadata?.icons[0]; + const requestURL = requestSession?.peer?.metadata?.url; + const [account, setAccount] = useState(); const [message, setMessage] = useState(''); const [network, setNetwork] = useState(''); @@ -95,9 +102,14 @@ const SignRequest = ({ route }: SignRequestProps) => { const handleEIP155Request = async () => { const { requestEvent } = route.params || {}; + + if (!account) { + throw new Error('account not found'); + } + const response = await approveEIP155Request( requestEvent, - account?.counterId, + account.counterId, ); const { topic } = requestEvent; await web3wallet.respondSessionRequest({ topic, response }); @@ -147,6 +159,16 @@ const SignRequest = ({ route }: SignRequestProps) => { ) : ( + + + {requestName} + {requestURL} + {message} diff --git a/context/AccountsContext.tsx b/context/AccountsContext.tsx index 642769f..7b60a3e 100644 --- a/context/AccountsContext.tsx +++ b/context/AccountsContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, useState } from 'react'; +import React, { createContext, useContext, useState } from 'react'; import { AccountsState } from '../types'; @@ -10,6 +10,11 @@ const AccountsContext = createContext<{ setAccounts: () => {}, }); +const useAccounts = () => { + const accountsContext = useContext(AccountsContext); + return accountsContext; +}; + const AccountsProvider = ({ children }: { children: any }) => { const [accounts, setAccounts] = useState({ ethAccounts: [], @@ -22,4 +27,4 @@ const AccountsProvider = ({ children }: { children: any }) => { ); }; -export { AccountsContext, AccountsProvider }; +export { useAccounts, AccountsProvider }; diff --git a/context/RequestContext.tsx b/context/RequestContext.tsx new file mode 100644 index 0000000..ee1ad57 --- /dev/null +++ b/context/RequestContext.tsx @@ -0,0 +1,30 @@ +import React, { createContext, useContext, useState } from 'react'; + +const RequestContext = createContext<{ + // TODO: Remove any type + requestSession: any; + setRequestSession: (requestSession: any) => void; +}>({ + requestSession: {}, + setRequestSession: () => {}, +}); + +const useRequests = () => { + const requestContext = useContext(RequestContext); + return requestContext; +}; + +const RequestProvider = ({ children }: { children: any }) => { + const [requestSession, setRequestSession] = useState({}); + return ( + + {children} + + ); +}; + +export { useRequests, RequestProvider }; diff --git a/index.js b/index.js index 25ceda7..d9007a3 100644 --- a/index.js +++ b/index.js @@ -5,13 +5,16 @@ import { PaperProvider } from 'react-native-paper'; import App from './App'; import { AccountsProvider } from './context/AccountsContext'; +import { RequestProvider } from './context/RequestContext'; import { name as appName } from './app.json'; export default function Main() { return ( - + + + ); diff --git a/package.json b/package.json index 1c1e103..d3dc40d 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "@react-native/metro-config": "0.73.4", "@react-native/typescript-config": "0.73.1", "@types/react": "^18.2.6", + "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.0.0", "@walletconnect/jsonrpc-types": "^1.0.3", "babel-jest": "^29.6.3", diff --git a/styles/stylesheet.js b/styles/stylesheet.js index fdf7338..e3de033 100644 --- a/styles/stylesheet.js +++ b/styles/stylesheet.js @@ -202,6 +202,10 @@ const styles = StyleSheet.create({ width: 400, height: 400, }, + dappDetails: { + display: 'flex', + alignItems: 'center', + }, }); export default styles; diff --git a/utils/wallet-connect/EIP155Requests.ts b/utils/wallet-connect/EIP155Requests.ts index ff22243..d83b23e 100644 --- a/utils/wallet-connect/EIP155Requests.ts +++ b/utils/wallet-connect/EIP155Requests.ts @@ -10,15 +10,14 @@ import { signEthMessage } from '../sign-message'; export async function approveEIP155Request( requestEvent: SignClientTypes.EventArguments['session_request'], - counterId: number | undefined, + counterId: number, ) { const { params, id } = requestEvent; const { request } = params; switch (request.method) { case EIP155_SIGNING_METHODS.PERSONAL_SIGN: const message = getSignParamsMessage(request.params); - const signedMessage = - counterId !== undefined && (await signEthMessage(message, counterId)); + const signedMessage = await signEthMessage(message, counterId); return formatJsonRpcResult(id, signedMessage); default: diff --git a/yarn.lock b/yarn.lock index d36347a..7c3b8b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2691,6 +2691,21 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== +"@types/react-native-vector-icons@^6.4.18": + version "6.4.18" + resolved "https://registry.yarnpkg.com/@types/react-native-vector-icons/-/react-native-vector-icons-6.4.18.tgz#18671c617b9d0958747bc959903470dde91a8c79" + integrity sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw== + dependencies: + "@types/react" "*" + "@types/react-native" "^0.70" + +"@types/react-native@^0.70": + version "0.70.19" + resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.70.19.tgz#b4e651dcf7f49c69ff3a4c3072584cad93155582" + integrity sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg== + dependencies: + "@types/react" "*" + "@types/react-test-renderer@^18.0.0": version "18.0.7" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-18.0.7.tgz#2cfe657adb3688cdf543995eceb2e062b5a68728" -- 2.45.2 From d7ebdd60638a5353d92e07cae98447fb13972c28 Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:12:22 +0530 Subject: [PATCH 09/64] Remove QRScanner.tsx (#45) * Remove QRScanner component * Remove unused variables --------- Co-authored-by: Adw8 --- components/QRScanner.tsx | 107 -------------------- utils/wallet-connect/WalletConnectUtils.tsx | 8 -- 2 files changed, 115 deletions(-) delete mode 100644 components/QRScanner.tsx diff --git a/components/QRScanner.tsx b/components/QRScanner.tsx deleted file mode 100644 index bd85501..0000000 --- a/components/QRScanner.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Alert, StyleSheet, View, AppState } from 'react-native'; -import { Text, Button } from 'react-native-paper'; -import { - Camera, - useCameraDevice, - useCameraPermission, - useCodeScanner, -} from 'react-native-vision-camera'; - -import { useNavigation } from '@react-navigation/native'; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; - -import { StackParamsList } from '../types'; - -const QRScanner = () => { - const navigation = - useNavigation>(); - - const { hasPermission, requestPermission } = useCameraPermission(); - - const [isActive, setIsActive] = useState(AppState.currentState === 'active'); - const device = useCameraDevice('back'); - const [isScanning, setScanning] = useState(true); - const codeScanner = useCodeScanner({ - codeTypes: ['qr'], - onCodeScanned: codes => { - if (isScanning) { - codes.forEach(code => { - if (code.value) { - Alert.alert(`Scanned: ${code.value}`, undefined, [ - { - text: 'OK', - onPress: () => { - navigation.navigate('Laconic'); - }, - }, - ]); - - setScanning(false); - } - }); - } - }, - }); - - useEffect(() => { - const handleAppStateChange = (newState: string) => { - setIsActive(newState === 'active'); - }; - - AppState.addEventListener('change', handleAppStateChange); - - if (!hasPermission) { - requestPermission(); - } - }, [hasPermission, isActive, requestPermission]); - - if (!hasPermission) { - return ( - - No Camera Permission! - - - ); - } - - if (!device) { - return ( - - No Camera Selected! - - ); - } - - return ( - - {isActive ? ( - - ) : ( - No Camera Selected! - )} - - ); -}; - -const styles = StyleSheet.create({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - camera: { - width: 400, - height: 500, - borderRadius: 50, - overflow: 'hidden', - }, -}); - -export default QRScanner; diff --git a/utils/wallet-connect/WalletConnectUtils.tsx b/utils/wallet-connect/WalletConnectUtils.tsx index 6a37893..02e993c 100644 --- a/utils/wallet-connect/WalletConnectUtils.tsx +++ b/utils/wallet-connect/WalletConnectUtils.tsx @@ -7,18 +7,10 @@ import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet'; export let web3wallet: IWeb3Wallet; export let core: ICore; -export let currentETHAddresses: string[]; -export let currentCosmosAddresses: string[]; import { useState, useCallback, useEffect } from 'react'; -import { retrieveAccounts } from '../accounts'; export async function createWeb3Wallet() { - const { ethLoadedAccounts } = await retrieveAccounts(); - currentETHAddresses = ethLoadedAccounts - ? ethLoadedAccounts.map(ethAccount => ethAccount.address) - : []; - // TODO: Move to dotenv const ENV_PROJECT_ID = 'c97365bf9f06d12a7488de36240b0ff4'; const core = new Core({ -- 2.45.2 From 4b0ed0ba9f4c10a47de7dc2830dbf6953bb32a66 Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Thu, 7 Mar 2024 19:28:30 +0530 Subject: [PATCH 10/64] Add toast on session approval (#46) * Add toast on session_approval * Handle case where request icon is null * Change camera permission message * Fix comments * Fix import order --------- Co-authored-by: Adw8 --- App.tsx | 17 +++++++++++++++-- components/PairingModal.tsx | 2 ++ components/SignRequest.tsx | 12 ++++++------ components/WalletConnect.tsx | 4 +++- types.ts | 1 + 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/App.tsx b/App.tsx index 716d145..118e126 100644 --- a/App.tsx +++ b/App.tsx @@ -1,4 +1,5 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { Snackbar } from 'react-native-paper'; import { SignClientTypes } from '@walletconnect/types'; import { NavigationContainer } from '@react-navigation/native'; @@ -30,7 +31,7 @@ const App = (): React.JSX.Element => { const { requestSession, setRequestSession } = useRequests(); const [modalVisible, setModalVisible] = useState(false); - //TODO: Remove any + const [toastVisible, setToastVisible] = useState(false); const [currentProposal, setCurrentProposal] = useState< SignClientTypes.EventArguments['session_proposal'] | undefined >(); @@ -85,7 +86,7 @@ const App = (): React.JSX.Element => { web3wallet?.off('session_proposal', onSessionProposal); web3wallet?.off('session_request', onSessionRequest); }; - //TODO: Investigate dependancies + //TODO: Investigate dependencies }); const linking = { @@ -147,7 +148,19 @@ const App = (): React.JSX.Element => { currentProposal={currentProposal} setCurrentProposal={setCurrentProposal} currentEthAddresses={currentEthAddresses} + setToastVisible={setToastVisible} /> + setToastVisible(false)} + action={{ + label: 'Ok', + onPress: () => { + setToastVisible(false); + }, + }}> + Session approved + ); }; diff --git a/components/PairingModal.tsx b/components/PairingModal.tsx index d29466c..6f2c9cc 100644 --- a/components/PairingModal.tsx +++ b/components/PairingModal.tsx @@ -15,6 +15,7 @@ const PairingModal = ({ currentEthAddresses, setCurrentProposal, setModalVisible, + setToastVisible, }: PairingModalProps) => { const url = currentProposal?.params?.proposer?.metadata.url; const methods = currentProposal?.params?.requiredNamespaces.eip155.methods; @@ -47,6 +48,7 @@ const PairingModal = ({ }); setModalVisible(false); + setToastVisible(true); setCurrentProposal(undefined); } }; diff --git a/components/SignRequest.tsx b/components/SignRequest.tsx index 9280cd8..92f088a 100644 --- a/components/SignRequest.tsx +++ b/components/SignRequest.tsx @@ -160,12 +160,12 @@ const SignRequest = ({ route }: SignRequestProps) => { ) : ( - + {requestIcon && ( + + )} {requestName} {requestURL} diff --git a/components/WalletConnect.tsx b/components/WalletConnect.tsx index eeac98d..64a489c 100644 --- a/components/WalletConnect.tsx +++ b/components/WalletConnect.tsx @@ -62,7 +62,9 @@ const WalletConnect = () => { {!hasPermission || !device ? ( - {!hasPermission ? 'No Camera Permission!' : 'No Camera Selected!'} + {!hasPermission + ? 'No Camera Permission granted' + : 'No Camera Selected'} ) : ( <> diff --git a/types.ts b/types.ts index 98e76da..8aac72b 100644 --- a/types.ts +++ b/types.ts @@ -98,6 +98,7 @@ export interface PairingModalProps { setCurrentProposal: ( arg1: SignClientTypes.EventArguments['session_proposal'] | undefined, ) => void; + setToastVisible: (arg1: boolean) => void; } export interface SignModalProps { -- 2.45.2 From 59be46c5f05978175a7213937f23beb65786c9aa Mon Sep 17 00:00:00 2001 From: nabarun Date: Fri, 8 Mar 2024 06:14:44 +0000 Subject: [PATCH 11/64] Update intent URL (#2) Part of https://www.notion.so/WalletConnect-integration-84b2f7377d514d7ead698bebd84f1e31 - Update intent URL to https://wallet.laconic.com/ - Move navigation to index.js Co-authored-by: Adw8 Reviewed-on: https://git.vdb.to/cerc-io/laconic-wallet/pulls/2 --- App.tsx | 28 ++++++++++-------------- android/app/src/main/AndroidManifest.xml | 4 ++-- index.js | 16 +++++++++++++- utils/RootNavigation.ts | 14 ------------ 4 files changed, 28 insertions(+), 34 deletions(-) delete mode 100644 utils/RootNavigation.ts diff --git a/App.tsx b/App.tsx index 118e126..8f77788 100644 --- a/App.tsx +++ b/App.tsx @@ -2,8 +2,11 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Snackbar } from 'react-native-paper'; import { SignClientTypes } from '@walletconnect/types'; -import { NavigationContainer } from '@react-navigation/native'; -import { createNativeStackNavigator } from '@react-navigation/native-stack'; +import { useNavigation } from '@react-navigation/native'; +import { + NativeStackNavigationProp, + createNativeStackNavigator, +} from '@react-navigation/native-stack'; import SignMessage from './components/SignMessage'; import HomeScreen from './components/HomeScreen'; @@ -18,7 +21,6 @@ import useInitialization, { import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; import { useAccounts } from './context/AccountsContext'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; -import { navigationRef, navigateTo } from './utils/RootNavigation'; import { useRequests } from './context/RequestContext'; const Stack = createNativeStackNavigator(); @@ -26,6 +28,9 @@ const Stack = createNativeStackNavigator(); const App = (): React.JSX.Element => { useInitialization(); + const navigation = + useNavigation>(); + const { accounts } = useAccounts(); const { requestSession, setRequestSession } = useRequests(); @@ -65,7 +70,7 @@ const App = (): React.JSX.Element => { case EIP155_SIGNING_METHODS.PERSONAL_SIGN: setRequestSession(requestSessionData); if (address && message) { - navigateTo('SignRequest', { + navigation.navigate('SignRequest', { network: 'eth', address, message, @@ -89,19 +94,8 @@ const App = (): React.JSX.Element => { //TODO: Investigate dependencies }); - const linking = { - prefixes: ['https://www.laconic-wallet.com'], - config: { - screens: { - SignRequest: { - path: 'sign/:network/:address/:message', - }, - }, - }, - }; - return ( - + <> { }}> Session approved - + ); }; diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index fa040e2..2158ddf 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -26,9 +26,9 @@ + android:scheme="https"/> diff --git a/index.js b/index.js index d9007a3..95dc0f7 100644 --- a/index.js +++ b/index.js @@ -3,17 +3,31 @@ import 'text-encoding-polyfill'; import { AppRegistry } from 'react-native'; import { PaperProvider } from 'react-native-paper'; +import { NavigationContainer } from '@react-navigation/native'; + import App from './App'; import { AccountsProvider } from './context/AccountsContext'; import { RequestProvider } from './context/RequestContext'; import { name as appName } from './app.json'; export default function Main() { + const linking = { + prefixes: ['https://wallet.laconic.com'], + config: { + screens: { + SignRequest: { + path: 'sign/:network/:address/:message', + }, + }, + }, + }; return ( - + + + diff --git a/utils/RootNavigation.ts b/utils/RootNavigation.ts deleted file mode 100644 index 10b50c5..0000000 --- a/utils/RootNavigation.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { - CommonActions, - createNavigationContainerRef, -} from '@react-navigation/native'; - -import { StackParamsList } from '../types'; - -export const navigationRef = createNavigationContainerRef(); - -export function navigateTo(routeName: string, params?: object) { - if (navigationRef.isReady()) { - navigationRef.dispatch(CommonActions.navigate(routeName, params)); - } -} -- 2.45.2 From c6128f222cdc198f1334af9cfb12cc472060f661 Mon Sep 17 00:00:00 2001 From: nabarun Date: Fri, 8 Mar 2024 06:54:16 +0000 Subject: [PATCH 12/64] Load config values from env (#1) Part of https://www.notion.so/WalletConnect-integration-84b2f7377d514d7ead698bebd84f1e31 - Use `react-native-config` library Co-authored-by: Shreerang Kale Reviewed-on: https://git.vdb.to/cerc-io/laconic-wallet/pulls/1 --- .env | 1 + README.md | 12 +++++++++--- android/app/build.gradle | 5 +++-- package.json | 1 + react-native-config.d.ts | 9 +++++++++ utils/accounts.ts | 1 + utils/sign-message.ts | 1 + utils/utils.ts | 1 + utils/wallet-connect/WalletConnectUtils.tsx | 10 ++++------ yarn.lock | 5 +++++ 10 files changed, 35 insertions(+), 11 deletions(-) create mode 100644 .env create mode 100644 react-native-config.d.ts diff --git a/.env b/.env new file mode 100644 index 0000000..c039d12 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +WALLET_CONNECT_PROJECT_ID= diff --git a/README.md b/README.md index 3baa401..00651c7 100644 --- a/README.md +++ b/README.md @@ -81,19 +81,25 @@ yarn ``` -4. Set up the Android device: +4. In [.env](./.env) file add WalletConnect project id. You can generate your own ProjectId at https://cloud.walletconnect.com + + ``` + WALLET_CONNECT_PROJECT_ID=39bc93c... + ``` + +5. Set up the Android device: - For a physical device, refer to the [React Native documentation for running on a physical device]("https://reactnative.dev/docs/running-on-device) - For a virtual device, continue with the steps. -5. Start the application +6. Start the application ``` yarn start ``` -6. Press `a` to run the application on android +7. Press `a` to run the application on android ## Setup for signature-requester-app diff --git a/android/app/build.gradle b/android/app/build.gradle index ef0e4f9..b22c5df 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,7 +1,8 @@ apply plugin: "com.android.application" +apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" apply plugin: "org.jetbrains.kotlin.android" apply plugin: "com.facebook.react" -apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle") +apply from: file("../../node_modules/react-native-vector-icons/fonts.gradle") /** * This is the configuration block to customize your React Native Android app. @@ -75,7 +76,7 @@ android { packagingOptions { pickFirst '**/libcrypto.so' } - + ndkVersion rootProject.ext.ndkVersion buildToolsVersion rootProject.ext.buildToolsVersion compileSdk rootProject.ext.compileSdkVersion diff --git a/package.json b/package.json index d3dc40d..05620d5 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "postinstall-postinstall": "^2.1.0", "react": "18.2.0", "react-native": "0.73.3", + "react-native-config": "^1.5.1", "react-native-get-random-values": "^1.10.0", "react-native-keychain": "^8.1.2", "react-native-paper": "^5.12.3", diff --git a/react-native-config.d.ts b/react-native-config.d.ts new file mode 100644 index 0000000..db50fa4 --- /dev/null +++ b/react-native-config.d.ts @@ -0,0 +1,9 @@ +// Reference: https://github.com/lugg/react-native-config?tab=readme-ov-file#typescript-declaration-for-your-env-file +declare module 'react-native-config' { + export interface NativeConfig { + WALLET_CONNECT_PROJECT_ID: string; + } + + export const Config: NativeConfig; + export default Config; +} diff --git a/utils/accounts.ts b/utils/accounts.ts index a4678a2..75e25d3 100644 --- a/utils/accounts.ts +++ b/utils/accounts.ts @@ -1,6 +1,7 @@ /* Importing this library provides react native with a secure random source. For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */ import 'react-native-get-random-values'; + import '@ethersproject/shims'; import { utils } from 'ethers'; diff --git a/utils/sign-message.ts b/utils/sign-message.ts index 44794c5..4d07e6b 100644 --- a/utils/sign-message.ts +++ b/utils/sign-message.ts @@ -1,6 +1,7 @@ /* Importing this library provides react native with a secure random source. For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */ import 'react-native-get-random-values'; + import '@ethersproject/shims'; import { Wallet } from 'ethers'; diff --git a/utils/utils.ts b/utils/utils.ts index 1602d0f..ceec650 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -1,6 +1,7 @@ /* Importing this library provides react native with a secure random source. For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */ import 'react-native-get-random-values'; + import '@ethersproject/shims'; import { HDNode } from 'ethers/lib/utils'; diff --git a/utils/wallet-connect/WalletConnectUtils.tsx b/utils/wallet-connect/WalletConnectUtils.tsx index 02e993c..e982a64 100644 --- a/utils/wallet-connect/WalletConnectUtils.tsx +++ b/utils/wallet-connect/WalletConnectUtils.tsx @@ -1,6 +1,8 @@ +import { useState, useCallback, useEffect } from 'react'; +import Config from 'react-native-config'; + import '@walletconnect/react-native-compat'; import '@ethersproject/shims'; - import { Core } from '@walletconnect/core'; import { ICore } from '@walletconnect/types'; import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet'; @@ -8,13 +10,9 @@ import { Web3Wallet, IWeb3Wallet } from '@walletconnect/web3wallet'; export let web3wallet: IWeb3Wallet; export let core: ICore; -import { useState, useCallback, useEffect } from 'react'; - export async function createWeb3Wallet() { - // TODO: Move to dotenv - const ENV_PROJECT_ID = 'c97365bf9f06d12a7488de36240b0ff4'; const core = new Core({ - projectId: ENV_PROJECT_ID, + projectId: Config.WALLET_CONNECT_PROJECT_ID, }); web3wallet = await Web3Wallet.init({ diff --git a/yarn.lock b/yarn.lock index 7c3b8b3..e0a57c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7511,6 +7511,11 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-native-config@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/react-native-config/-/react-native-config-1.5.1.tgz#73c94f511493e9b7ff9350cdf351d203a1b05acc" + integrity sha512-g1xNgt1tV95FCX+iWz6YJonxXkQX0GdD3fB8xQtR1GUBEqweB9zMROW77gi2TygmYmUkBI7LU4pES+zcTyK4HA== + react-native-get-random-values@^1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/react-native-get-random-values/-/react-native-get-random-values-1.10.0.tgz#c2c5f12a4ef8b1175145347b4a4b9f9a40d9ffc8" -- 2.45.2 From 7f1b2e38ef4939047392b5bb3ee8dbac55511bf1 Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Mon, 11 Mar 2024 16:14:08 +0530 Subject: [PATCH 13/64] Use separate page for walletconnect (#48) * Add function to disconnect session * Replace QR icon with WC logo * Use separate page for walletconnect * Change screen title * Make review changes * Make walletconnect page empty --------- Co-authored-by: Adw8 --- App.tsx | 23 ++++- assets/WalletConnect-Icon-Blueberry.png | Bin 0 -> 15660 bytes components/AddSession.tsx | 118 ++++++++++++++++++++++++ components/HomeScreen.tsx | 25 +++-- components/WalletConnect.tsx | 111 +--------------------- context/RequestContext.tsx | 9 +- types.ts | 1 + 7 files changed, 168 insertions(+), 119 deletions(-) create mode 100644 assets/WalletConnect-Icon-Blueberry.png create mode 100644 components/AddSession.tsx diff --git a/App.tsx b/App.tsx index 8f77788..0a5aa30 100644 --- a/App.tsx +++ b/App.tsx @@ -1,5 +1,6 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { Snackbar } from 'react-native-paper'; +import { Button, Snackbar } from 'react-native-paper'; +import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { SignClientTypes } from '@walletconnect/types'; import { useNavigation } from '@react-navigation/native'; @@ -13,6 +14,7 @@ import HomeScreen from './components/HomeScreen'; import SignRequest from './components/SignRequest'; import InvalidPath from './components/InvalidPath'; import PairingModal from './components/PairingModal'; +import AddSession from './components/AddSession'; import WalletConnect from './components/WalletConnect'; import { StackParamsList } from './types'; import useInitialization, { @@ -32,7 +34,6 @@ const App = (): React.JSX.Element => { useNavigation>(); const { accounts } = useAccounts(); - const { requestSession, setRequestSession } = useRequests(); const [modalVisible, setModalVisible] = useState(false); @@ -132,7 +133,23 @@ const App = (): React.JSX.Element => { name="WalletConnect" component={WalletConnect} options={{ - title: 'Connect Wallet', + title: 'WalletConnect Sessions', + headerRight: () => ( + + ), + }} + /> + + diff --git a/assets/WalletConnect-Icon-Blueberry.png b/assets/WalletConnect-Icon-Blueberry.png new file mode 100644 index 0000000000000000000000000000000000000000..66625b2c401966286d25cff209f80fc341e5f60f GIT binary patch literal 15660 zcmYLwdtA%!`~Pc=7P6XnmyC{TdQ(f$rXyPso#vEdT2vw(w$k~eRFgy-mZma?!kZ8} z=%fRhH4zdzsJ2NdTI!unziWAazP~?u-0eQ!*L~gBeO=G{`C_@bY@9z=b1s4)^Bp%) zw;~8yf*=ZqloTO{7ieh*$(*1~yO{`LtSA3Ng~MR^}C_v^0Qb!~RG_xi1WX_vLOdaj={Z}WrBc+3lhN(YA%s%1UP zb$x`3UzsT;mi;rES(wgv<~p6axGH9RWEEn!^skpZ{fOT$;`(Kg=U?fq#2{av2G-z_ z-@zNrJEq3-+iahF-<{c!vbQy{Qa{XL!aK9jVruH>=&Q=<$;e;L9cTozK!OBi?7b56 zaWdNS?z9HeS#6~E*@2GJztaYMbySd1RS6<|<27deu=wZXKjS~m8)Uj^mojeq*Gw`; z4_cUP`ITYq=G5ThpR$6C z7?+)|9iaY$CN7|3?Fs|h&QwnBPTE$~H=*<^?bo;QC#|SFb*OqZr1?klL~76=;)}gI z#QZ1DxJ(ndWgtX-*b=tkw_j{si97qLB;wciRX^1Gb{M}j#>8JzVj{~z{;qTFejM_l zeq>}cO(_&vrXxiC%Ti5Q|5^E$NtliSe_9Rp+p7E9h9a1mkB14#|Wi()xtwsUh*FU<$0V;OyUh zsllB=x=TH~lE~Fb;XZf`?j{UZ8|IB_^ZVwD>#h!Of8INQuwj?x_O_c%t@Ajh$axeq z`_tY*M+||&MV~IPtosh?64mes`TS)|W^t?28(VdcdDZ?f1a9Spj)M~!5B^4dXnpRl zFj+pl0oh|HL@9Rd49&x}IEvW3C-0VeYSiHKHhKgrnhzM+284#)vb1xB6{t8pPsj@P z#M@=S+!CCgqcTIbg}maU@h)6+|4qx?lc8%!2H3h@*rk%^Z3&sGh@LZ+wYU3Ph+gdBpYM-}M`6I#DwLoCy@G2GP&Sm7BW z=2BR9(yxo&?h4g8?Y_+Hap{|RC=EDimzKXkUD9bvxG{&BM?Qhi+pVzlusmDm1=BfscCxKU1vp@Fu_|^W+$l_ zHgwJp!1s^XUT;yT?k8zuxwGilh25SOh(Qf1*p60%*VsRbAN!>LYM#k#B;Ys4Iqv|+ zLy2et1?kcksDiq2=$IL0Njw#I-UA`3q|>d>^4@=UjvV(S26$s4!@3`b(y(@ae{RPrKdZxk zO>cgW;<9efEN_d+tiy>!%$di#Bz|l2?@T}Wu@&)jiu0C5t|=}?Z^TKeX@9*ee<2Af zHUtepmPa>8T4it?72a;)qA|VLFiY2=jjiXAYW--TL6hx84<$FPlqytIAGT6H@Ax~m z?0xI-SW*&;e6Of}wP7oMz~BX{GdA}D89%^;)r1mFF~{FjD16UNWgy`~EmLdE2rSU5 zAUiYn`QJ@Qr>e&tMJquazN966MJe=HYvnrRTAZ)&xuFjK@5vQpM7d@dU&3aQf2eI^ zAR0oMzIu)|Ccw-TlNoM8MotSGzE_7CNa9B`(bfMxHZ0CP4|B|6k%b?;vhrJ<8m3p4 zFGRRS8LdvQsxClLGBG7xDNaJv^GH-gcKq10hPybW>Zwx*raWT4CgPOaa_gy(%cXSVe?T#lh!@5aQ<7I* zAUhA~V8hYzmtqapW`=d)pTXKR&Ot`}X43to4*Ej+ENCTloRVu-{Qe5CPOR@l zD>@b}TW5~sdAYaZ=NgLWb~&ags8tst{(||x?Tc8OQe(ZULiKPKi>zGN*M!6;hhNrI zeo=>4)E1!PpZOoiKfX0mr%9KNweIsUW#FLIL+>Thd^nGHaFV90#)1xenCG#`%$~r( zbt$oPm|q*x2^i#Xn`JO|5^5yYuP)Sst|D;KCbj$rwdekCqmh#nk37Voz4msENo4$b z=tbYLz0m!JG>B0Apf-U@MykV1o*dQdL~8I@m<9dL0)b_W*6O`112jZbFhxVG(+P^V4yjTqC#+pQ)64>ybLZ~SBbKTktqoYcD?Rib%*Uu|Tr;G?nD zIc1T2A19HE^aS(=cOR6diEF4loi=VkRqB%)o=5Ix^2U-^GWCR6i)dK((DLlU=f_TG zpCy(JEdHfFhWj>xAq=j|ZYU-Bu7y5y1oQ=uxBXNxF5&V63d3>DAp~ch+21w1-Kt`@ zx6dcIr{avu|N5{yY}41{m!^DQ*H>Gym_8J4vlR_{^;1q><>k{)>@R<7D-loHQ`en# z{G#W!=lm1P4la`>v3emVs%8#hw`9OC4$l6eLu#e-l+QWC*ym0+fJLfPj z=?my?xtY9;r)ymUsK5J2By4gJX!W&WT~DPNo+Oo_QFl>4u!!~Z)yWnGcFR(H*!#(U zD|+_inSv>g*dK{&vY@+cEAy+jcQVRT`6PYT5qVbyTP$~x>_&4!zCNFRWb*pb^tMP; zQ;`Ku9Q4oa3GSIEJC>e#=poi^IJvj&ibY^iM7oZ7Z5_`iM2%Ior4VftdbyF=9oTjY zTidtnN#xRPAtcUCva_ja{_?=hCWz6Yb=cV<>w|qxVKGsc8})RXW~nhRH90SvT2X;5 zcrhM+9B&|gR`k{6<=t7gf+{@3esDw&B$1tWtz<4!U;TsI=YOL+(D^yNEP(fQYFz*` zC1oP6Z86r(n(m_Z*URYiH1(2KJJy=UdPM3}$HH;maPb8f8?^Om&9 zqNF8XKhQ}nFSgBN)Sy8NyfgMtaD^c4ONI!^taT^PYN3p?AStj65a z`Q*AV3*F|KTozb)84Mp=3nIVwb7y?`%d^P$<`1`6D6}~aAd49PE_t_iWw(kcQ};ze z6jaqr5JgAtE*$!2ST|3_K=QEDs6htvOPIsxq_s(Fv+Gv}>NWXwfIk^usl?o)z4dV0 zfmYswfmiG9v6m#o<}@CC&+BqYv$^Pj4As(YwPpy?~9wR?oF7w`Gb*XqJCW+i3$GOa#hJnC~V0057=Ers8G#wZnGG7QGdzv zVFsKu+f(rFe4t130=mmQ@Eegc!^^X^ZA~gvJ(raQA~`EBkY7p`4|i<2i@^qYfW!<7 zw_Kf*=53=tRy4fM)3pUAJ9Pstud$(1YmC6IWy#zRvwVeq^(n9<>xx$rewVY{|9cFE za&&jd0!$qZQ;5AzHjtPfyf2iNEXwE|=k)xF6#LKeykr$8s*CwgT7e*~9_QZK)o5_d z_Y^XW1PLql7BXPn`VF`JZJu~;7Qu9rwS|5TVL=z%tBLk;-po(GBVe%pTjRpW3yr=S zPEX%tZ#jH^Z>t}8!oIELHjQ7_I3;{~pI}f@ad}R7l}FZvW@EYw6THPuLsi#Xp8m7n zSN6N87_O$Ke7JRrB0_`~)r;8#`2jWv9rTWIk6bEz^IYH8C2jVecWswP)_QCckf`J$ zn8pQ?@1X+^N{_YO9*^Jsb->LsqnBKZ6AP1TI}Yy*JKT8tO`qpqQ=1vOf&_A}jV!1B z$*aZ51mB%bLcg2_GWvIB3QjOfl{QPlhWA7uP zBVTUQO@l>xunVv=kLA%JgkPbVJ945$xgY!&IKA@66dwF3^$%ZU+JL2%p zdh5vAk@ixsQy4mSX|T;vcSnzZ+{gAs>LAPqyCl2UVrokt#VmtX6mISOuILL>dkTlj zhIJ9iEz3s`MqIq!QjXBWGnIyYWY=3jOBzp7XlyWFL{5f4J`d%weWqduyBIBR- zM6b5-maThO2>MtZ-d|e}1zE%ty;1^UU^4Q25lAABSHD{7=yAd0xf5Jb@q1gpYzjDP z^34Q-wqo^ilZrEoOEwQVaVXOe4j39puSSV`LXiN5*LWTTf1D_PWkraXtWC*UjiKO7 zg?$}KcE8^8TAPMNo0+EMWbsr4%wvevk^4fwOqHMO9@)Sk2`ut(|FfQnZC|cGbl{-x zv40ti*~kl!1h(z?LPJ5Rp9fcHRTCD3gV{ zLb`rew284Y^7T9^j#HsNcY%bc8(gUd?UXn0I$pNVI|&|k7AfxRJT#)XTom`ybi{)o z`V#1tivAM&)287l)I9w(A;>Xvw5=K;IB_ZKyCy8AC`f~l=(d$; zx*G=lxZ^Kr;rrX=#uZAQwjLK1&lmjIaip*;&b|UIN)bxd#rKaR6<8sau`9?&jJxOb9pEn(qpO})T8J#9N0#v6`+p^y)8{&rk z9iAu~e|7mo6y3u~NLPf279#Nmt*D6>&}CSCd~C@0Prpp|fb>q9{>8o>Hm9qMJm)5n zg`2`u8?*JRY=3BtXZA!_2(ob(W+6xsAOgu{(l!^iRmIPYDuYs@4hD%2o&#VxLU{2d z5E8euL<|CvXA&8HEYugG5F`%(NBA-OPc?w$5X5dhfZGTH$3WhsX?jIPyrz6#hRhp! z@5KFrD5%s!+*fj2!P6LAIG-Yw%b$O{Uv#Xz>t3o6<%|exy~bjuBi>Jz^y+5Ed}Vyz z@oumbIMVimr)P z6w&W6Nw&E*y<#lJ#)nBiq@~~))$${ye@c1mzWm0Eyw-7aC@I3XhtTmmv*+aAxhTn< zw4t=qVWtno*>#A&)|Ijs_rm6Ph~?J$T-+$?#J9%uDN%G@5a<#HkxYhzFAwDH^Tsp>kWh^v|Hhs8U zgwovk)J7kYwO^n>{6yrtNU~_3@-di?VyHSrnl&|3x_vz-urWaEk~7zRp?s2gI*axz zA8Q=oE9H5QT=B~WFL;qdMAyUP@jlzbO*s>!J8d?z+)4$bS9X`Lxi&^DG#3=tk?q7r z6&fF58t3-{Jo;;p()c{0nGV52APl#8$1?HW5uJFs71=UWbu&uql0DC2i6osm_MWXj z9dck7vOVt{p+nxh_v4rSy`jU`#z=vyg-gkR;Ej zPFkZmBiJs@^r1UG-Q_olxv(5)G3SWYOap+nh#tdqSS=V!Ts_|8daEPlmNJtTYXZL&)G1*A&S;IRR{sEPJ`?lx@vZLi zvf2*nm|$3*RLkhj6)@8HCiOpJOp0!?o5cS9bpl}9O2;cFtnHno9l=Sm%GzjcY{ZYnQ|YK=jmgJ#iUiLjJqsY8j|*N}6gxoJaJ zVdi?N|Cel6ld`e0`qlY3aSW^gqbjbz{YeURMTScxZHiN2)QAJHGs~zEgMX4j11%|A znW;`mgsPVJROUPI(EWBC6|WggigB^wWNF%!r!WpCq%ixT%KVKycy!>Xd~?jK$}drI z{ub)TkKXQaT`?#v?Fud8yk_;Pkt;OkBU+b)b^ACOq8@ywqB`YjR7=}s#IBz*+vywj zuA`ICK7wW-t?n9H*;$D9Af__T%%1~PD zIl22R-2angqAB4=Zv|U_*L`dcIGG1=Vz#a*VK*e(%tdL|2cfMrVr`*H_)Mv)nBCp4 z=DC(SroSjgQoaeK*04ZYy)spv*vY74*A}J8Uf1s9xaN>Bmkb1kt~_+(0`U&Q0_oM{ zF|Vv*oOq3@)Q(M!SW`!C!qYs?zwVBHSlh8rc}GJ+keyjSWd}nW-!zNnapyHQX^FSj zfLFmD1F4-_KjqzOGUk?{;P%MfCw);tiLS!^%4?qX;0rR-uJ~|y=p7GK)*Sp1bGV6B zN(h9&#cioN#n}{^9Mn1@=p1kIRAbWG_1Vn4xyH$tytNc=L7=faQ{}$OBcFQ>W7;62 zYzbe9Yffvo8doLy$=$owl$?bgb89v9MNw7f_j4=)qxOzFf)oAOK;Zkddq?#l*3fQ^ zp2s5|h9JY8cZ;%i?E}2rPFLXjq6g1iNVBo5a1ljigN#}u7OY-ASb5u@$oz*n#=QJ-|cXGp6GS)BQ3nd zIJv~0hraqiB{+?Qaab-W6&H|f2_TDLm0;}Y8k;)nIx=xCO=R6}7!d=vRT%e)Mi;IN zudzMMG-TPhYM7d|8bX*1(qXNSe+%u&mau!;`;_nN1!I+=^_h|Ol;bwA*dk2+BXkAR zv~F3BVd+B^RLldF`zaM2nh&;d(->H7Q9^r(Mi)}`+-BDBq5Hz!{gi&+LQ>rD&f_%E zxzF#hczX_0e_S<)0|4xK56rudy^lvQAST{2E5{+%(FDbN>NFRX&rfILojA>jmkp$& z$)$vRUpPhgRA??4kXS&nEL~;^x@4vrjP0j#%n(SNIA-yf?ukm)m_3zTt17Kdq>|CF zhO3Z7=JeF=pYFOyJg`!>md~TJn4Kf^a{aupA?{WV&-|Uw^$xc|eU*eX! z_w5jEF;US8A0%G#QVOmNPoKU1H-e{OiI)s;UOTv6MlRJkdqPC`Z8h}mQ$T6F5lBrevU>@^ESKIzV738CiAd3`?P4DAR zc=v|dNzKP_k~q#@%tTjQnTk`MDWA7Fm3f#$@IB$e?m5sV6BE~%)RQ0DbG9vpRyeWK zTlOI{)3}|-mj;1-PFEFg-^`=vQOVojL2URy#`N<%LNNBcns~blk0Pd$*QiR~+VLpY zspNE3soM&g%c>;uBlvGI&E;SndAkE=n;PB48UFLSFZ`6s_^2nkvVhMML{EMHR2&HJ z+INOGd#Vh!{H^Xl+yM(uaPK=GxUhD6Yf3?5WH!v4v z@XUsK@~(W*2Vh-zDzCP#M8nHx>X_Dv-d!tYA@_y${gk&-U0Ar5;0_u)!niL4(<6Tq zU>XXtR&?km&-r?Hwh25Tg|7%oP17HPxiof*)4VGUBDMyt4=T8=pWnBm63(*l90B8- zYq!FJKLPf#TIX)d?Z&e|B{%zTNZx*n$ehP!YUC19^bMqLNrZe<$E&|f7fS-49l#7b z@M>RB$rCWQR4RFXiD*B)(nHM5oL;y~b=aO)y9po$M_#QGNG&`{M*AGsHg)mmH}h`> zBDyCZzf2$ALHIm}Xvf&t?-Eyf`=aqQQ} z0t+mJXZ{pNy_fzh{Yl}&y)r#_kr1w{8ITOFj9$JQ0HQm-zpLN~ZmC+j0gdSBD z6Ey2!eNhzs} zOAI%TAtpIOSLByW@_kPwFEkWot)aQ--DOfahEdC=j;mv8UMpd0OfC6%gZhLZ;Tb|c zt|FxFshaH0z!Qb(wpB_;ycwc@LgL<=z|(+{lL%e_T=XKB3@o)a?HH=J*MmjmygJG? zXEQJPYVmOu$7^3h+ko}?rsVtp1F3mqy{c2YO#kAD&K_@hxr!`u-k({zutD%^Vl!e= z6+?ZuO@gx%x}*pD$)i@jF9-iTWyQdY!O{->8!NDDO#r;IZJe{BRfYJif*1KqN+u0mA~Y>xuT2rEtPRNnUud7 zH0<=y`y1SlvJ3zW#4+*XgbLI11 z7%VyjkaiBq_YLT9$!5mtog72G84;f%`7+8{#`jY>l)gGLTOUT>v6emb>fqFktH}{V z)}mZ*Ssd}L6VGgMTW)<7$kQ0A=|Z)&f~qrOEpfketb6O z*L#u}IHIFTTPa{~py#AAFX@U_JgZj~wzKu)KBW-AhGu_ADLC+&;F{S{88g1~+o(&X z{K2i!?A)s~<5_1$k5mXEdhLl?e0)j0O>^X|F*|O<4q21EOiiYI45LjGa>6$U$ci$-ZtNKPLkLdydnL4!?LAPta_-OQ+p~NC|IWmyZL$8Q8zBb7g= z#)iVjb5(44cbLM&)Gn9O(YEpXZMWHW@H9thE@&(%dx z_9>$lEs)N?uk!k=1iCi=({%(|oLl(GB9>A4J*uUh|qykBeW5)vs{J6(FL!Sw2tTvm7;#kW6N-G>m>EHi2gB@ z>}}IZYUc8`S+Hg2N;DKonq~UA-zl|4DzBAjv5CaNRHmkm2qW=Q>Rdl^B_Y&Tf5vR8 ziHia0s3WI;@K{(us5tvRl@WhARhB?1;X0%aSt`l)LRolbPu!FFyk$^Ukt)l96QUy+ z)DaG9i~L?#2FJqNYmjPiyBgS1>KHm{47}<2IO#@JF#?z7Mo_{H9NP_CTRUE}YH%z< z!#2$ej@6#2j7=KTo2eWHl`9rVqh=}(ZsHEkRIWtRutcTcNcS!YhUt$f znea4tP>UbSpHPzXOYaP8-U8l>-`ZDv8x#5UefyV@^tw9j1OdZMA`en#pm+@4k)zd6 zH=zWY*Rwy2PW+pX2ZT5Ra}n$izZ_FDv8jufT+Z9ZOJl@0WbJ?iWE<7Bb0nwa&gEoa z&3vpRf>3&lvMUT*quC#pQRiK%y0f3ttVC=jt^8#!ck zP}bLJSAH2?bae581v^_aLX6Xfz#MTyk`d4xMQds7#4=H7ZrN#GEXjMj)l0DkOjrt1@*Y()u z#oT$F>fu_kNNRrQ&G^~EHJYn?3m?5#lAW8QsQJ@0JB;8uKy^8A(A?{+`)egd&D9N_ zKh`)<493>HUrqOj56Z)_qf)LHuF^F9+HxT%Plr7hb6~Iep{)n@GQVseQ7E(h>T)#y z_$&6l5Ov*p`Q`AhMLF-i3&lY8UKKn_YJ^TrxoWVBo&ioOcRvB+y#hyVtCh$ePJwuK z>G=xc`t^FJ+TITwO?6mUsN1`lj@T(wNkb={EA; zRE}#rYmBO-tZDkO=W~m4I#*bKws3`}si1`eMhP%|Ki)fHSWWZ>K!BWw2IW>WJ(xfI zNlEFOMV1`BGWTjKTqU33Sdx;AC8uHjvC8F%iSC6A zAyO}Q?*l`8>uNN( za({lP(mdwSJ?6kfmdTF=#p97G;D6ASChSgf>d+}A*(unb`{^?E;^}%|W9nbTiQg7B zq+KxOsEFRSGng+jF) z2f*kNOEn)oabduVI;HpIJuSM&3Uzyg{Wh8t*qVYENY`b%qv2UjX}4o;Iyd`wdKZl=zyU$Em5anei*HZZ~u^@X((I* z0s0w<*H;yR!G`M|eQ}7&Y;tdn%~; zQB@vd;FNHesWD@N4>G1IVWGeE1%H3R8t2jYyr?X$EbqVfSxoggYQ#w=Q9^PmW7#1u zQDT{jDqw6r{PL}U+5v2ku|Q(AnTKAaCO!`s7XSv9I7vShG8TvdFt7vMz*0@p4~t!d z6Q|>(cHUHS6x0BWYmurr9Wce^kO4KEd1$o}Q+~N0EQUlAtuHrDPXv6FXlk77Q+NAq z{2V~Uh;$JR99g$0` zSF#v&WVbhe?z{Dqp9L;~;G!*$04hece<~xk>+x~VLwsI4m)o2@kk|{n1f)n?Fm|$m zOw^^h6ef}3rmu!De)LaLp(Yu2;i1tB#PUT$qP&4?rUcNM9jC}_CNa~Xi#!4P1X@J< zpCm7doCxbwp^`BmAxIL{B+Agq#_;6=GMtPv+JeO=iM|WF@TB-Ooyg|;u{f|f1*c+C zUr&qxa`EPyC8{N4}cg%*1=tPpDi4-#Wg%f04=ugc@2>g{0Q|2g-0--T&{vbKU zpf!cNk`56k^YE!O9YAzX-1v~fH7|Z(wmaZxicg6KfAfgW;b%KCeMfX|IC0JVER0Xx zooh0ibefQVqziv5?0lom@T1`kojL*D`E%uDc&6nS8Jz6nh62CjPam&9EPRB^-F9*S z{mUG{oOv{tZBC*qu*6%y2GbE;(d6?CSj>WCLOxJZZUK~MMRRGcBRk~~@;BE&K+WW? zq={C}BrcDV$U7EDiDW)+Hy|~T03-hvP(W7$lLvquNZzSQwmb7E!S0UWe@bA@!O%Sn zWGK3#p-wrQ+x6LIR`&yy*UCUxt0SKl)vJEi7SThrz+=<-@7(5Dz$y$%Hw}Ri%Jb&q z2k2TSXy9Tob^>M3knY0%UwGRDq=yU;K7r=K2JL2-sq~LtZ$SajcMqN6{?wC7Zd@QS z-@>C5Qpx@6d6Ypa*;!3u4%0IM_6Hz)flvc7TzR!L$XL&-eLcfS%9$SFvra8L^ph$+@31imgGuX#v|zo5m@rRBE#LE-HN;NZZ1>ce(l z`?0mt1}YecvPeMj`Ei=ufAiB}pa6w?9s%;KsMXMkOGrV_VUprWzIf@vfbp%&7!YZwYjMg~({|^9e)Zc!q!GgNLs|M<jX-PsR2{ahMdbE zQZ!y%^dI=}KxO@_B;aw|45s(>>(}*6e;bTv2Zw0T%&=*(TuJ0K*5}xAp5LX9_&{Q&!G?M@u=L!-mT3QKU zf*wu^cq3&!tbI1zH}I%rhx54Z0Ph|rfe63eKw4L?>gHxawq?Z}7WGqzs2coSHE9vV zri6eBf%HR)gEqe;@GDJt< zl~Kt~r}wtfy~@&uVt@#zLBl#-cz)YwJ*O6!AYAR>_LY#6Lz45(Sisn!b(NF(_=03o zU?w4D4txPX?I6`LHrzqnMu+iLnGJ*PRI{rCYirL(xe@1!>yu{tGnyQ!0MR` z`rhP$m-zl~bqW$SWc0|RH6@P@+!-QU22WcGBs&Z8feRiR(fyPrk{XkXV#}_|vomA9 zdj8*$_1pziF0_S#wB_W*txEt6K!*h_?OE4wlaLYt$A2*dD!^TAVApK9nLxmN&1x7E zJ*LI(2Hr6V^xC0p{uJCc8Sq&Lnn><|W3(7{)2wTP27$F**90upXEV1}nI(&2G2(Hl zGD{Xi{z)Mm*f^l@IOvPwbOm$?aB;Y#X#f}(YKz#Z4DA}ckE=Wc&-7U)<>NPXh5Fg~ zCKqmf;xbe-PDkCK!~byl-~%AL%vNVnu{uT03$1 zT0k9L3Veo6OFDLf|0PWqScVD7qgQSNtwm1Yb4uIWns6ZGOJru_Z0g^zMawl~$^ls# zU2TdPn86g((%}0T2B@stm1F~$QTSOFIjC?zq02nf6#`4U>zY|G-A?!vEZj{DVLi&o zJpwX34IyLub$Ny+%}<`;TZ8}L=slCMZMQrFJ`($|d-+Vp4o7(gd=UHLWnK5g7MMq~ zfqeO-)RyZgOjhUZw*Y{c2wzYdlx{{aimV=AeegDI!L(#kuJ~i0$#q`~%C!&h}77H{%h~rcsfs z$loQXW6c{|@pGHuh_feAAxUKTfI*?xZ>{ { + const navigation = + useNavigation>(); + + const { hasPermission, requestPermission } = useCameraPermission(); + const device = useCameraDevice('back'); + + const [currentWCURI, setCurrentWCURI] = useState(''); + const [isActive, setIsActive] = useState(AppState.currentState === 'active'); + const [isScanning, setScanning] = useState(true); + + const codeScanner = useCodeScanner({ + codeTypes: ['qr'], + onCodeScanned: codes => { + if (isScanning) { + codes.forEach(code => { + if (code.value) { + setCurrentWCURI(code.value); + setScanning(false); + } + }); + } + }, + }); + + const pair = async () => { + const pairing = await web3WalletPair({ uri: currentWCURI }); + navigation.navigate('WalletConnect'); + return pairing; + }; + // const disconnect = async () => { + // const activeSessions = await web3wallet.getActiveSessions(); + // const topic = Object.values(activeSessions)[0].topic; + // if (activeSessions) { + // await web3wallet.disconnectSession({ + // topic, + // reason: getSdkError('USER_DISCONNECTED'), + // }); + // navigation.navigate('Laconic'); + // return; + // } + // }; + useEffect(() => { + const handleAppStateChange = (newState: string) => { + setIsActive(newState === 'active'); + }; + + AppState.addEventListener('change', handleAppStateChange); + + if (!hasPermission) { + requestPermission(); + } + }, [hasPermission, isActive, requestPermission]); + + return ( + + {!hasPermission || !device ? ( + + {!hasPermission + ? 'No Camera Permission granted' + : 'No Camera Selected'} + + ) : ( + <> + + {isActive ? ( + + ) : ( + No Camera Selected! + )} + + + + Enter WalletConnect URI + + + + + + + + )} + + ); +}; +export default AddSession; diff --git a/components/HomeScreen.tsx b/components/HomeScreen.tsx index 9be6809..24aded1 100644 --- a/components/HomeScreen.tsx +++ b/components/HomeScreen.tsx @@ -1,7 +1,11 @@ import React, { useEffect, useState } from 'react'; -import { View, ActivityIndicator } from 'react-native'; +import { + View, + ActivityIndicator, + TouchableHighlight, + Image, +} from 'react-native'; import { Button, Text } from 'react-native-paper'; -import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useNavigation } from '@react-navigation/native'; @@ -16,19 +20,28 @@ import styles from '../styles/stylesheet'; import { useAccounts } from '../context/AccountsContext'; import { StackParamsList } from '../types'; +const WCLogo = () => { + return ( + + ); +}; + const HomeScreen = () => { const { accounts, setAccounts } = useAccounts(); const navigation = useNavigation>(); - useEffect(() => { if (accounts.ethAccounts.length > 0) { navigation.setOptions({ headerRight: () => ( - + navigation.navigate('WalletConnect')}> + {} + ), }); } else { diff --git a/components/WalletConnect.tsx b/components/WalletConnect.tsx index 64a489c..1461f6d 100644 --- a/components/WalletConnect.tsx +++ b/components/WalletConnect.tsx @@ -1,107 +1,6 @@ -import React, { useEffect, useState } from 'react'; -import { AppState, View } from 'react-native'; -import { Button, Text, TextInput } from 'react-native-paper'; -import { - Camera, - useCameraDevice, - useCameraPermission, - useCodeScanner, -} from 'react-native-vision-camera'; +import React from 'react'; +import { View, Text } from 'react-native'; -import { useNavigation } from '@react-navigation/native'; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; - -import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils'; -import styles from '../styles/stylesheet'; -import { StackParamsList } from '../types'; - -const WalletConnect = () => { - const navigation = - useNavigation>(); - - const { hasPermission, requestPermission } = useCameraPermission(); - const device = useCameraDevice('back'); - - const [currentWCURI, setCurrentWCURI] = useState(''); - const [isActive, setIsActive] = useState(AppState.currentState === 'active'); - const [isScanning, setScanning] = useState(true); - - const codeScanner = useCodeScanner({ - codeTypes: ['qr'], - onCodeScanned: codes => { - if (isScanning) { - codes.forEach(code => { - if (code.value) { - setCurrentWCURI(code.value); - setScanning(false); - } - }); - } - }, - }); - - const pair = async () => { - const pairing = await web3WalletPair({ uri: currentWCURI }); - navigation.navigate('Laconic'); - return pairing; - }; - - useEffect(() => { - const handleAppStateChange = (newState: string) => { - setIsActive(newState === 'active'); - }; - - AppState.addEventListener('change', handleAppStateChange); - - if (!hasPermission) { - requestPermission(); - } - }, [hasPermission, isActive, requestPermission]); - - return ( - - {!hasPermission || !device ? ( - - {!hasPermission - ? 'No Camera Permission granted' - : 'No Camera Selected'} - - ) : ( - <> - - {isActive ? ( - - ) : ( - No Camera Selected! - )} - - - - Enter WalletConnect URI - - - - - - - - )} - - ); -}; -export default WalletConnect; +export default function WalletConnect() { + return ; +} diff --git a/context/RequestContext.tsx b/context/RequestContext.tsx index ee1ad57..c78233d 100644 --- a/context/RequestContext.tsx +++ b/context/RequestContext.tsx @@ -1,10 +1,11 @@ import React, { createContext, useContext, useState } from 'react'; -const RequestContext = createContext<{ - // TODO: Remove any type +interface RequestContextProps { requestSession: any; setRequestSession: (requestSession: any) => void; -}>({ +} + +const RequestContext = createContext({ requestSession: {}, setRequestSession: () => {}, }); @@ -14,7 +15,7 @@ const useRequests = () => { return requestContext; }; -const RequestProvider = ({ children }: { children: any }) => { +const RequestProvider = ({ children }: { children: React.ReactNode }) => { const [requestSession, setRequestSession] = useState({}); return ( Date: Tue, 12 Mar 2024 13:40:34 +0530 Subject: [PATCH 14/64] Sign message with cosmos accounts using signAmino method (#49) * Add functionality to use cosmos accounts while pairing * Sign message using cosmos accounts using signAmino method * Add todo to debug signDirect * Use cosmos wallet amino method directly * Add back displaying wallet connect data while pairing * Reset state for wallet connect data on closing pairing modal --- App.tsx | 63 +++++---- components/PairingModal.tsx | 127 ++++++++++++++++-- components/SignRequest.tsx | 15 ++- types.ts | 2 +- utils/wallet-connect/EIP155Requests.ts | 34 ----- utils/wallet-connect/WalletConnectRequests.ts | 65 +++++++++ 6 files changed, 227 insertions(+), 79 deletions(-) delete mode 100644 utils/wallet-connect/EIP155Requests.ts create mode 100644 utils/wallet-connect/WalletConnectRequests.ts diff --git a/App.tsx b/App.tsx index 0a5aa30..5697b09 100644 --- a/App.tsx +++ b/App.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { Button, Snackbar } from 'react-native-paper'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; @@ -21,7 +21,6 @@ import useInitialization, { web3wallet, } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; -import { useAccounts } from './context/AccountsContext'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; import { useRequests } from './context/RequestContext'; @@ -33,7 +32,6 @@ const App = (): React.JSX.Element => { const navigation = useNavigation>(); - const { accounts } = useAccounts(); const { requestSession, setRequestSession } = useRequests(); const [modalVisible, setModalVisible] = useState(false); @@ -42,13 +40,6 @@ const App = (): React.JSX.Element => { SignClientTypes.EventArguments['session_proposal'] | undefined >(); - const currentEthAddresses = useMemo(() => { - if (accounts.ethAccounts.length > 0) { - return accounts.ethAccounts.map(account => account.address); - } - return []; - }, [accounts]); - const onSessionProposal = useCallback( (proposal: SignClientTypes.EventArguments['session_proposal']) => { setModalVisible(true); @@ -61,28 +52,49 @@ const App = (): React.JSX.Element => { async (requestEvent: SignClientTypes.EventArguments['session_request']) => { const { topic, params } = requestEvent; const { request } = params; - const address = request.params[1]; - const message = getSignParamsMessage(request.params); + const requestSessionData = web3wallet.engine.signClient.session.get(topic); + setRequestSession(requestSessionData); + switch (request.method) { - case EIP155_SIGNING_METHODS.ETH_SIGN: case EIP155_SIGNING_METHODS.PERSONAL_SIGN: - setRequestSession(requestSessionData); - if (address && message) { - navigation.navigate('SignRequest', { - network: 'eth', - address, - message, - requestEvent, - requestSession, - }); - } - return; + navigation.navigate('SignRequest', { + network: 'eth', + address: request.params[1], + message: getSignParamsMessage(request.params), + requestEvent, + requestSession, + }); + + break; + // TODO: Debug signDirect + case 'cosmos_signDirect': + navigation.navigate('SignRequest', { + network: 'cosmos', + address: request.params.signerAddress, + message: request.params.signDoc.bodyBytes, + requestEvent, + requestSession, + }); + + break; + case 'cosmos_signAmino': + navigation.navigate('SignRequest', { + network: 'cosmos', + address: request.params.signerAddress, + message: request.params.signDoc.memo, + requestEvent, + requestSession, + }); + + break; + default: + throw new Error('Invalid method'); } }, - [requestSession, setRequestSession], + [requestSession, setRequestSession, navigation], ); useEffect(() => { web3wallet?.on('session_proposal', onSessionProposal); @@ -158,7 +170,6 @@ const App = (): React.JSX.Element => { setModalVisible={setModalVisible} currentProposal={currentProposal} setCurrentProposal={setCurrentProposal} - currentEthAddresses={currentEthAddresses} setToastVisible={setToastVisible} /> { + const { accounts } = useAccounts(); + const url = currentProposal?.params?.proposer?.metadata.url; - const methods = currentProposal?.params?.requiredNamespaces.eip155.methods; - const events = currentProposal?.params?.requiredNamespaces.eip155.events; - const chains = currentProposal?.params?.requiredNamespaces.eip155.chains; - const icon = currentProposal?.params.proposer.metadata.icons[0]; + const icon = currentProposal?.params.proposer?.metadata.icons[0]; + + const [walletConnectData, setWalletConnectData] = useState<{ + walletConnectMethods: string[]; + walletConnectEvents: string[]; + walletConnectChains: string[]; + }>({ + walletConnectMethods: [], + walletConnectEvents: [], + walletConnectChains: [], + }); + + useEffect(() => { + if (!currentProposal) { + return; + } + const { params } = currentProposal; + const { requiredNamespaces } = params; + Object.keys(requiredNamespaces).forEach(key => { + switch (key) { + case 'eip155': + const { + methods: ethMethods, + events: ethEvents, + chains: ethChains, + } = currentProposal?.params?.requiredNamespaces.eip155; + + setWalletConnectData(prevData => { + return { + walletConnectMethods: [ + ...prevData.walletConnectMethods, + ...ethMethods, + ], + walletConnectEvents: [ + ...prevData.walletConnectEvents, + ...ethEvents, + ], + walletConnectChains: ethChains + ? [...prevData.walletConnectChains, ...ethChains] + : [...prevData.walletConnectChains], + }; + }); + break; + case 'cosmos': + const { + methods: cosmosMethods, + events: cosmosEvents, + chains: cosmosChains, + } = currentProposal?.params?.requiredNamespaces.cosmos; + + setWalletConnectData(prevData => { + return { + walletConnectMethods: [ + ...prevData.walletConnectMethods, + ...cosmosMethods, + ], + walletConnectEvents: [ + ...prevData.walletConnectEvents, + ...cosmosEvents, + ], + walletConnectChains: cosmosChains + ? [...prevData.walletConnectChains, ...cosmosChains] + : [...prevData.walletConnectChains], + }; + }); + break; + default: + throw new Error(`${key} not supported`); + } + }); + }, [currentProposal]); const handleAccept = async () => { if (currentProposal) { const { id, params } = currentProposal; const { requiredNamespaces, relays } = params; const namespaces: SessionTypes.Namespaces = {}; + Object.keys(requiredNamespaces).forEach(key => { - const accounts: string[] = []; + let currentAddresses: string[]; + + switch (key) { + case 'eip155': + if (accounts.ethAccounts.length > 0) { + currentAddresses = accounts.ethAccounts.map( + account => account.address, + ); + } + break; + case 'cosmos': + if (accounts.cosmosAccounts.length > 0) { + currentAddresses = accounts.cosmosAccounts.map( + account => account.address, + ); + } + break; + default: + throw new Error(`${key} not supported`); + } + + const namespaceAccounts: string[] = []; requiredNamespaces[key].chains!.map((chain: any) => { - currentEthAddresses.map(acc => accounts.push(`${chain}:${acc}`)); + currentAddresses.map(acc => + namespaceAccounts.push(`${chain}:${acc}`), + ); }); namespaces[key] = { - accounts, + accounts: namespaceAccounts, methods: requiredNamespaces[key].methods, events: requiredNamespaces[key].events, }; @@ -50,6 +143,11 @@ const PairingModal = ({ setModalVisible(false); setToastVisible(true); setCurrentProposal(undefined); + setWalletConnectData({ + walletConnectMethods: [], + walletConnectEvents: [], + walletConnectChains: [], + }); } }; @@ -63,6 +161,11 @@ const PairingModal = ({ setModalVisible(false); setCurrentProposal(undefined); + setWalletConnectData({ + walletConnectMethods: [], + walletConnectEvents: [], + walletConnectChains: [], + }); } }; @@ -80,11 +183,11 @@ const PairingModal = ({ {url} Connect to this site? - Chains: {chains} + Chains: {walletConnectData.walletConnectChains} Methods Requested: - {methods?.map(method => ( + {walletConnectData.walletConnectMethods.map(method => ( {method} @@ -93,7 +196,7 @@ const PairingModal = ({ Events Requested: - {events?.map(event => ( + {walletConnectData.walletConnectEvents.map(event => ( {event} diff --git a/components/SignRequest.tsx b/components/SignRequest.tsx index 92f088a..df13712 100644 --- a/components/SignRequest.tsx +++ b/components/SignRequest.tsx @@ -14,9 +14,9 @@ import styles from '../styles/stylesheet'; import { signMessage } from '../utils/sign-message'; import { retrieveSingleAccount } from '../utils/accounts'; import { - approveEIP155Request, + approveWalletConnectRequest, rejectEIP155Request, -} from '../utils/wallet-connect/EIP155Requests'; +} from '../utils/wallet-connect/WalletConnectRequests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { useRequests } from '../context/RequestContext'; @@ -100,17 +100,20 @@ const SignRequest = ({ route }: SignRequestProps) => { ); }, [route]); - const handleEIP155Request = async () => { + const handleWalletConnectRequest = async () => { const { requestEvent } = route.params || {}; if (!account) { throw new Error('account not found'); } - const response = await approveEIP155Request( + const response = await approveWalletConnectRequest( requestEvent, - account.counterId, + account, + network, + message, ); + const { topic } = requestEvent; await web3wallet.respondSessionRequest({ topic, response }); }; @@ -131,7 +134,7 @@ const SignRequest = ({ route }: SignRequestProps) => { const signMessageHandler = async () => { if (route.params?.requestEvent) { - await handleEIP155Request(); + await handleWalletConnectRequest(); } else { await handleIntent(); } diff --git a/types.ts b/types.ts index 980c889..51418ad 100644 --- a/types.ts +++ b/types.ts @@ -1,3 +1,4 @@ +import { StdSignDoc } from '@cosmjs/amino'; import { SignClientTypes } from '@walletconnect/types'; export type StackParamsList = { @@ -92,7 +93,6 @@ export type PathState = { export interface PairingModalProps { visible: boolean; setModalVisible: (arg1: boolean) => void; - currentEthAddresses: string[]; currentProposal: | SignClientTypes.EventArguments['session_proposal'] | undefined; diff --git a/utils/wallet-connect/EIP155Requests.ts b/utils/wallet-connect/EIP155Requests.ts deleted file mode 100644 index d83b23e..0000000 --- a/utils/wallet-connect/EIP155Requests.ts +++ /dev/null @@ -1,34 +0,0 @@ -// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a - -import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'; -import { SignClientTypes } from '@walletconnect/types'; -import { getSdkError } from '@walletconnect/utils'; - -import { EIP155_SIGNING_METHODS } from './EIP155Lib'; -import { getSignParamsMessage } from './Helpers'; -import { signEthMessage } from '../sign-message'; - -export async function approveEIP155Request( - requestEvent: SignClientTypes.EventArguments['session_request'], - counterId: number, -) { - const { params, id } = requestEvent; - const { request } = params; - switch (request.method) { - case EIP155_SIGNING_METHODS.PERSONAL_SIGN: - const message = getSignParamsMessage(request.params); - const signedMessage = await signEthMessage(message, counterId); - return formatJsonRpcResult(id, signedMessage); - - default: - throw new Error(getSdkError('INVALID_METHOD').message); - } -} - -export function rejectEIP155Request( - request: SignClientTypes.EventArguments['session_request'], -) { - const { id } = request; - - return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message); -} diff --git a/utils/wallet-connect/WalletConnectRequests.ts b/utils/wallet-connect/WalletConnectRequests.ts new file mode 100644 index 0000000..3efbb69 --- /dev/null +++ b/utils/wallet-connect/WalletConnectRequests.ts @@ -0,0 +1,65 @@ +// Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a + +import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'; +import { SignClientTypes } from '@walletconnect/types'; +import { getSdkError } from '@walletconnect/utils'; + +import { EIP155_SIGNING_METHODS } from './EIP155Lib'; +import { signEthMessage } from '../sign-message'; +import { Account } from '../../types'; +import { getCosmosAccounts, getMnemonic, getPathKey } from '../utils'; + +export async function approveWalletConnectRequest( + requestEvent: SignClientTypes.EventArguments['session_request'], + account: Account, + network: string, + message: string, +) { + const { params, id } = requestEvent; + const { request } = params; + + const path = (await getPathKey(network, account.counterId)).path; + const mnemonic = await getMnemonic(); + const cosmosAccount = await getCosmosAccounts(mnemonic, path); + const address = cosmosAccount.data.address; + + switch (request.method) { + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: + const signedEthMessage = await signEthMessage(message, account.counterId); + return formatJsonRpcResult(id, signedEthMessage); + // TODO: Debug signDirect + case 'cosmos_signDirect': + const signedCosmosMessage = await cosmosAccount.cosmosWallet.signAmino( + address, + request.params.signDoc, + ); + + return formatJsonRpcResult(id, { + signature: signedCosmosMessage.signature.signature, + }); + + case 'cosmos_signAmino': + const signedAminoMessage = await cosmosAccount.cosmosWallet.signAmino( + address, + request.params.signDoc, + ); + + if (!signedAminoMessage) { + throw new Error('Error signing message'); + } + + return formatJsonRpcResult(id, { + signature: signedAminoMessage.signature.signature, + }); + default: + throw new Error(getSdkError('INVALID_METHOD').message); + } +} + +export function rejectEIP155Request( + request: SignClientTypes.EventArguments['session_request'], +) { + const { id } = request; + + return formatJsonRpcError(id, getSdkError('USER_REJECTED_METHODS').message); +} -- 2.45.2 From 72191621858e7eb9554fc03ffd7429bed4480d37 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Tue, 12 Mar 2024 16:47:05 +0530 Subject: [PATCH 15/64] Sign message using signDirect method with cosmos accounts (#51) * Sign message using signDirect method with cosmos accounts * Add explaination for signDirect method * Use existing utility function to convert hex string to uint8array * Handle review changes --- App.tsx | 1 - package.json | 6 +- utils/sign-message.ts | 36 ++++++++-- utils/utils.ts | 14 ++++ utils/wallet-connect/WalletConnectRequests.ts | 36 +++++++--- yarn.lock | 71 ++++++++++++------- 6 files changed, 119 insertions(+), 45 deletions(-) diff --git a/App.tsx b/App.tsx index 5697b09..ac2e27e 100644 --- a/App.tsx +++ b/App.tsx @@ -69,7 +69,6 @@ const App = (): React.JSX.Element => { }); break; - // TODO: Debug signDirect case 'cosmos_signDirect': navigation.navigate('SignRequest', { network: 'cosmos', diff --git a/package.json b/package.json index 05620d5..09bc1f6 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,9 @@ "postinstall": "patch-package" }, "dependencies": { - "@cosmjs/amino": "^0.32.2", - "@cosmjs/crypto": "^0.32.2", + "@cosmjs/amino": "^0.32.3", + "@cosmjs/crypto": "^0.32.3", + "@cosmjs/proto-signing": "^0.32.3", "@ethersproject/shims": "^5.7.0", "@json-rpc-tools/utils": "^1.7.6", "@react-native-async-storage/async-storage": "^1.22.3", @@ -21,6 +22,7 @@ "@react-navigation/native-stack": "^6.9.18", "@walletconnect/react-native-compat": "^2.11.2", "@walletconnect/web3wallet": "^1.10.2", + "cosmjs-types": "^0.9.0", "ethers": "5.7.2", "fast-text-encoding": "^1.0.6", "metro-react-native-babel-preset": "^0.77.0", diff --git a/utils/sign-message.ts b/utils/sign-message.ts index 4d07e6b..4256d8a 100644 --- a/utils/sign-message.ts +++ b/utils/sign-message.ts @@ -5,9 +5,15 @@ import 'react-native-get-random-values'; import '@ethersproject/shims'; import { Wallet } from 'ethers'; +import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; import { SignMessageParams } from '../types'; -import { getCosmosAccounts, getMnemonic, getPathKey } from './utils'; +import { + getCosmosAccounts, + getDirectWallet, + getMnemonic, + getPathKey, +} from './utils'; const signMessage = async ({ message, @@ -38,7 +44,7 @@ const signEthMessage = async ( return signature; } catch (error) { console.error('Error signing Ethereum message:', error); - return undefined; + throw error; } }; @@ -76,8 +82,30 @@ const signCosmosMessage = async ( return cosmosSignature.signature.signature; } catch (error) { console.error('Error signing Cosmos message:', error); - return undefined; + throw error; } }; -export { signMessage, signEthMessage, signCosmosMessage }; +const signDirectMessage = async ( + network: string, + accountId: number, + signDoc: SignDoc, +): Promise => { + try { + const path = (await getPathKey(network, accountId)).path; + const mnemonic = await getMnemonic(); + const { directWallet, data } = await getDirectWallet(mnemonic, path); + + const directSignature = await directWallet.signDirect( + data.address, + signDoc, + ); + + return directSignature.signature.signature; + } catch (error) { + console.error('Error signing Cosmos message:', error); + throw error; + } +}; + +export { signMessage, signEthMessage, signCosmosMessage, signDirectMessage }; diff --git a/utils/utils.ts b/utils/utils.ts index ceec650..b878eaa 100644 --- a/utils/utils.ts +++ b/utils/utils.ts @@ -12,6 +12,7 @@ import { } from 'react-native-keychain'; import { AccountData, Secp256k1HdWallet } from '@cosmjs/amino'; +import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; import { stringToPath } from '@cosmjs/crypto'; const getMnemonic = async (): Promise => { @@ -58,6 +59,19 @@ const getCosmosAccounts = async ( return { cosmosWallet, data }; }; +export const getDirectWallet = async ( + mnemonic: string, + path: string, +): Promise<{ directWallet: DirectSecp256k1HdWallet; data: AccountData }> => { + const directWallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { + hdPaths: [stringToPath(`m/44'/118'/${path}`)], + }); + const accountsData = await directWallet.getAccounts(); + const data = accountsData[0]; + + return { directWallet, data }; +}; + const accountInfoFromHDPath = async ( hdPath: string, ): Promise< diff --git a/utils/wallet-connect/WalletConnectRequests.ts b/utils/wallet-connect/WalletConnectRequests.ts index 3efbb69..57fe268 100644 --- a/utils/wallet-connect/WalletConnectRequests.ts +++ b/utils/wallet-connect/WalletConnectRequests.ts @@ -5,7 +5,7 @@ import { SignClientTypes } from '@walletconnect/types'; import { getSdkError } from '@walletconnect/utils'; import { EIP155_SIGNING_METHODS } from './EIP155Lib'; -import { signEthMessage } from '../sign-message'; +import { signDirectMessage, signEthMessage } from '../sign-message'; import { Account } from '../../types'; import { getCosmosAccounts, getMnemonic, getPathKey } from '../utils'; @@ -25,31 +25,45 @@ export async function approveWalletConnectRequest( switch (request.method) { case EIP155_SIGNING_METHODS.PERSONAL_SIGN: - const signedEthMessage = await signEthMessage(message, account.counterId); - return formatJsonRpcResult(id, signedEthMessage); - // TODO: Debug signDirect + const ethSignature = await signEthMessage(message, account.counterId); + return formatJsonRpcResult(id, ethSignature); + case 'cosmos_signDirect': - const signedCosmosMessage = await cosmosAccount.cosmosWallet.signAmino( - address, - request.params.signDoc, + // Reference: https://github.com/confio/cosmjs-types/blob/66e52711914fccd2a9d1a03e392d3628fdf499e2/src/cosmos/tx/v1beta1/tx.ts#L51 + // According above doc, in the signDoc interface 'bodyBytes' and 'authInfoBytes' have Uint8Array type + const bodyBytesArray = Uint8Array.from( + Buffer.from(request.params.signDoc.bodyBytes, 'hex'), + ); + const authInfoBytesArray = Uint8Array.from( + Buffer.from(request.params.signDoc.authInfoBytes, 'hex'), + ); + + const cosmosDirectSignature = await signDirectMessage( + network, + account.counterId, + { + ...request.params.signDoc, + bodyBytes: bodyBytesArray, + authInfoBytes: authInfoBytesArray, + }, ); return formatJsonRpcResult(id, { - signature: signedCosmosMessage.signature.signature, + signature: cosmosDirectSignature, }); case 'cosmos_signAmino': - const signedAminoMessage = await cosmosAccount.cosmosWallet.signAmino( + const cosmosAminoSignature = await cosmosAccount.cosmosWallet.signAmino( address, request.params.signDoc, ); - if (!signedAminoMessage) { + if (!cosmosAminoSignature) { throw new Error('Error signing message'); } return formatJsonRpcResult(id, { - signature: signedAminoMessage.signature.signature, + signature: cosmosAminoSignature.signature.signature, }); default: throw new Error(getSdkError('INVALID_METHOD').message); diff --git a/yarn.lock b/yarn.lock index e0a57c5..8cc90cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1167,49 +1167,61 @@ deepmerge "^3.2.0" hoist-non-react-statics "^3.3.0" -"@cosmjs/amino@^0.32.2": - version "0.32.2" - resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.32.2.tgz#ba3cf255e4e6b1ba67461f1ef7b0b8ad3f895da7" - integrity sha512-lcK5RCVm4OfdAooxKcF2+NwaDVVpghOq6o/A40c2mHXDUzUoRZ33VAHjVJ9Me6vOFxshrw/XEFn1f4KObntjYA== +"@cosmjs/amino@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.32.3.tgz#b81d4a2b8d61568431a1afcd871e1344a19d97ff" + integrity sha512-G4zXl+dJbqrz1sSJ56H/25l5NJEk/pAPIr8piAHgbXYw88OdAOlpA26PQvk2IbSN/rRgVbvlLTNgX2tzz1dyUA== dependencies: - "@cosmjs/crypto" "^0.32.2" - "@cosmjs/encoding" "^0.32.2" - "@cosmjs/math" "^0.32.2" - "@cosmjs/utils" "^0.32.2" + "@cosmjs/crypto" "^0.32.3" + "@cosmjs/encoding" "^0.32.3" + "@cosmjs/math" "^0.32.3" + "@cosmjs/utils" "^0.32.3" -"@cosmjs/crypto@^0.32.2": - version "0.32.2" - resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.32.2.tgz#8ed255d3d1c1c4d916a1586f8cbc33eaab82f511" - integrity sha512-RuxrYKzhrPF9g6NmU7VEq++Hn1vZJjqqJpZ9Tmw9lOYOV8BUsv+j/0BE86kmWi7xVJ7EwxiuxYsKuM8IR18CIA== +"@cosmjs/crypto@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/crypto/-/crypto-0.32.3.tgz#787f8e659709678722068ee1ddf379f65051a25e" + integrity sha512-niQOWJHUtlJm2GG4F00yGT7sGPKxfUwz+2qQ30uO/E3p58gOusTcH2qjiJNVxb8vScYJhFYFqpm/OA/mVqoUGQ== dependencies: - "@cosmjs/encoding" "^0.32.2" - "@cosmjs/math" "^0.32.2" - "@cosmjs/utils" "^0.32.2" + "@cosmjs/encoding" "^0.32.3" + "@cosmjs/math" "^0.32.3" + "@cosmjs/utils" "^0.32.3" "@noble/hashes" "^1" bn.js "^5.2.0" elliptic "^6.5.4" libsodium-wrappers-sumo "^0.7.11" -"@cosmjs/encoding@^0.32.2": - version "0.32.2" - resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.32.2.tgz#8c5c64481a85cd570740c34dccce69d024a29805" - integrity sha512-WX7m1wLpA9V/zH0zRcz4EmgZdAv1F44g4dbXOgNj1eXZw1PIGR12p58OEkLN51Ha3S4DKRtCv5CkhK1KHEvQtg== +"@cosmjs/encoding@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/encoding/-/encoding-0.32.3.tgz#e245ff511fe4a0df7ba427b5187aab69e3468e5b" + integrity sha512-p4KF7hhv8jBQX3MkB3Defuhz/W0l3PwWVYU2vkVuBJ13bJcXyhU9nJjiMkaIv+XP+W2QgRceqNNgFUC5chNR7w== dependencies: base64-js "^1.3.0" bech32 "^1.1.4" readonly-date "^1.0.0" -"@cosmjs/math@^0.32.2": - version "0.32.2" - resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.32.2.tgz#4522312769197e132679e4960862bcec0eed4cb8" - integrity sha512-b8+ruAAY8aKtVKWSft2IvtCVCUH1LigIlf9ALIiY8n9jtM4kMASiaRbQ/27etnSAInV88IaezKK9rQZrtxTjcw== +"@cosmjs/math@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.32.3.tgz#16e4256f4da507b9352327da12ae64056a2ba6c9" + integrity sha512-amumUtZs8hCCnV+lSBaJIiZkGabQm22QGg/IotYrhcmoOEOjt82n7hMNlNXRs7V6WLMidGrGYcswB5zcmp0Meg== dependencies: bn.js "^5.2.0" -"@cosmjs/utils@^0.32.2": - version "0.32.2" - resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.32.2.tgz#324304aa85bfa6f10561cc17781d824d02130897" - integrity sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q== +"@cosmjs/proto-signing@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/proto-signing/-/proto-signing-0.32.3.tgz#91ae149b747d18666a6ccc924165b306431f7c0d" + integrity sha512-kSZ0ZUY0DwcRT0NcIn2HkadH4NKlwjfZgbLj1ABwh/4l0RgeT84QCscZCu63tJYq3K6auwqTiZSZERwlO4/nbg== + dependencies: + "@cosmjs/amino" "^0.32.3" + "@cosmjs/crypto" "^0.32.3" + "@cosmjs/encoding" "^0.32.3" + "@cosmjs/math" "^0.32.3" + "@cosmjs/utils" "^0.32.3" + cosmjs-types "^0.9.0" + +"@cosmjs/utils@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.32.3.tgz#5dcaee6dd7cc846cdc073e9a7a7f63242f5f7e31" + integrity sha512-WCZK4yksj2hBDz4w7xFZQTRZQ/RJhBX26uFHmmQFIcNUUVAihrLO+RerqJgk0dZqC42wstM9pEUQGtPmLcIYvg== "@craftzdog/react-native-buffer@^6.0.5": version "6.0.5" @@ -3944,6 +3956,11 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" +cosmjs-types@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/cosmjs-types/-/cosmjs-types-0.9.0.tgz#c3bc482d28c7dfa25d1445093fdb2d9da1f6cfcc" + integrity sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ== + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" -- 2.45.2 From cf197f386f90b05b85cbe3df27c35910fb90d7de Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:23:16 +0530 Subject: [PATCH 16/64] Display active sessions on walletconnect page (#50) * Replace QR icon with WC logo * Change screen title * Display active sessions * Change title * Display sessions on WalletConnect page * Display session topic in list item * Fix types * Add line * Change message * Move useEffect to WalletConnectContext * Disconnect sessions on resetting wallet * Review changes --------- Co-authored-by: Adw8 --- App.tsx | 35 +++++++++------- components/AddSession.tsx | 13 +----- components/HomeScreen.tsx | 28 ++++++++----- components/PairingModal.tsx | 5 +++ components/SignMessage.tsx | 2 +- components/SignRequest.tsx | 4 +- components/WalletConnect.tsx | 71 ++++++++++++++++++++++++++++++-- context/RequestContext.tsx | 31 -------------- context/WalletConnectContext.tsx | 48 +++++++++++++++++++++ index.js | 6 +-- styles/stylesheet.js | 4 +- types.ts | 12 +++++- 12 files changed, 179 insertions(+), 80 deletions(-) delete mode 100644 context/RequestContext.tsx create mode 100644 context/WalletConnectContext.tsx diff --git a/App.tsx b/App.tsx index ac2e27e..b36da6c 100644 --- a/App.tsx +++ b/App.tsx @@ -1,8 +1,9 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { Button, Snackbar } from 'react-native-paper'; +import { Button, Snackbar, Text } from 'react-native-paper'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { SignClientTypes } from '@walletconnect/types'; +import { getSdkError } from '@walletconnect/utils'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp, @@ -17,22 +18,19 @@ import PairingModal from './components/PairingModal'; import AddSession from './components/AddSession'; import WalletConnect from './components/WalletConnect'; import { StackParamsList } from './types'; -import useInitialization, { - web3wallet, -} from './utils/wallet-connect/WalletConnectUtils'; +import { web3wallet } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; -import { useRequests } from './context/RequestContext'; +import { useWalletConnect } from './context/WalletConnectContext'; const Stack = createNativeStackNavigator(); const App = (): React.JSX.Element => { - useInitialization(); - const navigation = useNavigation>(); - const { requestSession, setRequestSession } = useRequests(); + const { requestSession, setRequestSession, setActiveSessions } = + useWalletConnect(); const [modalVisible, setModalVisible] = useState(false); const [toastVisible, setToastVisible] = useState(false); @@ -95,13 +93,20 @@ const App = (): React.JSX.Element => { }, [requestSession, setRequestSession, navigation], ); + + const onSessionDelete = useCallback(() => { + let sessions = web3wallet?.getActiveSessions(); + setActiveSessions(sessions); + }, []); + useEffect(() => { web3wallet?.on('session_proposal', onSessionProposal); web3wallet?.on('session_request', onSessionRequest); - + web3wallet?.on('session_delete', onSessionDelete); return () => { web3wallet?.off('session_proposal', onSessionProposal); web3wallet?.off('session_request', onSessionRequest); + web3wallet?.off('session_delete', onSessionDelete); }; //TODO: Investigate dependencies }); @@ -113,7 +118,7 @@ const App = (): React.JSX.Element => { name="Laconic" component={HomeScreen} options={{ - title: 'Laconic Wallet', + headerTitle: () => Laconic Wallet, headerBackVisible: false, }} /> @@ -121,7 +126,7 @@ const App = (): React.JSX.Element => { name="SignMessage" component={SignMessage} options={{ - title: 'Sign Message', + headerTitle: () => Sign Message, }} initialParams={{ selectedNetwork: 'Ethereum' }} /> @@ -129,14 +134,16 @@ const App = (): React.JSX.Element => { name="SignRequest" component={SignRequest} options={{ - title: 'Sign this message?', + headerTitle: () => ( + Sign this message? + ), }} /> Bad Request, headerBackVisible: false, }} /> @@ -144,7 +151,7 @@ const App = (): React.JSX.Element => { name="WalletConnect" component={WalletConnect} options={{ - title: 'WalletConnect Sessions', + headerTitle: () => WalletConnect, headerRight: () => ( ), }); } else { @@ -86,6 +84,16 @@ const HomeScreen = () => { cosmosAccounts: [], }); setCurrentIndex(0); + const sessions = await web3wallet.getActiveSessions(); + + Object.keys(sessions).forEach(async sessionId => { + await web3wallet.disconnectSession({ + topic: sessionId, + reason: getSdkError('USER_DISCONNECTED'), + }); + }); + setActiveSessions({}); + hideResetDialog(); setNetwork('eth'); }; diff --git a/components/PairingModal.tsx b/components/PairingModal.tsx index 3b34841..b44959f 100644 --- a/components/PairingModal.tsx +++ b/components/PairingModal.tsx @@ -9,6 +9,7 @@ import { PairingModalProps } from '../types'; import styles from '../styles/stylesheet'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { useAccounts } from '../context/AccountsContext'; +import { useWalletConnect } from '../context/WalletConnectContext'; const PairingModal = ({ visible, @@ -92,6 +93,8 @@ const PairingModal = ({ }); }, [currentProposal]); + const { setActiveSessions } = useWalletConnect(); + const handleAccept = async () => { if (currentProposal) { const { id, params } = currentProposal; @@ -140,6 +143,8 @@ const PairingModal = ({ namespaces, }); + const sessions = web3wallet.getActiveSessions(); + setActiveSessions(sessions); setModalVisible(false); setToastVisible(true); setCurrentProposal(undefined); diff --git a/components/SignMessage.tsx b/components/SignMessage.tsx index 0f90654..a193741 100644 --- a/components/SignMessage.tsx +++ b/components/SignMessage.tsx @@ -35,7 +35,7 @@ const SignMessage = ({ route }: SignProps) => { - + {account && `Account ${account.counterId + 1}`} diff --git a/components/SignRequest.tsx b/components/SignRequest.tsx index df13712..fa5ebeb 100644 --- a/components/SignRequest.tsx +++ b/components/SignRequest.tsx @@ -18,12 +18,12 @@ import { rejectEIP155Request, } from '../utils/wallet-connect/WalletConnectRequests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; -import { useRequests } from '../context/RequestContext'; +import { useWalletConnect } from '../context/WalletConnectContext'; type SignRequestProps = NativeStackScreenProps; const SignRequest = ({ route }: SignRequestProps) => { - const { requestSession } = useRequests(); + const { requestSession } = useWalletConnect(); const requestName = requestSession?.peer?.metadata?.name; const requestIcon = requestSession?.peer?.metadata?.icons[0]; diff --git a/components/WalletConnect.tsx b/components/WalletConnect.tsx index 1461f6d..fed7fed 100644 --- a/components/WalletConnect.tsx +++ b/components/WalletConnect.tsx @@ -1,6 +1,71 @@ -import React from 'react'; -import { View, Text } from 'react-native'; +import React, { useEffect } from 'react'; +import { Image, TouchableOpacity, View } from 'react-native'; +import { List, Text } from 'react-native-paper'; + +import { getSdkError } from '@walletconnect/utils'; + +import { useWalletConnect } from '../context/WalletConnectContext'; +import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; +import styles from '../styles/stylesheet'; export default function WalletConnect() { - return ; + const { activeSessions, setActiveSessions } = useWalletConnect(); + + const disconnect = async (sessionId: string) => { + await web3wallet.disconnectSession({ + topic: sessionId, + reason: getSdkError('USER_DISCONNECTED'), + }); + const sessions = await web3wallet?.getActiveSessions(); + setActiveSessions(sessions); + return; + }; + + useEffect(() => { + const sessions = web3wallet.getActiveSessions(); + setActiveSessions(sessions); + }, []); + + return ( + + {Object.keys(activeSessions).length > 0 ? ( + <> + + Active Sessions + + + {Object.entries(activeSessions).map( + ([sessionId, session], index) => ( + ( + + )} + right={() => ( + disconnect(sessionId)} + style={{ display: 'flex', justifyContent: 'center' }}> + + + )} + /> + ), + )} + + + ) : ( + + You have no active sessions + + )} + + ); } diff --git a/context/RequestContext.tsx b/context/RequestContext.tsx deleted file mode 100644 index c78233d..0000000 --- a/context/RequestContext.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import React, { createContext, useContext, useState } from 'react'; - -interface RequestContextProps { - requestSession: any; - setRequestSession: (requestSession: any) => void; -} - -const RequestContext = createContext({ - requestSession: {}, - setRequestSession: () => {}, -}); - -const useRequests = () => { - const requestContext = useContext(RequestContext); - return requestContext; -}; - -const RequestProvider = ({ children }: { children: React.ReactNode }) => { - const [requestSession, setRequestSession] = useState({}); - return ( - - {children} - - ); -}; - -export { useRequests, RequestProvider }; diff --git a/context/WalletConnectContext.tsx b/context/WalletConnectContext.tsx new file mode 100644 index 0000000..8161d69 --- /dev/null +++ b/context/WalletConnectContext.tsx @@ -0,0 +1,48 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +import { SessionTypes } from '@walletconnect/types'; + +import { WalletConnectContextProps } from '../types'; +import useInitialization, { + web3wallet, +} from '../utils/wallet-connect/WalletConnectUtils'; + +const WalletConnectContext = createContext({ + requestSession: {}, + setRequestSession: () => {}, + activeSessions: {}, + setActiveSessions: () => {}, +}); + +const useWalletConnect = () => { + const walletConnectContext = useContext(WalletConnectContext); + return walletConnectContext; +}; + +const WalletConnectProvider = ({ children }: { children: React.ReactNode }) => { + useInitialization(); + + useEffect(() => { + const sessions = web3wallet?.getActiveSessions() ?? {}; + setActiveSessions(sessions); + }, []); + + const [requestSession, setRequestSession] = useState({}); + const [activeSessions, setActiveSessions] = useState< + Record + >({}); + + return ( + + {children} + + ); +}; + +export { useWalletConnect, WalletConnectProvider }; diff --git a/index.js b/index.js index 95dc0f7..37d8a67 100644 --- a/index.js +++ b/index.js @@ -7,7 +7,7 @@ import { NavigationContainer } from '@react-navigation/native'; import App from './App'; import { AccountsProvider } from './context/AccountsContext'; -import { RequestProvider } from './context/RequestContext'; +import { WalletConnectProvider } from './context/WalletConnectContext'; import { name as appName } from './app.json'; export default function Main() { @@ -24,11 +24,11 @@ export default function Main() { return ( - + - + ); diff --git a/styles/stylesheet.js b/styles/stylesheet.js index e3de033..0690a8c 100644 --- a/styles/stylesheet.js +++ b/styles/stylesheet.js @@ -18,7 +18,7 @@ const styles = StyleSheet.create({ fontWeight: '700', }, accountContainer: { - marginTop: 24, + marginTop: 12, }, addAccountButton: { marginTop: 24, @@ -48,7 +48,7 @@ const styles = StyleSheet.create({ paddingHorizontal: 24, }, accountInfo: { - marginTop: 24, + marginTop: 12, marginBottom: 30, }, networkDropdown: { diff --git a/types.ts b/types.ts index 51418ad..edbc1b4 100644 --- a/types.ts +++ b/types.ts @@ -1,5 +1,4 @@ -import { StdSignDoc } from '@cosmjs/amino'; -import { SignClientTypes } from '@walletconnect/types'; +import { SignClientTypes, SessionTypes } from '@walletconnect/types'; export type StackParamsList = { Laconic: undefined; @@ -109,3 +108,12 @@ export interface SignModalProps { requestEvent: SignClientTypes.EventArguments['session_request'] | undefined; currentEthAddresses: string[]; } + +export interface WalletConnectContextProps { + requestSession: any; + setRequestSession: (requestSession: any) => void; + activeSessions: Record; + setActiveSessions: ( + activeSessions: Record, + ) => void; +} -- 2.45.2 From 2a92e71594b7b29c04a8432adb8e0ff70032a1a8 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Wed, 13 Mar 2024 12:02:08 +0530 Subject: [PATCH 17/64] Display decoded message received while using signDirect method (#52) * Display decoded message received while using signDirect method * Fix permission dialog not exiting after clicking outside * Handle review changes * Remove optional chaining in while checking for signDirect method --- App.tsx | 20 ++++++++++++++++++-- components/AddSession.tsx | 26 +++++++++++++++++++------- components/SignRequest.tsx | 26 +++++++++++++++++++++----- styles/stylesheet.js | 9 +++++++++ 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/App.tsx b/App.tsx index b36da6c..4ab5be3 100644 --- a/App.tsx +++ b/App.tsx @@ -1,9 +1,9 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Button, Snackbar, Text } from 'react-native-paper'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; +import { TxBody, AuthInfo } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; import { SignClientTypes } from '@walletconnect/types'; -import { getSdkError } from '@walletconnect/utils'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp, @@ -68,10 +68,26 @@ const App = (): React.JSX.Element => { break; case 'cosmos_signDirect': + const message = { + txbody: TxBody.toJSON( + TxBody.decode( + Uint8Array.from( + Buffer.from(request.params.signDoc.bodyBytes, 'hex'), + ), + ), + ), + authInfo: AuthInfo.toJSON( + AuthInfo.decode( + Uint8Array.from( + Buffer.from(request.params.signDoc.authInfoBytes, 'hex'), + ), + ), + ), + }; navigation.navigate('SignRequest', { network: 'cosmos', address: request.params.signerAddress, - message: request.params.signDoc.bodyBytes, + message: JSON.stringify(message, undefined, 2), requestEvent, requestSession, }); diff --git a/components/AddSession.tsx b/components/AddSession.tsx index 9efabb5..723298e 100644 --- a/components/AddSession.tsx +++ b/components/AddSession.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { AppState, View } from 'react-native'; +import { AppState, TouchableOpacity, View } from 'react-native'; import { Button, Text, TextInput } from 'react-native-paper'; import { Camera, @@ -7,6 +7,7 @@ import { useCameraPermission, useCodeScanner, } from 'react-native-vision-camera'; +import { Linking } from 'react-native'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -40,6 +41,10 @@ const AddSession = () => { }, }); + const linkToSettings = async () => { + await Linking.openSettings(); + }; + const pair = async () => { const pairing = await web3WalletPair({ uri: currentWCURI }); navigation.navigate('WalletConnect'); @@ -56,16 +61,23 @@ const AddSession = () => { if (!hasPermission) { requestPermission(); } - }, [hasPermission, isActive, requestPermission]); + }, [hasPermission, requestPermission]); return ( {!hasPermission || !device ? ( - - {!hasPermission - ? 'No Camera Permission granted' - : 'No Camera Selected'} - + <> + + {!hasPermission + ? 'No Camera Permission granted' + : 'No Camera Selected'} + + + + Go to settings + + + ) : ( <> diff --git a/components/SignRequest.tsx b/components/SignRequest.tsx index fa5ebeb..6c0bcf9 100644 --- a/components/SignRequest.tsx +++ b/components/SignRequest.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useState } from 'react'; -import { Alert, Image, View } from 'react-native'; +import React, { useEffect, useMemo, useState } from 'react'; +import { Alert, Image, ScrollView, View } from 'react-native'; import { ActivityIndicator, Button, Text } from 'react-native-paper'; import { useNavigation } from '@react-navigation/native'; @@ -37,6 +37,12 @@ const SignRequest = ({ route }: SignRequestProps) => { const navigation = useNavigation>(); + const isCosmosSignDirect = useMemo(() => { + const requestParams = route.params!.requestEvent.params.request; + + return requestParams.method === 'cosmos_signDirect'; + }, [route.params]); + const retrieveData = async ( requestNetwork: string, requestAddress: string, @@ -173,9 +179,19 @@ const SignRequest = ({ route }: SignRequestProps) => { {requestURL} - - {message} - + + {isCosmosSignDirect ? ( + + + {message} + + + ) : ( + + {message} + + )} + - - - + + + + + + + diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index 7bc6282..22f96a1 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -150,6 +150,7 @@ const styles = StyleSheet.create({ flex: 1, alignItems: 'center', justifyContent: 'center', + marginBottom: 10, }, modalContentContainer: { display: 'flex', -- 2.45.2 From 3cd4c5151572b409af2da25285cecd5c556575be Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:33:29 +0530 Subject: [PATCH 24/64] Add support for eth send transaction method (#62) * Add support for eth send transaction method * Add reference to eip155 data file --- src/App.tsx | 10 ++ src/screens/HomeScreen.tsx | 4 +- src/screens/SignRequest.tsx | 20 ++- src/utils/wallet-connect/EIP155Data.ts | 140 ++++++++++++++++++ .../wallet-connect/WalletConnectRequests.ts | 17 ++- 5 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 src/utils/wallet-connect/EIP155Data.ts diff --git a/src/App.tsx b/src/App.tsx index 92b34b2..0a94d35 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -64,6 +64,16 @@ const App = (): React.JSX.Element => { const requestSessionData = web3wallet!.engine.signClient.session.get(topic); switch (request.method) { + case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: + navigation.navigate('SignRequest', { + network: 'eth', + address: request.params[0].from, + message: JSON.stringify(request.params[0], undefined, 2), + requestEvent, + requestSessionData, + }); + break; + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: navigation.navigate('SignRequest', { network: 'eth', diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 89d1ace..ac7a234 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -84,10 +84,10 @@ const HomeScreen = () => { cosmosAccounts: [], }); setCurrentIndex(0); - const sessions = await web3wallet.getActiveSessions(); + const sessions = web3wallet!.getActiveSessions(); Object.keys(sessions).forEach(async sessionId => { - await web3wallet.disconnectSession({ + await web3wallet!.disconnectSession({ topic: sessionId, reason: getSdkError('USER_DISCONNECTED'), }); diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 8248d2c..d22c932 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -19,6 +19,7 @@ import { rejectWalletConnectRequest, } from '../utils/wallet-connect/WalletConnectRequests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; +import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; type SignRequestProps = NativeStackScreenProps; @@ -46,6 +47,19 @@ const SignRequest = ({ route }: SignRequestProps) => { return requestParams.params.request.method === 'cosmos_signDirect'; }, [route.params]); + const isEthSendTransaction = useMemo(() => { + const requestParams = route.params!.requestEvent; + + if (!requestParams) { + return false; + } + + return ( + requestParams.params.request.method === + EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION + ); + }, [route.params]); + const retrieveData = async ( requestNetwork: string, requestAddress: string, @@ -128,7 +142,7 @@ const SignRequest = ({ route }: SignRequestProps) => { ); const { topic } = requestEvent; - await web3wallet.respondSessionRequest({ topic, response }); + await web3wallet!.respondSessionRequest({ topic, response }); }; const handleIntent = async () => { @@ -159,7 +173,7 @@ const SignRequest = ({ route }: SignRequestProps) => { if (route.params?.requestEvent) { const response = rejectWalletConnectRequest(route.params?.requestEvent); const { topic } = route.params?.requestEvent; - await web3wallet.respondSessionRequest({ + await web3wallet!.respondSessionRequest({ topic, response, }); @@ -211,7 +225,7 @@ const SignRequest = ({ route }: SignRequestProps) => { - {isCosmosSignDirect ? ( + {isCosmosSignDirect || isEthSendTransaction ? ( {message} diff --git a/src/utils/wallet-connect/EIP155Data.ts b/src/utils/wallet-connect/EIP155Data.ts new file mode 100644 index 0000000..b47948a --- /dev/null +++ b/src/utils/wallet-connect/EIP155Data.ts @@ -0,0 +1,140 @@ +/** + * @desc Refference list of eip155 chains + * @url https://chainlist.org + */ + +// Taken from https://github.com/WalletConnect/web-examples/blob/main/advanced/wallets/react-wallet-v2/src/data/EIP155Data.ts + +/** + * Types + */ +export type TEIP155Chain = keyof typeof EIP155_CHAINS; + +export type EIP155Chain = { + chainId: number; + name: string; + logo: string; + rgb: string; + rpc: string; + namespace: string; + smartAccountEnabled?: boolean; +}; + +/** + * Chains + */ +export const EIP155_MAINNET_CHAINS: Record = { + 'eip155:1': { + chainId: 1, + name: 'Ethereum', + logo: '/chain-logos/eip155-1.png', + rgb: '99, 125, 234', + rpc: 'https://cloudflare-eth.com/', + namespace: 'eip155', + }, + 'eip155:43114': { + chainId: 43114, + name: 'Avalanche C-Chain', + logo: '/chain-logos/eip155-43113.png', + rgb: '232, 65, 66', + rpc: 'https://api.avax.network/ext/bc/C/rpc', + namespace: 'eip155', + }, + 'eip155:137': { + chainId: 137, + name: 'Polygon', + logo: '/chain-logos/eip155-137.png', + rgb: '130, 71, 229', + rpc: 'https://polygon-rpc.com/', + namespace: 'eip155', + }, + 'eip155:10': { + chainId: 10, + name: 'Optimism', + logo: '/chain-logos/eip155-10.png', + rgb: '235, 0, 25', + rpc: 'https://mainnet.optimism.io', + namespace: 'eip155', + }, + 'eip155:324': { + chainId: 324, + name: 'zkSync Era', + logo: '/chain-logos/eip155-324.svg', + rgb: '242, 242, 242', + rpc: 'https://mainnet.era.zksync.io/', + namespace: 'eip155', + }, +}; + +export const EIP155_TEST_CHAINS: Record = { + 'eip155:5': { + chainId: 5, + name: 'Ethereum Goerli', + logo: '/chain-logos/eip155-1.png', + rgb: '99, 125, 234', + rpc: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', + namespace: 'eip155', + smartAccountEnabled: true, + }, + 'eip155:11155111': { + chainId: 11155111, + name: 'Ethereum Sepolia', + logo: '/chain-logos/eip155-1.png', + rgb: '99, 125, 234', + rpc: 'https://gateway.tenderly.co/public/sepolia', + namespace: 'eip155', + smartAccountEnabled: true, + }, + 'eip155:43113': { + chainId: 43113, + name: 'Avalanche Fuji', + logo: '/chain-logos/eip155-43113.png', + rgb: '232, 65, 66', + rpc: 'https://api.avax-test.network/ext/bc/C/rpc', + namespace: 'eip155', + }, + 'eip155:80001': { + chainId: 80001, + name: 'Polygon Mumbai', + logo: '/chain-logos/eip155-137.png', + rgb: '130, 71, 229', + rpc: 'https://matic-mumbai.chainstacklabs.com', + namespace: 'eip155', + smartAccountEnabled: true, + }, + 'eip155:420': { + chainId: 420, + name: 'Optimism Goerli', + logo: '/chain-logos/eip155-10.png', + rgb: '235, 0, 25', + rpc: 'https://goerli.optimism.io', + namespace: 'eip155', + }, + 'eip155:280': { + chainId: 280, + name: 'zkSync Era Testnet', + logo: '/chain-logos/eip155-324.svg', + rgb: '242, 242, 242', + rpc: 'https://testnet.era.zksync.dev/', + namespace: 'eip155', + }, +}; + +export const EIP155_CHAINS = { + ...EIP155_MAINNET_CHAINS, + ...EIP155_TEST_CHAINS, +}; + +/** + * Methods + */ +export const EIP155_SIGNING_METHODS = { + PERSONAL_SIGN: 'personal_sign', + ETH_SIGN: 'eth_sign', + ETH_SIGN_TRANSACTION: 'eth_signTransaction', + ETH_SIGN_TYPED_DATA: 'eth_signTypedData', + ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3', + ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4', + ETH_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction', + ETH_SEND_TRANSACTION: 'eth_sendTransaction', +}; diff --git a/src/utils/wallet-connect/WalletConnectRequests.ts b/src/utils/wallet-connect/WalletConnectRequests.ts index a4d42ff..8f023c4 100644 --- a/src/utils/wallet-connect/WalletConnectRequests.ts +++ b/src/utils/wallet-connect/WalletConnectRequests.ts @@ -1,4 +1,5 @@ // Taken from https://medium.com/walletconnect/how-to-build-a-wallet-in-react-native-with-the-web3wallet-sdk-b6f57bf02f9a +import { Wallet, providers } from 'ethers'; import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'; import { SignClientTypes } from '@walletconnect/types'; @@ -9,6 +10,7 @@ import { signDirectMessage, signEthMessage } from '../sign-message'; import { Account } from '../../types'; import { getMnemonic, getPathKey } from '../misc'; import { getCosmosAccounts } from '../accounts'; +import { TEIP155Chain, EIP155_CHAINS } from './EIP155Data'; export async function approveWalletConnectRequest( requestEvent: SignClientTypes.EventArguments['session_request'], @@ -17,7 +19,7 @@ export async function approveWalletConnectRequest( message: string, ) { const { params, id } = requestEvent; - const { request } = params; + const { request, chainId } = params; const path = (await getPathKey(network, account.counterId)).path; const mnemonic = await getMnemonic(); @@ -66,6 +68,19 @@ export async function approveWalletConnectRequest( return formatJsonRpcResult(id, { signature: cosmosAminoSignature.signature.signature, }); + + case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: + const provider = new providers.JsonRpcProvider( + EIP155_CHAINS[chainId as TEIP155Chain].rpc, + ); + + const privKey = (await getPathKey('eth', account.counterId)).privKey; + const wallet = new Wallet(privKey); + const sendTransaction = request.params[0]; + const connectedWallet = await wallet.connect(provider); + const hash = await connectedWallet.sendTransaction(sendTransaction); + const receipt = typeof hash === 'string' ? hash : hash?.hash; + return formatJsonRpcResult(id, receipt); default: throw new Error(getSdkError('INVALID_METHOD').message); } -- 2.45.2 From 0fa793bead04d10bcfb5e076c074d81574c21577 Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Thu, 21 Mar 2024 16:09:25 +0530 Subject: [PATCH 25/64] Add new page for approving eth transactions (#63) * Display funds on signRequest page * Format balance value * Display upto 18 digits * Use useMemo for provider * Display balance in wei * Make UI changes * Make review changes * Add page to approve eth transactions * Update approve transaction page ui * Update balance unit display --------- Co-authored-by: Shreerang Kale --- src/App.tsx | 18 +- src/components/DataBox.tsx | 17 ++ src/screens/ApproveTransaction.tsx | 228 ++++++++++++++++++ src/screens/SignRequest.tsx | 7 +- src/styles/stylesheet.js | 34 ++- src/types.ts | 10 + .../wallet-connect/WalletConnectRequests.ts | 35 +-- 7 files changed, 323 insertions(+), 26 deletions(-) create mode 100644 src/components/DataBox.tsx create mode 100644 src/screens/ApproveTransaction.tsx diff --git a/src/App.tsx b/src/App.tsx index 0a94d35..ec4a44c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -25,6 +25,7 @@ import { StackParamsList } from './types'; import { web3wallet } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; +import ApproveTransaction from './screens/ApproveTransaction'; const Stack = createNativeStackNavigator(); @@ -65,10 +66,9 @@ const App = (): React.JSX.Element => { web3wallet!.engine.signClient.session.get(topic); switch (request.method) { case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - navigation.navigate('SignRequest', { + navigation.navigate('ApproveTransaction', { network: 'eth', - address: request.params[0].from, - message: JSON.stringify(request.params[0], undefined, 2), + transaction: request.params[0], requestEvent, requestSessionData, }); @@ -167,9 +167,7 @@ const App = (): React.JSX.Element => { name="SignRequest" component={SignRequest} options={{ - headerTitle: () => ( - Sign this message? - ), + headerTitle: () => Sign Request, }} /> { title: 'New session', }} /> + + { + return ( + + {label} + + {data} + + + ); +}; + +export default DataBox; diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx new file mode 100644 index 0000000..d017a8e --- /dev/null +++ b/src/screens/ApproveTransaction.tsx @@ -0,0 +1,228 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import { Image, ScrollView, View } from 'react-native'; +import { ActivityIndicator, Button, Text, Appbar } from 'react-native-paper'; +import { PopulatedTransaction, providers, BigNumber } from 'ethers'; + +import { useNavigation } from '@react-navigation/native'; +import { + NativeStackNavigationProp, + NativeStackScreenProps, +} from '@react-navigation/native-stack'; +import { getHeaderTitle } from '@react-navigation/elements'; + +import { Account, StackParamsList } from '../types'; +import AccountDetails from '../components/AccountDetails'; +import styles from '../styles/stylesheet'; +import { retrieveSingleAccount } from '../utils/accounts'; +import { + approveWalletConnectRequest, + rejectWalletConnectRequest, +} from '../utils/wallet-connect/WalletConnectRequests'; +import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; +import { + TEIP155Chain, + EIP155_CHAINS, +} from '../utils/wallet-connect/EIP155Data'; +import DataBox from '../components/DataBox'; + +type SignRequestProps = NativeStackScreenProps< + StackParamsList, + 'ApproveTransaction' +>; + +const ApproveTransaction = ({ route }: SignRequestProps) => { + const requestSession = route.params?.requestSessionData; + const requestName = requestSession?.peer?.metadata?.name; + const requestIcon = requestSession?.peer?.metadata?.icons[0]; + const requestURL = requestSession?.peer?.metadata?.url; + + const [account, setAccount] = useState(); + const [transactionData, setTransactionData] = + useState(); + const [network, setNetwork] = useState(''); + const [isLoading, setIsLoading] = useState(true); + const [balance, setBalance] = useState(''); + + const requestParams = route.params!.requestEvent; + const chainId = requestParams?.params.chainId; + const provider = useMemo(() => { + return new providers.JsonRpcProvider( + EIP155_CHAINS[chainId as TEIP155Chain].rpc, + ); + }, [chainId]); + + const navigation = + useNavigation>(); + + const retrieveData = async ( + requestNetwork: string, + requestAddress: string, + ) => { + const requestAccount = await retrieveSingleAccount( + requestNetwork, + requestAddress, + ); + if (!requestAccount) { + navigation.navigate('InvalidPath'); + return; + } + + if (requestAccount !== account) { + setAccount(requestAccount); + } + if (requestNetwork !== network) { + setNetwork(requestNetwork); + } + + if (route.params?.transaction) { + setTransactionData(route.params?.transaction); + } + + setIsLoading(false); + }; + + const gasFees = useMemo(() => { + if (!transactionData) { + return; + } + return BigNumber.from(transactionData?.gasLimit) + .mul(BigNumber.from(transactionData?.gasPrice)) + .toString(); + }, [transactionData]); + + useEffect(() => { + route.params && + retrieveData(route.params?.network, route.params?.transaction.from!); + }, [route]); + + const acceptRequestHandler = async () => { + const { requestEvent } = route.params || {}; + + if (!account) { + throw new Error('account not found'); + } + + if (!requestEvent) { + throw new Error('Request event not found'); + } + + const response = await approveWalletConnectRequest( + requestEvent, + account, + network, + '', + provider, + ); + + const { topic } = requestEvent; + await web3wallet!.respondSessionRequest({ topic, response }); + + navigation.navigate('Laconic'); + }; + + const rejectRequestHandler = async () => { + if (route.params?.requestEvent) { + const response = rejectWalletConnectRequest(route.params?.requestEvent); + const { topic } = route.params?.requestEvent; + await web3wallet!.respondSessionRequest({ + topic, + response, + }); + } + navigation.navigate('Laconic'); + }; + + useEffect(() => { + const getAccountBalance = async (account: Account) => { + const fetchedBalance = await provider.getBalance(account.address); + setBalance(fetchedBalance.toString()); + }; + + if (account) { + getAccountBalance(account); + } + }, [account, provider]); + + useEffect(() => { + navigation.setOptions({ + // eslint-disable-next-line react/no-unstable-nested-components + header: ({ options, back }) => { + const title = getHeaderTitle(options, 'Approve Transaction'); + + return ( + + {back && ( + { + await rejectRequestHandler(); + navigation.navigate('Laconic'); + }} + /> + )} + + + ); + }, + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [navigation, route.name]); + + return ( + <> + {isLoading ? ( + + + + ) : ( + + + {requestIcon && ( + + )} + {requestName} + {requestURL} + + + From + + + + + + {transactionData && ( + + + + + + + )} + + + + + + )} + + ); +}; + +export default ApproveTransaction; diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index d22c932..35bd254 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -185,7 +185,7 @@ const SignRequest = ({ route }: SignRequestProps) => { navigation.setOptions({ // eslint-disable-next-line react/no-unstable-nested-components header: ({ options, back }) => { - const title = getHeaderTitle(options, route.name); + const title = getHeaderTitle(options, 'Sign Request'); return ( @@ -212,7 +212,7 @@ const SignRequest = ({ route }: SignRequestProps) => { ) : ( - + {requestIcon && ( { {requestURL} - {isCosmosSignDirect || isEthSendTransaction ? ( @@ -248,7 +247,7 @@ const SignRequest = ({ route }: SignRequestProps) => { No - + )} ); diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index 22f96a1..7d4fe66 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -18,7 +18,8 @@ const styles = StyleSheet.create({ fontWeight: '700', }, accountContainer: { - marginTop: 12, + padding: 8, + paddingBottom: 0, }, addAccountButton: { marginTop: 24, @@ -129,6 +130,9 @@ const styles = StyleSheet.create({ justifyContent: 'center', padding: 8, }, + approveTransaction: { + height: '40%', + }, buttonContainer: { marginTop: 50, flexDirection: 'row', @@ -216,6 +220,34 @@ const styles = StyleSheet.create({ display: 'flex', alignItems: 'center', }, + dataBoxContainer: { + marginBottom: 10, + }, + dataBoxLabel: { + fontSize: 18, + fontWeight: 'bold', + marginBottom: 3, + color: 'black', + }, + dataBox: { + borderWidth: 1, + borderColor: '#ccc', + padding: 10, + borderRadius: 5, + }, + dataBoxData: { + fontSize: 16, + color: 'black', + }, + transactionText: { + padding: 8, + fontSize: 18, + fontWeight: 'bold', + color: 'black', + }, + balancePadding: { + padding: 8, + }, }); export default styles; diff --git a/src/types.ts b/src/types.ts index 4ac0616..40bf5f4 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1,5 @@ +import { PopulatedTransaction } from 'ethers'; + import { SignClientTypes, SessionTypes } from '@walletconnect/types'; import { Web3WalletTypes } from '@walletconnect/web3wallet'; @@ -13,6 +15,14 @@ export type StackParamsList = { requestSessionData?: SessionTypes.Struct; } | undefined; + ApproveTransaction: + | { + network: string; + transaction: PopulatedTransaction; + requestEvent?: Web3WalletTypes.SessionRequest; + requestSessionData?: SessionTypes.Struct; + } + | undefined; InvalidPath: undefined; WalletConnect: undefined; AddSession: undefined; diff --git a/src/utils/wallet-connect/WalletConnectRequests.ts b/src/utils/wallet-connect/WalletConnectRequests.ts index 8f023c4..62c818c 100644 --- a/src/utils/wallet-connect/WalletConnectRequests.ts +++ b/src/utils/wallet-connect/WalletConnectRequests.ts @@ -10,16 +10,16 @@ import { signDirectMessage, signEthMessage } from '../sign-message'; import { Account } from '../../types'; import { getMnemonic, getPathKey } from '../misc'; import { getCosmosAccounts } from '../accounts'; -import { TEIP155Chain, EIP155_CHAINS } from './EIP155Data'; export async function approveWalletConnectRequest( requestEvent: SignClientTypes.EventArguments['session_request'], account: Account, network: string, - message: string, + message?: string, + provider?: providers.JsonRpcProvider, ) { const { params, id } = requestEvent; - const { request, chainId } = params; + const { request } = params; const path = (await getPathKey(network, account.counterId)).path; const mnemonic = await getMnemonic(); @@ -27,7 +27,24 @@ export async function approveWalletConnectRequest( const address = cosmosAccount.data.address; switch (request.method) { + case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: + if (!provider) { + throw new Error('JSON RPC provider not found'); + } + + const privKey = (await getPathKey('eth', account.counterId)).privKey; + const wallet = new Wallet(privKey); + const sendTransaction = request.params[0]; + const connectedWallet = await wallet.connect(provider); + const hash = await connectedWallet.sendTransaction(sendTransaction); + const receipt = typeof hash === 'string' ? hash : hash?.hash; + return formatJsonRpcResult(id, receipt); + case EIP155_SIGNING_METHODS.PERSONAL_SIGN: + if (!message) { + throw new Error('Message to be signed not found'); + } + const ethSignature = await signEthMessage(message, account.counterId); return formatJsonRpcResult(id, ethSignature); @@ -69,18 +86,6 @@ export async function approveWalletConnectRequest( signature: cosmosAminoSignature.signature.signature, }); - case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - const provider = new providers.JsonRpcProvider( - EIP155_CHAINS[chainId as TEIP155Chain].rpc, - ); - - const privKey = (await getPathKey('eth', account.counterId)).privKey; - const wallet = new Wallet(privKey); - const sendTransaction = request.params[0]; - const connectedWallet = await wallet.connect(provider); - const hash = await connectedWallet.sendTransaction(sendTransaction); - const receipt = typeof hash === 'string' ? hash : hash?.hash; - return formatJsonRpcResult(id, receipt); default: throw new Error(getSdkError('INVALID_METHOD').message); } -- 2.45.2 From b275f376c48a813bd7273930312ced7779cb991e Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Fri, 22 Mar 2024 15:40:43 +0530 Subject: [PATCH 26/64] Refactor account utils (#64) * Refactor method in account utils * Make container height auto --------- Co-authored-by: Adw8 --- src/styles/stylesheet.js | 3 ++- src/utils/accounts.ts | 37 +------------------------------------ 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index 7d4fe66..d2a289d 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -117,9 +117,10 @@ const styles = StyleSheet.create({ borderWidth: 1, borderRadius: 5, marginTop: 50, - height: 50, + height: 'auto', alignItems: 'center', justifyContent: 'center', + padding: 10, }, requestDirectMessage: { borderWidth: 1, diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 382d79f..aebbf8f 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -19,7 +19,6 @@ import { stringToPath } from '@cosmjs/crypto'; import { Account, WalletDetails } from '../types'; import { getHDPath, - getMnemonic, getPathKey, resetKeyServers, updateGlobalCounter, @@ -85,27 +84,9 @@ const createWallet = async (): Promise => { const addAccount = async (network: string): Promise => { try { - const mnemonic = await getMnemonic(); - const hdNode = HDNode.fromMnemonic(mnemonic); const id = await getNextAccountId(network); const hdPath = getHDPath(network, `0'/0/${id}`); - - const node = hdNode.derivePath(hdPath); - const pubKey = node.publicKey; - const address = await getAddress(network, mnemonic, `0'/0/${id}`); - - await updateAccountIndices(network, id); - const { counterId } = await updateGlobalCounter(network); - - await Promise.all([ - setInternetCredentials( - `${network}:keyServer:${counterId}`, - `${network}:pathKey:${counterId}`, - `0'/0/${id},${node.privateKey},${node.publicKey},${address}`, - ), - ]); - - return { counterId, pubKey, address, hdPath }; + return addAccountFromHDPath(hdPath); } catch (error) { console.error('Error creating account:', error); } @@ -322,22 +303,6 @@ const getCosmosAccounts = async ( return { cosmosWallet, data }; }; -const getAddress = async ( - network: string, - mnemonic: string, - path: string, -): Promise => { - switch (network) { - case 'eth': - return HDNode.fromMnemonic(mnemonic).derivePath(`m/44'/60'/${path}`) - .address; - case 'cosmos': - return (await getCosmosAccounts(mnemonic, `${path}`)).data.address; - default: - throw new Error('Invalid wallet type'); - } -}; - export { createWallet, addAccount, -- 2.45.2 From 3a2087b389dad5dea1b813ff707541aefe21a8b3 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Wed, 27 Mar 2024 18:09:01 +0530 Subject: [PATCH 27/64] Add method to send cosmos tokens (#66) * Add support to send cosmos tokens * Fix approve transaction page ui * Take gas amount from dApp * Handle review changes * Add check while creating stargate client * Remove unnecessary checks * Remove unnecessary states * Fix balance showing undefined while loading --- .env | 1 + package.json | 1 + src/App.tsx | 11 + src/screens/ApproveTransaction.tsx | 185 ++++++++------ src/screens/SignMessage.tsx | 21 +- src/screens/SignRequest.tsx | 36 ++- src/styles/stylesheet.js | 1 - src/types.ts | 32 ++- src/utils/constants.ts | 1 + src/utils/wallet-connect/COSMOSData.ts | 37 +++ .../wallet-connect/WalletConnectRequests.ts | 52 +++- yarn.lock | 226 +++++++++++++++++- 12 files changed, 474 insertions(+), 130 deletions(-) create mode 100644 src/utils/constants.ts create mode 100644 src/utils/wallet-connect/COSMOSData.ts diff --git a/.env b/.env index c039d12..9ee31bb 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ WALLET_CONNECT_PROJECT_ID= + diff --git a/package.json b/package.json index 09bc1f6..143b800 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@cosmjs/amino": "^0.32.3", "@cosmjs/crypto": "^0.32.3", "@cosmjs/proto-signing": "^0.32.3", + "@cosmjs/stargate": "^0.32.3", "@ethersproject/shims": "^5.7.0", "@json-rpc-tools/utils": "^1.7.6", "@react-native-async-storage/async-storage": "^1.22.3", diff --git a/src/App.tsx b/src/App.tsx index ec4a44c..24d522d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -120,6 +120,17 @@ const App = (): React.JSX.Element => { }); break; + + case 'cosmos_sendTokens': + navigation.navigate('ApproveTransaction', { + network: 'cosmos', + transaction: request.params[0], + requestEvent, + requestSessionData, + }); + + break; + default: throw new Error('Invalid method'); } diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index d017a8e..21e0f6f 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -9,6 +9,12 @@ import { NativeStackScreenProps, } from '@react-navigation/native-stack'; import { getHeaderTitle } from '@react-navigation/elements'; +import { DirectSecp256k1Wallet } from '@cosmjs/proto-signing'; +import { + calculateFee, + GasPrice, + SigningStargateClient, +} from '@cosmjs/stargate'; import { Account, StackParamsList } from '../types'; import AccountDetails from '../components/AccountDetails'; @@ -19,11 +25,11 @@ import { rejectWalletConnectRequest, } from '../utils/wallet-connect/WalletConnectRequests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; -import { - TEIP155Chain, - EIP155_CHAINS, -} from '../utils/wallet-connect/EIP155Data'; +import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data'; import DataBox from '../components/DataBox'; +import { getPathKey } from '../utils/misc'; +import { COSMOS_TESTNET_CHAINS } from '../utils/wallet-connect/COSMOSData'; +import { COSMOS_DENOM } from '../utils/constants'; type SignRequestProps = NativeStackScreenProps< StackParamsList, @@ -31,87 +37,69 @@ type SignRequestProps = NativeStackScreenProps< >; const ApproveTransaction = ({ route }: SignRequestProps) => { - const requestSession = route.params?.requestSessionData; - const requestName = requestSession?.peer?.metadata?.name; - const requestIcon = requestSession?.peer?.metadata?.icons[0]; - const requestURL = requestSession?.peer?.metadata?.url; + const requestSession = route.params.requestSessionData; + const requestName = requestSession.peer.metadata.name; + const requestIcon = requestSession.peer.metadata.icons[0]; + const requestURL = requestSession.peer.metadata.url; + const network = route.params.network; + const transaction = route.params.transaction; + const requestEvent = route.params.requestEvent; + const chainId = requestEvent.params.chainId; const [account, setAccount] = useState(); - const [transactionData, setTransactionData] = - useState(); - const [network, setNetwork] = useState(''); const [isLoading, setIsLoading] = useState(true); const [balance, setBalance] = useState(''); + const [cosmosStargateClient, setCosmosStargateClient] = + useState(); - const requestParams = route.params!.requestEvent; - const chainId = requestParams?.params.chainId; const provider = useMemo(() => { - return new providers.JsonRpcProvider( - EIP155_CHAINS[chainId as TEIP155Chain].rpc, - ); - }, [chainId]); + if (network === 'eth') { + return new providers.JsonRpcProvider(EIP155_CHAINS[chainId].rpc); + } + }, [chainId, network]); const navigation = useNavigation>(); - const retrieveData = async ( - requestNetwork: string, - requestAddress: string, - ) => { - const requestAccount = await retrieveSingleAccount( - requestNetwork, - requestAddress, - ); + const retrieveData = async (requestAddress: string) => { + const requestAccount = await retrieveSingleAccount(network, requestAddress); if (!requestAccount) { navigation.navigate('InvalidPath'); return; } - if (requestAccount !== account) { - setAccount(requestAccount); - } - if (requestNetwork !== network) { - setNetwork(requestNetwork); - } - - if (route.params?.transaction) { - setTransactionData(route.params?.transaction); - } - + setAccount(requestAccount); setIsLoading(false); }; const gasFees = useMemo(() => { - if (!transactionData) { - return; + if (network === 'eth') { + return BigNumber.from(transaction.gasLimit) + .mul(BigNumber.from(transaction.gasPrice)) + .toString(); + } else { + const gasPrice = GasPrice.fromString(transaction.gasPrice!.toString()); + const cosmosFees = calculateFee(Number(transaction.gasLimit), gasPrice); + + return cosmosFees.amount[0].amount; } - return BigNumber.from(transactionData?.gasLimit) - .mul(BigNumber.from(transactionData?.gasPrice)) - .toString(); - }, [transactionData]); + }, [transaction, network]); useEffect(() => { - route.params && - retrieveData(route.params?.network, route.params?.transaction.from!); + retrieveData(transaction.from!); }, [route]); const acceptRequestHandler = async () => { - const { requestEvent } = route.params || {}; - if (!account) { throw new Error('account not found'); } - if (!requestEvent) { - throw new Error('Request event not found'); - } - const response = await approveWalletConnectRequest( requestEvent, account, network, '', - provider, + network === 'eth' ? provider : cosmosStargateClient, ); const { topic } = requestEvent; @@ -121,27 +109,37 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { }; const rejectRequestHandler = async () => { - if (route.params?.requestEvent) { - const response = rejectWalletConnectRequest(route.params?.requestEvent); - const { topic } = route.params?.requestEvent; - await web3wallet!.respondSessionRequest({ - topic, - response, - }); - } + const response = rejectWalletConnectRequest(requestEvent); + const { topic } = requestEvent; + await web3wallet!.respondSessionRequest({ + topic, + response, + }); + navigation.navigate('Laconic'); }; useEffect(() => { const getAccountBalance = async (account: Account) => { - const fetchedBalance = await provider.getBalance(account.address); - setBalance(fetchedBalance.toString()); + if (network === 'eth') { + const fetchedBalance = + provider && (await provider.getBalance(account.address)); + + setBalance(fetchedBalance ? fetchedBalance.toString() : '0'); + } else { + const cosmosBalance = await cosmosStargateClient?.getBalance( + account.address, + COSMOS_DENOM, + ); + + setBalance(cosmosBalance?.amount!); + } }; if (account) { getAccountBalance(account); } - }, [account, provider]); + }, [account, provider, network, cosmosStargateClient]); useEffect(() => { navigation.setOptions({ @@ -167,6 +165,34 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, [navigation, route.name]); + useEffect(() => { + if (network !== 'cosmos') { + return; + } + + const setClient = async () => { + if (!account) { + return; + } + + const cosmosPrivKey = (await getPathKey('cosmos', account.counterId)) + .privKey; + + const sender = await DirectSecp256k1Wallet.fromKey( + Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'), + ); + + const client = await SigningStargateClient.connectWithSigner( + COSMOS_TESTNET_CHAINS[chainId as string].rpc, + sender, + ); + + setCosmosStargateClient(client); + }; + + setClient(); + }, [account, chainId, network]); + return ( <> {isLoading ? ( @@ -174,7 +200,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { ) : ( - + {requestIcon && ( { - {transactionData && ( + {transaction && ( - + - - + + {network === 'eth' && ( + + )} )} diff --git a/src/screens/SignMessage.tsx b/src/screens/SignMessage.tsx index 39df73f..521906e 100644 --- a/src/screens/SignMessage.tsx +++ b/src/screens/SignMessage.tsx @@ -12,23 +12,18 @@ import AccountDetails from '../components/AccountDetails'; type SignProps = NativeStackScreenProps; const SignMessage = ({ route }: SignProps) => { - const network = route.params?.selectedNetwork; - const account = route.params?.accountInfo; + const network = route.params.selectedNetwork; + const account = route.params.accountInfo; const [message, setMessage] = useState(''); const signMessageHandler = async () => { - if (network) { - if (!account) { - throw new Error('Account is not valid'); - } - const signedMessage = await signMessage({ - message, - network, - accountId: account.counterId, - }); - Alert.alert('Signature', signedMessage); - } + const signedMessage = await signMessage({ + message, + network, + accountId: account.counterId, + }); + Alert.alert('Signature', signedMessage); }; return ( diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 35bd254..920ac8d 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -24,7 +24,7 @@ import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; type SignRequestProps = NativeStackScreenProps; const SignRequest = ({ route }: SignRequestProps) => { - const requestSession = route.params?.requestSessionData; + const requestSession = route.params.requestSessionData; const requestName = requestSession?.peer?.metadata?.name; const requestIcon = requestSession?.peer?.metadata?.icons[0]; const requestURL = requestSession?.peer?.metadata?.url; @@ -38,7 +38,7 @@ const SignRequest = ({ route }: SignRequestProps) => { useNavigation>(); const isCosmosSignDirect = useMemo(() => { - const requestParams = route.params!.requestEvent; + const requestParams = route.params.requestEvent; if (!requestParams) { return false; @@ -48,7 +48,7 @@ const SignRequest = ({ route }: SignRequestProps) => { }, [route.params]); const isEthSendTransaction = useMemo(() => { - const requestParams = route.params!.requestEvent; + const requestParams = route.params.requestEvent; if (!requestParams) { return false; @@ -107,24 +107,22 @@ const SignRequest = ({ route }: SignRequestProps) => { if (route.path) { const sanitizedRoute = sanitizePath(route.path); sanitizedRoute && - route.params && retrieveData( - route.params?.network, - route.params?.address, - route.params?.message, + route.params.network, + route.params.address, + route.params.message, ); return; } - route.params && - retrieveData( - route.params?.network, - route.params?.address, - route.params?.message, - ); + retrieveData( + route.params.network, + route.params.address, + route.params.message, + ); }, [route]); const handleWalletConnectRequest = async () => { - const { requestEvent } = route.params || {}; + const { requestEvent } = route.params; if (!account) { throw new Error('account not found'); @@ -160,7 +158,7 @@ const SignRequest = ({ route }: SignRequestProps) => { }; const signMessageHandler = async () => { - if (route.params?.requestEvent) { + if (route.params.requestEvent) { await handleWalletConnectRequest(); } else { await handleIntent(); @@ -170,9 +168,9 @@ const SignRequest = ({ route }: SignRequestProps) => { }; const rejectRequestHandler = async () => { - if (route.params?.requestEvent) { - const response = rejectWalletConnectRequest(route.params?.requestEvent); - const { topic } = route.params?.requestEvent; + if (route.params.requestEvent) { + const response = rejectWalletConnectRequest(route.params.requestEvent); + const { topic } = route.params.requestEvent; await web3wallet!.respondSessionRequest({ topic, response, @@ -212,7 +210,7 @@ const SignRequest = ({ route }: SignRequestProps) => { ) : ( - + {requestIcon && ( = { + 'cosmos:theta-testnet-001': { + chainId: 'theta-testnet-001', + name: 'Cosmos Hub Testnet', + rpc: 'https://rpc-t.cosmos.nodestake.top', + namespace: 'cosmos', + }, +}; + +/** + * Methods + */ +export const COSMOS_METHODS = { + COSMOS_SIGN_DIRECT: 'cosmos_signDirect', + COSMOS_SIGN_AMINO: 'cosmos_signAmino', + COSMOS_SEND_TOKENS: 'cosmos_sendTokens', // Added for pay.laconic.com +}; diff --git a/src/utils/wallet-connect/WalletConnectRequests.ts b/src/utils/wallet-connect/WalletConnectRequests.ts index 62c818c..dd11741 100644 --- a/src/utils/wallet-connect/WalletConnectRequests.ts +++ b/src/utils/wallet-connect/WalletConnectRequests.ts @@ -4,19 +4,26 @@ import { Wallet, providers } from 'ethers'; import { formatJsonRpcError, formatJsonRpcResult } from '@json-rpc-tools/utils'; import { SignClientTypes } from '@walletconnect/types'; import { getSdkError } from '@walletconnect/utils'; +import { + SigningStargateClient, + coins, + GasPrice, + calculateFee, +} from '@cosmjs/stargate'; import { EIP155_SIGNING_METHODS } from './EIP155Lib'; import { signDirectMessage, signEthMessage } from '../sign-message'; import { Account } from '../../types'; import { getMnemonic, getPathKey } from '../misc'; import { getCosmosAccounts } from '../accounts'; +import { COSMOS_DENOM } from '../constants'; export async function approveWalletConnectRequest( requestEvent: SignClientTypes.EventArguments['session_request'], account: Account, network: string, message?: string, - provider?: providers.JsonRpcProvider, + provider?: providers.JsonRpcProvider | SigningStargateClient, ) { const { params, id } = requestEvent; const { request } = params; @@ -35,10 +42,17 @@ export async function approveWalletConnectRequest( const privKey = (await getPathKey('eth', account.counterId)).privKey; const wallet = new Wallet(privKey); const sendTransaction = request.params[0]; - const connectedWallet = await wallet.connect(provider); + + if (!(provider instanceof providers.JsonRpcProvider)) { + throw new Error('Provider not found'); + } + const connectedWallet = wallet.connect(provider); + const hash = await connectedWallet.sendTransaction(sendTransaction); const receipt = typeof hash === 'string' ? hash : hash?.hash; - return formatJsonRpcResult(id, receipt); + return formatJsonRpcResult(id, { + signature: receipt, + }); case EIP155_SIGNING_METHODS.PERSONAL_SIGN: if (!message) { @@ -46,7 +60,9 @@ export async function approveWalletConnectRequest( } const ethSignature = await signEthMessage(message, account.counterId); - return formatJsonRpcResult(id, ethSignature); + return formatJsonRpcResult(id, { + signature: ethSignature, + }); case 'cosmos_signDirect': // Reference: https://github.com/confio/cosmjs-types/blob/66e52711914fccd2a9d1a03e392d3628fdf499e2/src/cosmos/tx/v1beta1/tx.ts#L51 @@ -86,6 +102,34 @@ export async function approveWalletConnectRequest( signature: cosmosAminoSignature.signature.signature, }); + case 'cosmos_sendTokens': + const amount = coins(request.params[0].value, COSMOS_DENOM); + const gasPrice = GasPrice.fromString( + request.params[0].gasPrice.toString(), + ); + const cosmosFee = calculateFee( + Number(request.params[0].gasLimit), + gasPrice, + ); + + const receiverAddress = request.params[0].to; + + if (!(provider instanceof SigningStargateClient)) { + throw new Error('Cosmos stargate client not found'); + } + + const result = await provider.sendTokens( + address, + receiverAddress, + amount, + cosmosFee, + 'Sending signed tx from Laconic Wallet', + ); + + return formatJsonRpcResult(id, { + signature: result.transactionHash, + }); + default: throw new Error(getSdkError('INVALID_METHOD').message); } diff --git a/yarn.lock b/yarn.lock index 8cc90cf..c47f594 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1167,6 +1167,14 @@ deepmerge "^3.2.0" hoist-non-react-statics "^3.3.0" +"@confio/ics23@^0.6.8": + version "0.6.8" + resolved "https://registry.yarnpkg.com/@confio/ics23/-/ics23-0.6.8.tgz#2a6b4f1f2b7b20a35d9a0745bb5a446e72930b3d" + integrity sha512-wB6uo+3A50m0sW/EWcU64xpV/8wShZ6bMTa7pF8eYsTrSkQA7oLUIJcs/wb8g4y2Oyq701BaGiO6n/ak5WXO1w== + dependencies: + "@noble/hashes" "^1.0.0" + protobufjs "^6.8.8" + "@cosmjs/amino@^0.32.3": version "0.32.3" resolved "https://registry.yarnpkg.com/@cosmjs/amino/-/amino-0.32.3.tgz#b81d4a2b8d61568431a1afcd871e1344a19d97ff" @@ -1199,6 +1207,14 @@ bech32 "^1.1.4" readonly-date "^1.0.0" +"@cosmjs/json-rpc@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/json-rpc/-/json-rpc-0.32.3.tgz#ccffdd7f722cecfab6daaa7463843b92f5d25355" + integrity sha512-JwFRWZa+Y95KrAG8CuEbPVOSnXO2uMSEBcaAB/FBU3Mo4jQnDoUjXvt3vwtFWxfAytrWCn1I4YDFaOAinnEG/Q== + dependencies: + "@cosmjs/stream" "^0.32.3" + xstream "^11.14.0" + "@cosmjs/math@^0.32.3": version "0.32.3" resolved "https://registry.yarnpkg.com/@cosmjs/math/-/math-0.32.3.tgz#16e4256f4da507b9352327da12ae64056a2ba6c9" @@ -1218,6 +1234,55 @@ "@cosmjs/utils" "^0.32.3" cosmjs-types "^0.9.0" +"@cosmjs/socket@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/socket/-/socket-0.32.3.tgz#fa5c36bf58e87c0ad865d6318ecb0f8d9c89a28a" + integrity sha512-F2WwNmaUPdZ4SsH6Uyreq3wQk7jpaEkb3wfOP951f5Jt6HCW/PxbxhKzHkAAf6+Sqks6SPhkbWoE8XaZpjL2KA== + dependencies: + "@cosmjs/stream" "^0.32.3" + isomorphic-ws "^4.0.1" + ws "^7" + xstream "^11.14.0" + +"@cosmjs/stargate@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/stargate/-/stargate-0.32.3.tgz#5a92b222ada960ebecea72cc9f366370763f4b66" + integrity sha512-OQWzO9YWKerUinPIxrO1MARbe84XkeXJAW0lyMIjXIEikajuXZ+PwftiKA5yA+8OyditVmHVLtPud6Pjna2s5w== + dependencies: + "@confio/ics23" "^0.6.8" + "@cosmjs/amino" "^0.32.3" + "@cosmjs/encoding" "^0.32.3" + "@cosmjs/math" "^0.32.3" + "@cosmjs/proto-signing" "^0.32.3" + "@cosmjs/stream" "^0.32.3" + "@cosmjs/tendermint-rpc" "^0.32.3" + "@cosmjs/utils" "^0.32.3" + cosmjs-types "^0.9.0" + xstream "^11.14.0" + +"@cosmjs/stream@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/stream/-/stream-0.32.3.tgz#7522579aaf18025d322c2f33d6fb7573220395d6" + integrity sha512-J2zVWDojkynYifAUcRmVczzmp6STEpyiAARq0rSsviqjreGIfspfuws/8rmkPa6qQBZvpQOBQCm2HyZZwYplIw== + dependencies: + xstream "^11.14.0" + +"@cosmjs/tendermint-rpc@^0.32.3": + version "0.32.3" + resolved "https://registry.yarnpkg.com/@cosmjs/tendermint-rpc/-/tendermint-rpc-0.32.3.tgz#f0406b9f0233e588fb924dca8c614972f9038aff" + integrity sha512-xeprW+VR9xKGstqZg0H/KBZoUp8/FfFyS9ljIUTLM/UINjP2MhiwncANPS2KScfJVepGufUKk0/phHUeIBSEkw== + dependencies: + "@cosmjs/crypto" "^0.32.3" + "@cosmjs/encoding" "^0.32.3" + "@cosmjs/json-rpc" "^0.32.3" + "@cosmjs/math" "^0.32.3" + "@cosmjs/socket" "^0.32.3" + "@cosmjs/stream" "^0.32.3" + "@cosmjs/utils" "^0.32.3" + axios "^1.6.0" + readonly-date "^1.0.0" + xstream "^11.14.0" + "@cosmjs/utils@^0.32.3": version "0.32.3" resolved "https://registry.yarnpkg.com/@cosmjs/utils/-/utils-0.32.3.tgz#5dcaee6dd7cc846cdc073e9a7a7f63242f5f7e31" @@ -1944,6 +2009,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== +"@noble/hashes@^1.0.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -2062,6 +2132,59 @@ resolved "https://registry.yarnpkg.com/@pedrouid/environment/-/environment-1.0.1.tgz#858f0f8a057340e0b250398b75ead77d6f4342ec" integrity sha512-HaW78NszGzRZd9SeoI3JD11JqY+lubnaOx7Pewj5pfjqWXOEATpeKIFb9Z4t2WBUK2iryiXX3lzWwmYWgUL0Ug== +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@react-native-async-storage/async-storage@^1.22.3": version "1.22.3" resolved "https://registry.yarnpkg.com/@react-native-async-storage/async-storage/-/async-storage-1.22.3.tgz#ad490236a9eda8ac68cffc12c738f20b1815464e" @@ -2686,6 +2809,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/long@^4.0.1": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" + integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== + "@types/node@*": version "20.11.16" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.16.tgz#4411f79411514eb8e2926f036c86c9f0e4ec6708" @@ -2693,6 +2821,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@>=13.7.0": + version "20.11.30" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.30.tgz#9c33467fc23167a347e73834f788f4b9f399d66f" + integrity sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw== + dependencies: + undici-types "~5.26.4" + "@types/node@^17.0.31": version "17.0.45" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" @@ -3323,6 +3458,11 @@ asynciterator.prototype@^1.0.0: dependencies: has-symbols "^1.0.3" +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== + at-least-node@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2" @@ -3338,6 +3478,15 @@ available-typed-arrays@^1.0.5, available-typed-arrays@^1.0.6: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.6.tgz#ac812d8ce5a6b976d738e1c45f08d0b00bc7d725" integrity sha512-j1QzY8iPNPG4o4xmO3ptzpRxTciqD3MgEHtifP/YnJpIo58Xu+ne4BejlbkuaLfXn/nz6HFiw29bLpj2PNMdGg== +axios@^1.6.0: + version "1.6.8" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.8.tgz#66d294951f5d988a00e87a0ffb955316a619ea66" + integrity sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ== + dependencies: + follow-redirects "^1.15.6" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + babel-core@^7.0.0-bridge.0: version "7.0.0-bridge.0" resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" @@ -3864,6 +4013,13 @@ colorette@^1.0.7: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== + dependencies: + delayed-stream "~1.0.0" + command-exists@^1.2.8: version "1.2.9" resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" @@ -4120,6 +4276,11 @@ defu@^6.1.3, defu@^6.1.4: resolved "https://registry.yarnpkg.com/defu/-/defu-6.1.4.tgz#4e0c9cf9ff68fe5f3d7f2765cc1a012dfdcb0479" integrity sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg== +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== + denodeify@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" @@ -4881,6 +5042,11 @@ flow-parser@^0.206.0: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.206.0.tgz#f4f794f8026535278393308e01ea72f31000bfef" integrity sha512-HVzoK3r6Vsg+lKvlIZzaWNBVai+FXTX1wdYhz/wVlH13tb/gOdLXmlTqy6odmTBhT5UoWUbq0k8263Qhr9d88w== +follow-redirects@^1.15.6: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== + for-each@^0.3.3: version "0.3.3" resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" @@ -4888,6 +5054,15 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -5040,7 +5215,7 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -globalthis@^1.0.3: +globalthis@^1.0.1, globalthis@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf" integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA== @@ -5641,6 +5816,11 @@ isomorphic-unfetch@3.1.0, isomorphic-unfetch@^3.1.0: node-fetch "^2.6.1" unfetch "^4.2.0" +isomorphic-ws@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc" + integrity sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz#2d166c4b0644d43a39f04bf6c2edd1e585f31756" @@ -6390,6 +6570,11 @@ logkitty@^0.7.1: dayjs "^1.8.15" yargs "^15.1.0" +long@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" + integrity sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -6731,7 +6916,7 @@ mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.27, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -7426,6 +7611,30 @@ prop-types@^15.7.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +protobufjs@^6.8.8: + version "6.11.4" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-6.11.4.tgz#29a412c38bf70d89e537b6d02d904a6f448173aa" + integrity sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/long" "^4.0.1" + "@types/node" ">=13.7.0" + long "^4.0.0" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + public-encrypt@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" @@ -8409,6 +8618,11 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +symbol-observable@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.3.tgz#5b521d3d07a43c351055fa43b8355b62d33fd16a" + integrity sha512-sQV7phh2WCYAn81oAkakC5qjq2Ml0g8ozqz03wOGnx9dDlG1de6yrF+0RAzSJD8fPUow3PTSMf2SAbOGxb93BA== + system-architecture@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/system-architecture/-/system-architecture-0.1.0.tgz#71012b3ac141427d97c67c56bc7921af6bff122d" @@ -8933,6 +9147,14 @@ ws@^7, ws@^7.5.1: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== +xstream@^11.14.0: + version "11.14.0" + resolved "https://registry.yarnpkg.com/xstream/-/xstream-11.14.0.tgz#2c071d26b18310523b6877e86b4e54df068a9ae5" + integrity sha512-1bLb+kKKtKPbgTK6i/BaoAn03g47PpFstlbe1BA+y3pNS/LfvcaghS5BFf9+EE1J+KwSQsEpfJvFN5GqFtiNmw== + dependencies: + globalthis "^1.0.1" + symbol-observable "^2.0.3" + xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" -- 2.45.2 From 4eecdb7e4cd06404d18b7e35facf3dd29788d482 Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Thu, 28 Mar 2024 11:10:55 +0530 Subject: [PATCH 28/64] Support SIWE using selected account (#61) * Sign in using selected address * Use optional namespace * Merge namespaces and use combined namespace * Add todo for lodash merge * Use walletConnect util buildApprovedNamespaces while approving sessions * Lint fixes --------- Co-authored-by: Adw8 Co-authored-by: Nabarun --- README.md | 2 +- __tests__/App.test.tsx | 2 +- metro.config.js | 2 +- package.json | 2 + src/App.tsx | 2 +- src/components/PairingModal.tsx | 241 ++++++++++-------- src/context/AccountsContext.tsx | 8 +- src/hooks/useInitialization.ts | 2 +- src/screens/ApproveTransaction.tsx | 30 ++- src/screens/HomeScreen.tsx | 4 +- src/screens/SignRequest.tsx | 88 ++++--- src/screens/WalletConnect.tsx | 4 +- src/utils/wallet-connect/COSMOSData.ts | 24 +- src/utils/wallet-connect/EIP155Data.ts | 10 + src/utils/wallet-connect/EIP155Lib.ts | 94 ------- src/utils/wallet-connect/Helpers.ts | 2 +- .../wallet-connect/WalletConnectRequests.ts | 6 +- yarn.lock | 5 + 18 files changed, 255 insertions(+), 273 deletions(-) diff --git a/README.md b/README.md index 00651c7..f1dab0e 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ 5. Set up the Android device: - - For a physical device, refer to the [React Native documentation for running on a physical device]("https://reactnative.dev/docs/running-on-device) + - For a physical device, refer to the [React Native documentation for running on a physical device](https://reactnative.dev/docs/running-on-device) - For a virtual device, continue with the steps. diff --git a/__tests__/App.test.tsx b/__tests__/App.test.tsx index fe831ed..7f59a79 100644 --- a/__tests__/App.test.tsx +++ b/__tests__/App.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import App from '../src/App'; // Note: import explicitly to use the types shipped with jest. -import {it} from '@jest/globals'; +import { it } from '@jest/globals'; // Note: test renderer must be required after react-native. import renderer from 'react-test-renderer'; diff --git a/metro.config.js b/metro.config.js index ad8f87b..ab63415 100644 --- a/metro.config.js +++ b/metro.config.js @@ -1,4 +1,4 @@ -const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); +const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config'); /** * Metro configuration diff --git a/package.json b/package.json index 143b800..21dc97d 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "cosmjs-types": "^0.9.0", "ethers": "5.7.2", "fast-text-encoding": "^1.0.6", + "lodash": "^4.17.21", "metro-react-native-babel-preset": "^0.77.0", "patch-package": "^8.0.0", "postinstall-postinstall": "^2.1.0", @@ -51,6 +52,7 @@ "@react-native/eslint-config": "0.73.2", "@react-native/metro-config": "0.73.4", "@react-native/typescript-config": "0.73.1", + "@types/lodash": "^4.17.0", "@types/react": "^18.2.6", "@types/react-native-vector-icons": "^6.4.18", "@types/react-test-renderer": "^18.0.0", diff --git a/src/App.tsx b/src/App.tsx index 24d522d..d62e52f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -23,7 +23,7 @@ import AddSession from './screens/AddSession'; import WalletConnect from './screens/WalletConnect'; import { StackParamsList } from './types'; import { web3wallet } from './utils/wallet-connect/WalletConnectUtils'; -import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Lib'; +import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; import ApproveTransaction from './screens/ApproveTransaction'; diff --git a/src/components/PairingModal.tsx b/src/components/PairingModal.tsx index 3f282ac..bb24f9e 100644 --- a/src/components/PairingModal.tsx +++ b/src/components/PairingModal.tsx @@ -1,15 +1,17 @@ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { Image, View, Modal, ScrollView } from 'react-native'; import { Button, Text } from 'react-native-paper'; +import mergeWith from 'lodash/mergeWith'; -import { SessionTypes } from '@walletconnect/types'; -import { getSdkError } from '@walletconnect/utils'; +import { buildApprovedNamespaces, getSdkError } from '@walletconnect/utils'; -import { PairingModalProps } from '../types'; +import { AccountsState, PairingModalProps } from '../types'; import styles from '../styles/stylesheet'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { useAccounts } from '../context/AccountsContext'; import { useWalletConnect } from '../context/WalletConnectContext'; +import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data'; +import { COSMOS_MAINNET_CHAINS } from '../utils/wallet-connect/COSMOSData'; const PairingModal = ({ visible, @@ -18,7 +20,7 @@ const PairingModal = ({ setModalVisible, setToastVisible, }: PairingModalProps) => { - const { accounts } = useAccounts(); + const { accounts, currentIndex } = useAccounts(); const url = currentProposal?.params?.proposer?.metadata.url; const icon = currentProposal?.params.proposer?.metadata.icons[0]; @@ -38,126 +40,147 @@ const PairingModal = ({ return; } const { params } = currentProposal; - const { requiredNamespaces } = params; + const { requiredNamespaces, optionalNamespaces } = params; + setWalletConnectData({ walletConnectMethods: [], walletConnectEvents: [], walletConnectChains: [], }); - Object.keys(requiredNamespaces).forEach(key => { - switch (key) { - case 'eip155': - const { - methods: ethMethods, - events: ethEvents, - chains: ethChains, - } = currentProposal?.params?.requiredNamespaces.eip155; - setWalletConnectData(prevData => { - return { - walletConnectMethods: [ - ...prevData.walletConnectMethods, - ...ethMethods, - ], - walletConnectEvents: [ - ...prevData.walletConnectEvents, - ...ethEvents, - ], - walletConnectChains: ethChains - ? [...prevData.walletConnectChains, ...ethChains] - : [...prevData.walletConnectChains], - }; - }); - break; - case 'cosmos': - const { - methods: cosmosMethods, - events: cosmosEvents, - chains: cosmosChains, - } = currentProposal?.params?.requiredNamespaces.cosmos; + const combinedNamespaces = mergeWith( + requiredNamespaces, + optionalNamespaces, + (obj, src) => + Array.isArray(obj) && Array.isArray(src) ? [...src, ...obj] : undefined, + ); + Object.keys(combinedNamespaces).forEach(key => { + const { methods, events, chains } = combinedNamespaces[key]; - setWalletConnectData(prevData => { - return { - walletConnectMethods: [ - ...prevData.walletConnectMethods, - ...cosmosMethods, - ], - walletConnectEvents: [ - ...prevData.walletConnectEvents, - ...cosmosEvents, - ], - walletConnectChains: cosmosChains - ? [...prevData.walletConnectChains, ...cosmosChains] - : [...prevData.walletConnectChains], - }; - }); - break; - default: - throw new Error(`${key} not supported`); - } + setWalletConnectData(prevData => { + return { + walletConnectMethods: [...prevData.walletConnectMethods, ...methods], + walletConnectEvents: [...prevData.walletConnectEvents, ...events], + walletConnectChains: chains + ? [...prevData.walletConnectChains, ...chains] + : [...prevData.walletConnectChains], + }; + }); }); }, [currentProposal]); const { setActiveSessions } = useWalletConnect(); - const handleAccept = async () => { - if (currentProposal) { - const { id, params } = currentProposal; - const { requiredNamespaces, relays } = params; - const namespaces: SessionTypes.Namespaces = {}; + const supportedNamespaces = useMemo(() => { + if (!currentProposal) { + return; + } - Object.keys(requiredNamespaces).forEach(key => { - let currentAddresses: string[]; + // eip155 + const eip155Chains = Object.keys(EIP155_CHAINS); - switch (key) { - case 'eip155': - if (accounts.ethAccounts.length > 0) { - currentAddresses = accounts.ethAccounts.map( - account => account.address, - ); - } - break; - case 'cosmos': - if (accounts.cosmosAccounts.length > 0) { - currentAddresses = accounts.cosmosAccounts.map( - account => account.address, - ); - } - break; - default: - throw new Error(`${key} not supported`); + // cosmos + const cosmosChains = Object.keys(COSMOS_MAINNET_CHAINS); + + // Set selected account as the first account in supported namespaces + const sortedAccounts = Object.entries(accounts).reduce( + (acc: AccountsState, [key, value]) => { + let newValue = [...value]; + + // TODO: Implement selectedAccount instead of currentIndex in AccountsContext + if (value.length > currentIndex) { + const currentAccount = newValue[currentIndex]; + const remainingAccounts = newValue.filter( + (_, index) => index !== currentIndex, + ); + newValue = [currentAccount, ...remainingAccounts]; } - const namespaceAccounts: string[] = []; - requiredNamespaces[key].chains!.map((chain: string) => { - currentAddresses.map(acc => - namespaceAccounts.push(`${chain}:${acc}`), - ); + acc[key as 'ethAccounts' | 'cosmosAccounts'] = newValue; + return acc; + }, + { ethAccounts: [], cosmosAccounts: [] }, + ); + + const { optionalNamespaces, requiredNamespaces } = currentProposal.params; + + return { + eip155: { + chains: eip155Chains, + // TODO: Debug optional namespace methods and events being required for approval + methods: [ + ...(optionalNamespaces.eip155?.methods ?? []), + ...(requiredNamespaces.eip155?.methods ?? []), + ], + events: [ + ...(optionalNamespaces.eip155?.events ?? []), + ...(requiredNamespaces.eip155?.events ?? []), + ], + + accounts: eip155Chains + .map(chain => + sortedAccounts.ethAccounts.map( + account => `${chain}:${account.address}`, + ), + ) + .flat(), + }, + cosmos: { + chains: cosmosChains, + methods: [ + ...(optionalNamespaces.cosmos?.methods ?? []), + ...(requiredNamespaces.cosmos?.methods ?? []), + ], + events: [ + ...(optionalNamespaces.cosmos?.events ?? []), + ...(requiredNamespaces.cosmos?.events ?? []), + ], + accounts: cosmosChains + .map(chain => + sortedAccounts.cosmosAccounts.map( + account => `${chain}:${account.address}`, + ), + ) + .flat(), + }, + }; + }, [currentIndex, accounts, currentProposal]); + + const namespaces = useMemo(() => { + return ( + currentProposal && + supportedNamespaces && + buildApprovedNamespaces({ + proposal: currentProposal.params, + supportedNamespaces, + }) + ); + }, [currentProposal, supportedNamespaces]); + + const handleAccept = async () => { + try { + if (currentProposal && namespaces) { + const { id } = currentProposal; + + await web3wallet!.approveSession({ + id, + namespaces, }); - namespaces[key] = { - accounts: namespaceAccounts, - methods: requiredNamespaces[key].methods, - events: requiredNamespaces[key].events, - }; - }); - - await web3wallet!.approveSession({ - id, - relayProtocol: relays[0].protocol, - namespaces, - }); - - const sessions = web3wallet!.getActiveSessions(); - setActiveSessions(sessions); - setModalVisible(false); - setToastVisible(true); - setCurrentProposal(undefined); - setWalletConnectData({ - walletConnectMethods: [], - walletConnectEvents: [], - walletConnectChains: [], - }); + const sessions = web3wallet!.getActiveSessions(); + setActiveSessions(sessions); + setModalVisible(false); + setToastVisible(true); + setCurrentProposal(undefined); + setWalletConnectData({ + walletConnectMethods: [], + walletConnectEvents: [], + walletConnectChains: [], + }); + } + } catch (error) { + console.error('Error in approve session:', error); + throw error; } }; @@ -216,11 +239,11 @@ const PairingModal = ({ - - diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index 7b60a3e..8e3cf39 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -5,9 +5,13 @@ import { AccountsState } from '../types'; const AccountsContext = createContext<{ accounts: AccountsState; setAccounts: (account: AccountsState) => void; + currentIndex: number; + setCurrentIndex: (index: number) => void; }>({ accounts: { ethAccounts: [], cosmosAccounts: [] }, setAccounts: () => {}, + currentIndex: 0, + setCurrentIndex: () => {}, }); const useAccounts = () => { @@ -20,8 +24,10 @@ const AccountsProvider = ({ children }: { children: any }) => { ethAccounts: [], cosmosAccounts: [], }); + const [currentIndex, setCurrentIndex] = useState(0); return ( - + {children} ); diff --git a/src/hooks/useInitialization.ts b/src/hooks/useInitialization.ts index a77129d..3759e5c 100644 --- a/src/hooks/useInitialization.ts +++ b/src/hooks/useInitialization.ts @@ -9,7 +9,7 @@ export default function useInitialization() { await createWeb3Wallet(); setInitialized(true); } catch (err: unknown) { - console.log('Error for initializing', err); + console.error('Error for initializing', err); } }, []); diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index 21e0f6f..e031604 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -1,7 +1,7 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Image, ScrollView, View } from 'react-native'; import { ActivityIndicator, Button, Text, Appbar } from 'react-native-paper'; -import { PopulatedTransaction, providers, BigNumber } from 'ethers'; +import { providers, BigNumber } from 'ethers'; import { useNavigation } from '@react-navigation/native'; import { @@ -61,16 +61,22 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { const navigation = useNavigation>(); - const retrieveData = async (requestAddress: string) => { - const requestAccount = await retrieveSingleAccount(network, requestAddress); - if (!requestAccount) { - navigation.navigate('InvalidPath'); - return; - } + const retrieveData = useCallback( + async (requestAddress: string) => { + const requestAccount = await retrieveSingleAccount( + network, + requestAddress, + ); + if (!requestAccount) { + navigation.navigate('InvalidPath'); + return; + } - setAccount(requestAccount); - setIsLoading(false); - }; + setAccount(requestAccount); + setIsLoading(false); + }, + [navigation, network], + ); const gasFees = useMemo(() => { if (network === 'eth') { @@ -87,7 +93,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { useEffect(() => { retrieveData(transaction.from!); - }, [route]); + }, [retrieveData, transaction]); const acceptRequestHandler = async () => { if (!account) { diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index ac7a234..324d9c1 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -28,7 +28,8 @@ const WCLogo = () => { }; const HomeScreen = () => { - const { accounts, setAccounts } = useAccounts(); + const { accounts, setAccounts, currentIndex, setCurrentIndex } = + useAccounts(); const { setActiveSessions } = useWalletConnect(); const navigation = @@ -54,7 +55,6 @@ const HomeScreen = () => { const [walletDialog, setWalletDialog] = useState(false); const [resetWalletDialog, setResetWalletDialog] = useState(false); const [network, setNetwork] = useState('eth'); - const [currentIndex, setCurrentIndex] = useState(0); const [isAccountsFetched, setIsAccountsFetched] = useState(false); const [phrase, setPhrase] = useState(''); diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 920ac8d..3286eb6 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Alert, Image, ScrollView, View } from 'react-native'; import { ActivityIndicator, Button, Text, Appbar } from 'react-native-paper'; @@ -60,48 +60,54 @@ const SignRequest = ({ route }: SignRequestProps) => { ); }, [route.params]); - const retrieveData = async ( - requestNetwork: string, - requestAddress: string, - requestMessage: string, - ) => { - const requestAccount = await retrieveSingleAccount( - requestNetwork, - requestAddress, - ); - if (!requestAccount) { - navigation.navigate('InvalidPath'); - return; - } + const retrieveData = useCallback( + async ( + requestNetwork: string, + requestAddress: string, + requestMessage: string, + ) => { + const requestAccount = await retrieveSingleAccount( + requestNetwork, + requestAddress, + ); + if (!requestAccount) { + navigation.navigate('InvalidPath'); + return; + } - if (requestAccount !== account) { - setAccount(requestAccount); - } - if (requestMessage !== message) { - setMessage(decodeURIComponent(requestMessage)); - } - if (requestNetwork !== network) { - setNetwork(requestNetwork); - } - setIsLoading(false); - }; + if (requestAccount !== account) { + setAccount(requestAccount); + } + if (requestMessage !== message) { + setMessage(decodeURIComponent(requestMessage)); + } + if (requestNetwork !== network) { + setNetwork(requestNetwork); + } + setIsLoading(false); + }, + [account, message, navigation, network], + ); - const sanitizePath = (path: string) => { - const regex = /^\/sign\/(eth|cosmos)\/(.+)\/(.+)$/; - const match = path.match(regex); + const sanitizePath = useCallback( + (path: string) => { + const regex = /^\/sign\/(eth|cosmos)\/(.+)\/(.+)$/; + const match = path.match(regex); - if (match) { - const [network, address, message] = match; - return { - network, - address, - message, - }; - } else { - navigation.navigate('InvalidPath'); - } - return null; - }; + if (match) { + const [network, address, message] = match; + return { + network, + address, + message, + }; + } else { + navigation.navigate('InvalidPath'); + } + return null; + }, + [navigation], + ); useEffect(() => { if (route.path) { @@ -119,7 +125,7 @@ const SignRequest = ({ route }: SignRequestProps) => { route.params.address, route.params.message, ); - }, [route]); + }, [retrieveData, sanitizePath, route]); const handleWalletConnectRequest = async () => { const { requestEvent } = route.params; diff --git a/src/screens/WalletConnect.tsx b/src/screens/WalletConnect.tsx index 9201a8c..bfc353a 100644 --- a/src/screens/WalletConnect.tsx +++ b/src/screens/WalletConnect.tsx @@ -12,7 +12,7 @@ export default function WalletConnect() { const { activeSessions, setActiveSessions } = useWalletConnect(); const disconnect = async (sessionId: string) => { - await web3wallet.disconnectSession({ + await web3wallet!.disconnectSession({ topic: sessionId, reason: getSdkError('USER_DISCONNECTED'), }); @@ -22,7 +22,7 @@ export default function WalletConnect() { }; useEffect(() => { - const sessions = web3wallet.getActiveSessions(); + const sessions = web3wallet!.getActiveSessions(); setActiveSessions(sessions); }, [setActiveSessions]); diff --git a/src/utils/wallet-connect/COSMOSData.ts b/src/utils/wallet-connect/COSMOSData.ts index 0f0cf9a..1e6101e 100644 --- a/src/utils/wallet-connect/COSMOSData.ts +++ b/src/utils/wallet-connect/COSMOSData.ts @@ -3,7 +3,7 @@ /** * Types */ -export type TCosmosChain = keyof typeof COSMOS_TESTNET_CHAINS; +export type TCosmosChain = keyof typeof COSMOS_CHAINS; /** * Chains @@ -27,11 +27,31 @@ export const COSMOS_TESTNET_CHAINS: Record< }, }; +export const COSMOS_MAINNET_CHAINS = { + 'cosmos:cosmoshub-4': { + chainId: 'cosmoshub-4', + name: 'Cosmos Hub', + logo: '/chain-logos/cosmos-cosmoshub-4.png', + rgb: '107, 111, 147', + rpc: '', + namespace: 'cosmos', + }, +}; + +export const COSMOS_CHAINS = { + ...COSMOS_MAINNET_CHAINS, + ...COSMOS_TESTNET_CHAINS, +}; + /** * Methods */ -export const COSMOS_METHODS = { +export const COSMOS_SIGNING_METHODS = { COSMOS_SIGN_DIRECT: 'cosmos_signDirect', COSMOS_SIGN_AMINO: 'cosmos_signAmino', +}; + +export const COSMOS_METHODS = { + ...COSMOS_SIGNING_METHODS, COSMOS_SEND_TOKENS: 'cosmos_sendTokens', // Added for pay.laconic.com }; diff --git a/src/utils/wallet-connect/EIP155Data.ts b/src/utils/wallet-connect/EIP155Data.ts index b47948a..eb2acd1 100644 --- a/src/utils/wallet-connect/EIP155Data.ts +++ b/src/utils/wallet-connect/EIP155Data.ts @@ -64,6 +64,16 @@ export const EIP155_MAINNET_CHAINS: Record = { rpc: 'https://mainnet.era.zksync.io/', namespace: 'eip155', }, + + // Required chain by SIWE + 'eip155:42161': { + chainId: 42161, + name: 'Arbitrum One', + logo: '', + rgb: '242, 242, 242', + rpc: 'https://arb1.arbitrum.io/rpc', + namespace: 'eip155', + }, }; export const EIP155_TEST_CHAINS: Record = { diff --git a/src/utils/wallet-connect/EIP155Lib.ts b/src/utils/wallet-connect/EIP155Lib.ts index 038f3b8..caeee02 100644 --- a/src/utils/wallet-connect/EIP155Lib.ts +++ b/src/utils/wallet-connect/EIP155Lib.ts @@ -51,97 +51,3 @@ export default class EIP155Lib { return this.wallet.signTransaction(transaction); } } - -/** - * @desc Reference list of eip155 chains - * @url https://chainlist.org - */ - -/** - * Types - */ -export type TEIP155Chain = keyof typeof EIP155_CHAINS; - -/** - * Chains - */ -export const EIP155_MAINNET_CHAINS = { - 'eip155:1': { - chainId: 1, - name: 'Ethereum', - logo: '/chain-logos/eip155-1.png', - rgb: '99, 125, 234', - rpc: 'https://cloudflare-eth.com/', - }, - 'eip155:43114': { - chainId: 43114, - name: 'Avalanche C-Chain', - logo: '/chain-logos/eip155-43113.png', - rgb: '232, 65, 66', - rpc: 'https://api.avax.network/ext/bc/C/rpc', - }, - 'eip155:137': { - chainId: 137, - name: 'Polygon', - logo: '/chain-logos/eip155-137.png', - rgb: '130, 71, 229', - rpc: 'https://polygon-rpc.com/', - }, - 'eip155:10': { - chainId: 10, - name: 'Optimism', - logo: '/chain-logos/eip155-10.png', - rgb: '235, 0, 25', - rpc: 'https://mainnet.optimism.io', - }, -}; - -export const EIP155_TEST_CHAINS = { - 'eip155:5': { - chainId: 5, - name: 'Ethereum Goerli', - logo: '/chain-logos/eip155-1.png', - rgb: '99, 125, 234', - rpc: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161', - }, - 'eip155:43113': { - chainId: 43113, - name: 'Avalanche Fuji', - logo: '/chain-logos/eip155-43113.png', - rgb: '232, 65, 66', - rpc: 'https://api.avax-test.network/ext/bc/C/rpc', - }, - 'eip155:80001': { - chainId: 80001, - name: 'Polygon Mumbai', - logo: '/chain-logos/eip155-137.png', - rgb: '130, 71, 229', - rpc: 'https://matic-mumbai.chainstacklabs.com', - }, - 'eip155:420': { - chainId: 420, - name: 'Optimism Goerli', - logo: '/chain-logos/eip155-10.png', - rgb: '235, 0, 25', - rpc: 'https://goerli.optimism.io', - }, -}; - -export const EIP155_CHAINS = { - ...EIP155_MAINNET_CHAINS, - ...EIP155_TEST_CHAINS, -}; - -/** - * Methods - */ -export const EIP155_SIGNING_METHODS = { - PERSONAL_SIGN: 'personal_sign', - ETH_SIGN: 'eth_sign', - ETH_SIGN_TRANSACTION: 'eth_signTransaction', - ETH_SIGN_TYPED_DATA: 'eth_signTypedData', - ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3', - ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4', - ETH_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction', - ETH_SEND_TRANSACTION: 'eth_sendTransaction', -}; diff --git a/src/utils/wallet-connect/Helpers.ts b/src/utils/wallet-connect/Helpers.ts index 5f777a9..1098fa5 100644 --- a/src/utils/wallet-connect/Helpers.ts +++ b/src/utils/wallet-connect/Helpers.ts @@ -3,7 +3,7 @@ import { utils } from 'ethers'; import { Account } from '../../types'; -import { EIP155_CHAINS, TEIP155Chain } from './EIP155Lib'; +import { EIP155_CHAINS, TEIP155Chain } from './EIP155Data'; /** * Truncates string (in the middle) via given lenght value diff --git a/src/utils/wallet-connect/WalletConnectRequests.ts b/src/utils/wallet-connect/WalletConnectRequests.ts index dd11741..62164c7 100644 --- a/src/utils/wallet-connect/WalletConnectRequests.ts +++ b/src/utils/wallet-connect/WalletConnectRequests.ts @@ -11,7 +11,7 @@ import { calculateFee, } from '@cosmjs/stargate'; -import { EIP155_SIGNING_METHODS } from './EIP155Lib'; +import { EIP155_SIGNING_METHODS } from './EIP155Data'; import { signDirectMessage, signEthMessage } from '../sign-message'; import { Account } from '../../types'; import { getMnemonic, getPathKey } from '../misc'; @@ -60,9 +60,7 @@ export async function approveWalletConnectRequest( } const ethSignature = await signEthMessage(message, account.counterId); - return formatJsonRpcResult(id, { - signature: ethSignature, - }); + return formatJsonRpcResult(id, ethSignature); case 'cosmos_signDirect': // Reference: https://github.com/confio/cosmjs-types/blob/66e52711914fccd2a9d1a03e392d3628fdf499e2/src/cosmos/tx/v1beta1/tx.ts#L51 diff --git a/yarn.lock b/yarn.lock index c47f594..340f828 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2814,6 +2814,11 @@ resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.2.tgz#b74129719fc8d11c01868010082d483b7545591a" integrity sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA== +"@types/lodash@^4.17.0": + version "4.17.0" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.0.tgz#d774355e41f372d5350a4d0714abb48194a489c3" + integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA== + "@types/node@*": version "20.11.16" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.16.tgz#4411f79411514eb8e2926f036c86c9f0e4ec6708" -- 2.45.2 From cc3d7f708f44a41af73dc564f5bbefd6ade453de Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Thu, 28 Mar 2024 13:36:14 +0530 Subject: [PATCH 29/64] Add spinner for tx after being approved (#67) * Add spinner for tx * Change text color --- src/screens/ApproveTransaction.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index e031604..8935c8a 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -49,6 +49,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { const [account, setAccount] = useState(); const [isLoading, setIsLoading] = useState(true); const [balance, setBalance] = useState(''); + const [isTxLoading, setIsTxLoading] = useState(false); const [cosmosStargateClient, setCosmosStargateClient] = useState(); @@ -96,6 +97,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { }, [retrieveData, transaction]); const acceptRequestHandler = async () => { + setIsTxLoading(true); if (!account) { throw new Error('account not found'); } @@ -110,6 +112,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { const { topic } = requestEvent; await web3wallet!.respondSessionRequest({ topic, response }); + setIsTxLoading(false); navigation.navigate('Laconic'); }; @@ -254,8 +257,11 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { )} - - - - + + + + + + diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 3286eb6..5fe8ad5 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -128,7 +128,8 @@ const SignRequest = ({ route }: SignRequestProps) => { }, [retrieveData, sanitizePath, route]); const handleWalletConnectRequest = async () => { - const { requestEvent } = route.params; + setIsLoading(true) + const { requestEvent } = route.params || {}; if (!account) { throw new Error('account not found'); @@ -147,6 +148,7 @@ const SignRequest = ({ route }: SignRequestProps) => { const { topic } = requestEvent; await web3wallet!.respondSessionRequest({ topic, response }); + setIsLoading(false) }; const handleIntent = async () => { @@ -174,13 +176,15 @@ const SignRequest = ({ route }: SignRequestProps) => { }; const rejectRequestHandler = async () => { - if (route.params.requestEvent) { - const response = rejectWalletConnectRequest(route.params.requestEvent); - const { topic } = route.params.requestEvent; + if (route.params?.requestEvent) { + setIsLoading(true); + const response = rejectWalletConnectRequest(route.params?.requestEvent); + const { topic } = route.params?.requestEvent; await web3wallet!.respondSessionRequest({ topic, response, }); + setIsLoading(true); } navigation.navigate('Laconic'); }; diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index 0c56df5..47c387f 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -137,6 +137,8 @@ const styles = StyleSheet.create({ buttonContainer: { flexDirection: 'row', marginLeft: 20, + marginTop: 20, + marginBottom: 20, justifyContent: 'space-evenly', }, badRequestContainer: { @@ -155,6 +157,7 @@ const styles = StyleSheet.create({ alignItems: 'center', justifyContent: 'center', marginBottom: 10, + paddingHorizontal: 20, }, modalContentContainer: { display: 'flex', @@ -186,6 +189,7 @@ const styles = StyleSheet.create({ alignItems: 'center', marginTop: 20, paddingHorizontal: 16, + marginBottom: 10, }, marginVertical8: { marginVertical: 8, -- 2.45.2 From ac18a43fab3150f6213439d2786b45c73cda545c Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Thu, 28 Mar 2024 16:51:58 +0530 Subject: [PATCH 32/64] Disable button for approve tx (#70) --- src/screens/ApproveTransaction.tsx | 3 ++- src/styles/stylesheet.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index 8935c8a..5eac3b0 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -260,7 +260,8 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { - + )} ); diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index a109018..509deb6 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -221,38 +221,39 @@ const SignRequest = ({ route }: SignRequestProps) => { ) : ( - - - {requestIcon && ( - <> - {requestIcon.endsWith('.svg') ? ( - - - - ) : ( - - )} - - )} - {requestName} - {requestURL} - - - {isCosmosSignDirect || isEthSendTransaction ? ( - - + <> + + + {requestIcon && ( + <> + {requestIcon.endsWith('.svg') ? ( + + + + ) : ( + + )} + + )} + {requestName} + {requestURL} + + + {isCosmosSignDirect || isEthSendTransaction ? ( + + + {message} + + + ) : ( + {message} - - - ) : ( - - {message} - - )} - + + )} + - + )} ); diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index 6052bb6..eaf2084 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -125,21 +125,21 @@ const styles = StyleSheet.create({ requestDirectMessage: { borderWidth: 1, borderRadius: 5, - marginTop: 50, - height: '40%', + marginTop: 20, + marginBottom: 50, + height: 500, alignItems: 'center', justifyContent: 'center', padding: 8, }, approveTransaction: { height: '40%', + marginBottom: 30, }, - // TODO: Fix button position buttonContainer: { flexDirection: 'row', marginLeft: 20, - marginTop: 20, - marginBottom: 150, + marginBottom: 10, justifyContent: 'space-evenly', }, badRequestContainer: { -- 2.45.2 From 703ea72c1fe7971c722ef55bf94b90f50afdab4e Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Tue, 2 Apr 2024 19:37:40 +0530 Subject: [PATCH 35/64] Add functionality to configure EVM networks (#74) * Configure EVM networks * Display added EVM networks in network drop down * Add network for configured networks --- package.json | 1 + src/App.tsx | 8 ++ src/components/Accounts.tsx | 13 +++ src/components/NetworkDropdown.tsx | 29 ++++-- src/context/AccountsContext.tsx | 23 ++++- src/screens/AddNetwork.tsx | 146 +++++++++++++++++++++++++++++ src/screens/ApproveTransaction.tsx | 16 +++- src/screens/HomeScreen.tsx | 19 ++-- src/types.ts | 14 +++ yarn.lock | 5 + 10 files changed, 255 insertions(+), 19 deletions(-) create mode 100644 src/screens/AddNetwork.tsx diff --git a/package.json b/package.json index efa41e9..17228df 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "patch-package": "^8.0.0", "postinstall-postinstall": "^2.1.0", "react": "18.2.0", + "react-hook-form": "^7.51.2", "react-native": "0.73.3", "react-native-config": "^1.5.1", "react-native-get-random-values": "^1.10.0", diff --git a/src/App.tsx b/src/App.tsx index d62e52f..cd26e38 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -26,6 +26,7 @@ import { web3wallet } from './utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; import ApproveTransaction from './screens/ApproveTransaction'; +import AddNetwork from './screens/AddNetwork'; const Stack = createNativeStackNavigator(); @@ -220,6 +221,13 @@ const App = (): React.JSX.Element => { title: 'Approve transaction', }} /> + + + + { + navigation.navigate('AddNetwork'); + }}> + + Add Network + + + ); diff --git a/src/components/NetworkDropdown.tsx b/src/components/NetworkDropdown.tsx index 320eaae..c6a76b8 100644 --- a/src/components/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown.tsx @@ -1,14 +1,34 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { View } from 'react-native'; import { List } from 'react-native-paper'; import { NetworkDropdownProps } from '../types'; import styles from '../styles/stylesheet'; +import { useAccounts } from '../context/AccountsContext'; const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { const [expanded, setExpanded] = useState(false); const [selectedNetwork, setSelectedNetwork] = useState('Ethereum'); + const { networksData } = useAccounts(); + + const networks = useMemo(() => { + const defaultNetworks = [ + { value: 'eth', chainId: 'eip155:1', displayName: 'Ethereum' }, + { value: 'cosmos', chainId: 'cosmos:cosmoshub-4', displayName: 'Cosmos' }, + ]; + + networksData.forEach(network => { + defaultNetworks.push({ + value: network.networkType, + chainId: network.chainId, + displayName: network.networkName, + }); + }); + + return defaultNetworks; + }, [networksData]); + const handleNetworkPress = (network: string, displayName: string) => { updateNetwork(network); setSelectedNetwork(displayName); @@ -23,7 +43,7 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { onPress={() => setExpanded(!expanded)}> {networks.map(network => ( handleNetworkPress(network.value, network.displayName) @@ -35,9 +55,4 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { ); }; -const networks = [ - { value: 'eth', displayName: 'Ethereum' }, - { value: 'cosmos', displayName: 'Cosmos' }, -]; - export { NetworkDropdown }; diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index 8e3cf39..1acb946 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -1,17 +1,25 @@ import React, { createContext, useContext, useState } from 'react'; -import { AccountsState } from '../types'; +import { AccountsState, NetworksDataState } from '../types'; const AccountsContext = createContext<{ accounts: AccountsState; setAccounts: (account: AccountsState) => void; currentIndex: number; setCurrentIndex: (index: number) => void; + networksData: NetworksDataState[]; + setNetworksData: (networksDataArray: NetworksDataState[]) => void; + networkType: string; + setNetworkType: (networkType: string) => void; }>({ accounts: { ethAccounts: [], cosmosAccounts: [] }, setAccounts: () => {}, currentIndex: 0, setCurrentIndex: () => {}, + networksData: [], + setNetworksData: () => {}, + networkType: '', + setNetworkType: () => {}, }); const useAccounts = () => { @@ -24,10 +32,21 @@ const AccountsProvider = ({ children }: { children: any }) => { ethAccounts: [], cosmosAccounts: [], }); + const [networksData, setNetworksData] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); + const [networkType, setNetworkType] = useState('eth'); return ( + value={{ + accounts, + setAccounts, + currentIndex, + setCurrentIndex, + networksData, + setNetworksData, + networkType, + setNetworkType, + }}> {children} ); diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx new file mode 100644 index 0000000..56b41c3 --- /dev/null +++ b/src/screens/AddNetwork.tsx @@ -0,0 +1,146 @@ +import React from 'react'; +import { View } from 'react-native'; +import { useForm, Controller } from 'react-hook-form'; +import { TextInput, Button, HelperText } from 'react-native-paper'; + +import styles from '../styles/stylesheet'; +import { NetworksDataState, StackParamsList } from '../types'; +import { useAccounts } from '../context/AccountsContext'; +import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useNavigation } from '@react-navigation/native'; + +// TODO: Add validation to form inputs +const AddNetwork = () => { + const navigation = + useNavigation>(); + + const { + control, + formState: { errors, isValid }, + handleSubmit, + } = useForm({ + mode: 'onChange', + }); + + const { networksData, setNetworksData } = useAccounts(); + + const submit = (data: NetworksDataState) => { + setNetworksData([...networksData, data]); + navigation.navigate('Laconic'); + }; + return ( + + ( + <> + onChange(value)} + /> + {errors.networkName?.message} + + )} + /> + ( + <> + onChange(value)} + /> + {errors.rpcUrl?.message} + + )} + /> + ( + <> + onChange(value)} + /> + {errors.chainId?.message} + + )} + /> + ( + <> + onChange(value)} + /> + + )} + /> + {errors.currencySymbol?.message} + ( + <> + onChange(value)} + /> + + )} + /> + {errors.blockExplorerUrl?.message} + ( + <> + + + )} + /> + {errors.blockExplorerUrl?.message} + + + ); +}; + +export default AddNetwork; diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index a828806..66f478f 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -25,11 +25,11 @@ import { rejectWalletConnectRequest, } from '../utils/wallet-connect/WalletConnectRequests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; -import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data'; import DataBox from '../components/DataBox'; import { getPathKey } from '../utils/misc'; import { COSMOS_TESTNET_CHAINS } from '../utils/wallet-connect/COSMOSData'; import { COSMOS_DENOM } from '../utils/constants'; +import { useAccounts } from '../context/AccountsContext'; type SignRequestProps = NativeStackScreenProps< StackParamsList, @@ -37,6 +37,8 @@ type SignRequestProps = NativeStackScreenProps< >; const ApproveTransaction = ({ route }: SignRequestProps) => { + const { networksData } = useAccounts(); + const requestSession = route.params.requestSessionData; const requestName = requestSession.peer.metadata.name; const requestIcon = requestSession.peer.metadata.icons[0]; @@ -55,9 +57,17 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { const provider = useMemo(() => { if (network === 'eth') { - return new providers.JsonRpcProvider(EIP155_CHAINS[chainId].rpc); + const currentChain = networksData.find( + networkData => networkData.chainId === chainId, + ); + + if (!currentChain) { + throw new Error('Requested chain not supported'); + } + + return new providers.JsonRpcProvider(currentChain.rpcUrl); } - }, [chainId, network]); + }, [chainId, network, networksData]); const navigation = useNavigation>(); diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 324d9c1..1b81461 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -28,8 +28,14 @@ const WCLogo = () => { }; const HomeScreen = () => { - const { accounts, setAccounts, currentIndex, setCurrentIndex } = - useAccounts(); + const { + accounts, + setAccounts, + currentIndex, + setCurrentIndex, + networkType, + setNetworkType, + } = useAccounts(); const { setActiveSessions } = useWalletConnect(); const navigation = @@ -54,7 +60,6 @@ const HomeScreen = () => { const [isWalletCreating, setIsWalletCreating] = useState(false); const [walletDialog, setWalletDialog] = useState(false); const [resetWalletDialog, setResetWalletDialog] = useState(false); - const [network, setNetwork] = useState('eth'); const [isAccountsFetched, setIsAccountsFetched] = useState(false); const [phrase, setPhrase] = useState(''); @@ -95,11 +100,11 @@ const HomeScreen = () => { setActiveSessions({}); hideResetDialog(); - setNetwork('eth'); + setNetworkType('eth'); }; const updateNetwork = (newNetwork: string) => { - setNetwork(newNetwork); + setNetworkType(newNetwork); setCurrentIndex(0); }; @@ -140,12 +145,12 @@ const HomeScreen = () => { ) : isWalletCreated ? ( <> diff --git a/src/types.ts b/src/types.ts index ec1b2e3..87734ad 100644 --- a/src/types.ts +++ b/src/types.ts @@ -22,6 +22,7 @@ export type StackParamsList = { InvalidPath: undefined; WalletConnect: undefined; AddSession: undefined; + AddNetwork: undefined; }; export type Account = { @@ -46,6 +47,10 @@ export type AccountsProps = { export type NetworkDropdownProps = { selectedNetwork: string; updateNetwork: (network: string) => void; + customNetwork?: { + value: string; + displayName: string; + }; }; export type AccountsState = { @@ -53,6 +58,15 @@ export type AccountsState = { cosmosAccounts: Account[]; }; +export type NetworksDataState = { + networkName: string; + rpcUrl: string; + chainId: string; + currencySymbol: string; + blockExplorerUrl?: string; + networkType: string; +}; + export type SignMessageParams = { message: string; network: string; diff --git a/yarn.lock b/yarn.lock index 4f584f4..6ccbc45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7803,6 +7803,11 @@ react-freeze@^1.0.0: resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.3.tgz#5e3ca90e682fed1d73a7cb50c2c7402b3e85618d" integrity sha512-ZnXwLQnGzrDpHBHiC56TXFXvmolPeMjTn1UOm610M4EXGzbEDR7oOIyS2ZiItgbs6eZc4oU/a0hpk8PrcKvv5g== +react-hook-form@^7.51.2: + version "7.51.2" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.51.2.tgz#79f7f72ee217c5114ff831012d1a7ec344096e7f" + integrity sha512-y++lwaWjtzDt/XNnyGDQy6goHskFualmDlf+jzEZvjvz6KWDf7EboL7pUvRCzPTJd0EOPpdekYaQLEvvG6m6HA== + "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" -- 2.45.2 From 2e18397a95adf2ad7779b9162156caa2dc65826e Mon Sep 17 00:00:00 2001 From: Adwait Gharpure <69599306+Adw8@users.noreply.github.com> Date: Wed, 3 Apr 2024 11:36:46 +0530 Subject: [PATCH 36/64] Navigate to homescreen on approving sign request (#75) * Navigate to homescreen on approving sign request * Add spinner to button on approve --------- Co-authored-by: Adw8 --- src/screens/SignRequest.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 509deb6..68b621d 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -34,6 +34,7 @@ const SignRequest = ({ route }: SignRequestProps) => { const [message, setMessage] = useState(''); const [network, setNetwork] = useState(''); const [isLoading, setIsLoading] = useState(true); + const [isApproving, setIsApproving] = useState(false); const navigation = useNavigation>(); @@ -129,7 +130,6 @@ const SignRequest = ({ route }: SignRequestProps) => { }, [retrieveData, sanitizePath, route]); const handleWalletConnectRequest = async () => { - setIsLoading(true); const { requestEvent } = route.params || {}; if (!account) { @@ -149,7 +149,6 @@ const SignRequest = ({ route }: SignRequestProps) => { const { topic } = requestEvent; await web3wallet!.respondSessionRequest({ topic, response }); - setIsLoading(false); }; const handleIntent = async () => { @@ -167,25 +166,25 @@ const SignRequest = ({ route }: SignRequestProps) => { }; const signMessageHandler = async () => { + setIsApproving(true); if (route.params.requestEvent) { await handleWalletConnectRequest(); } else { await handleIntent(); } + setIsApproving(false); navigation.navigate('Laconic'); }; const rejectRequestHandler = async () => { if (route.params?.requestEvent) { - setIsLoading(true); const response = rejectWalletConnectRequest(route.params?.requestEvent); const { topic } = route.params?.requestEvent; await web3wallet!.respondSessionRequest({ topic, response, }); - setIsLoading(true); } navigation.navigate('Laconic'); }; @@ -255,7 +254,10 @@ const SignRequest = ({ route }: SignRequestProps) => { )} - - + ); }; diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index 66f478f..427a95e 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -55,19 +55,19 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { const [cosmosStargateClient, setCosmosStargateClient] = useState(); + const requestedChain = networksData.find( + networkData => networkData.chainId === chainId, + ); + const provider = useMemo(() => { if (network === 'eth') { - const currentChain = networksData.find( - networkData => networkData.chainId === chainId, - ); - - if (!currentChain) { + if (!requestedChain) { throw new Error('Requested chain not supported'); } - return new providers.JsonRpcProvider(currentChain.rpcUrl); + return new providers.JsonRpcProvider(requestedChain.rpcUrl); } - }, [chainId, network, networksData]); + }, [requestedChain, network]); const navigation = useNavigation>(); @@ -77,6 +77,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { const requestAccount = await retrieveSingleAccount( network, requestAddress, + requestedChain?.addressPrefix, ); if (!requestAccount) { navigation.navigate('InvalidPath'); @@ -86,7 +87,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { setAccount(requestAccount); setIsLoading(false); }, - [navigation, network], + [navigation, network, requestedChain], ); const gasFees = useMemo(() => { @@ -199,6 +200,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { const sender = await DirectSecp256k1Wallet.fromKey( Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'), + requestedChain?.addressPrefix, ); const client = await SigningStargateClient.connectWithSigner( @@ -210,7 +212,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { }; setClient(); - }, [account, chainId, network]); + }, [account, requestedChain, chainId, network]); return ( <> diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 1b81461..02e68c7 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -15,7 +15,7 @@ import ResetWalletDialog from '../components/ResetWalletDialog'; import styles from '../styles/stylesheet'; import { useAccounts } from '../context/AccountsContext'; import { useWalletConnect } from '../context/WalletConnectContext'; -import { StackParamsList } from '../types'; +import { NetworksDataState, StackParamsList } from '../types'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; const WCLogo = () => { @@ -35,6 +35,7 @@ const HomeScreen = () => { setCurrentIndex, networkType, setNetworkType, + networksData, } = useAccounts(); const { setActiveSessions } = useWalletConnect(); @@ -59,6 +60,9 @@ const HomeScreen = () => { const [isWalletCreated, setIsWalletCreated] = useState(false); const [isWalletCreating, setIsWalletCreating] = useState(false); const [walletDialog, setWalletDialog] = useState(false); + const [currentChainId, setCurrentChainId] = useState( + networksData[0].chainId, + ); const [resetWalletDialog, setResetWalletDialog] = useState(false); const [isAccountsFetched, setIsAccountsFetched] = useState(false); const [phrase, setPhrase] = useState(''); @@ -103,8 +107,9 @@ const HomeScreen = () => { setNetworkType('eth'); }; - const updateNetwork = (newNetwork: string) => { - setNetworkType(newNetwork); + const updateNetwork = (networksData: NetworksDataState) => { + setNetworkType(networksData.networkType); + setCurrentChainId(networksData.chainId); setCurrentIndex(0); }; @@ -114,18 +119,25 @@ const HomeScreen = () => { useEffect(() => { const fetchAccounts = async () => { - if (isAccountsFetched) { + if (!currentChainId) { return; } - const { ethLoadedAccounts, cosmosLoadedAccounts } = - await retrieveAccounts(); - if (ethLoadedAccounts && cosmosLoadedAccounts) { + const currentNetwork = networksData.find( + networkData => networkData.chainId === currentChainId, + ); + if (!currentNetwork) { + throw new Error('Current Network not found'); + } + + const { ethLoadedAccounts, cosmosLoadedAccounts } = + await retrieveAccounts(currentNetwork.addressPrefix); + + if (cosmosLoadedAccounts && ethLoadedAccounts) { setAccounts({ ethAccounts: ethLoadedAccounts, cosmosAccounts: cosmosLoadedAccounts, }); - setIsWalletCreated(true); } @@ -133,7 +145,7 @@ const HomeScreen = () => { }; fetchAccounts(); - }, [isAccountsFetched, setAccounts]); + }, [currentChainId, networksData, setAccounts]); return ( @@ -144,10 +156,7 @@ const HomeScreen = () => { ) : isWalletCreated ? ( <> - + ; const SignRequest = ({ route }: SignRequestProps) => { + const { networksData } = useAccounts(); + const requestSession = route.params.requestSessionData; const requestName = requestSession?.peer?.metadata?.name; const requestIcon = requestSession?.peer?.metadata?.icons[0]; @@ -35,6 +38,7 @@ const SignRequest = ({ route }: SignRequestProps) => { const [network, setNetwork] = useState(''); const [isLoading, setIsLoading] = useState(true); const [isApproving, setIsApproving] = useState(false); + const [isRejecting, setIsRejecting] = useState(false); const navigation = useNavigation>(); @@ -68,9 +72,16 @@ const SignRequest = ({ route }: SignRequestProps) => { requestAddress: string, requestMessage: string, ) => { + const currentChain = networksData.find(networkData => { + if (networkData.addressPrefix) { + return requestAddress.includes(networkData.addressPrefix); + } + }); + const requestAccount = await retrieveSingleAccount( requestNetwork, requestAddress, + currentChain?.addressPrefix, ); if (!requestAccount) { navigation.navigate('InvalidPath'); @@ -88,7 +99,7 @@ const SignRequest = ({ route }: SignRequestProps) => { } setIsLoading(false); }, - [account, message, navigation, network], + [account, message, navigation, network, networksData], ); const sanitizePath = useCallback( @@ -178,6 +189,7 @@ const SignRequest = ({ route }: SignRequestProps) => { }; const rejectRequestHandler = async () => { + setIsRejecting(true); if (route.params?.requestEvent) { const response = rejectWalletConnectRequest(route.params?.requestEvent); const { topic } = route.params?.requestEvent; @@ -186,6 +198,8 @@ const SignRequest = ({ route }: SignRequestProps) => { response, }); } + + setIsRejecting(false); navigation.navigate('Laconic'); }; @@ -263,6 +277,7 @@ const SignRequest = ({ route }: SignRequestProps) => { diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index eaf2084..d2fa756 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -48,6 +48,10 @@ const styles = StyleSheet.create({ signPage: { paddingHorizontal: 24, }, + addNetwork: { + paddingHorizontal: 24, + marginTop: 30, + }, accountInfo: { marginTop: 12, marginBottom: 30, diff --git a/src/types.ts b/src/types.ts index 87734ad..2f17e30 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,12 +45,7 @@ export type AccountsProps = { }; export type NetworkDropdownProps = { - selectedNetwork: string; - updateNetwork: (network: string) => void; - customNetwork?: { - value: string; - displayName: string; - }; + updateNetwork: (networksData: NetworksDataState) => void; }; export type AccountsState = { @@ -62,9 +57,12 @@ export type NetworksDataState = { networkName: string; rpcUrl: string; chainId: string; - currencySymbol: string; + currencySymbol?: string; blockExplorerUrl?: string; networkType: string; + nativeDenom?: string; + addressPrefix?: string; + coinType?: string; }; export type SignMessageParams = { diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 3d3213b..7bdf899 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -124,16 +124,17 @@ const addAccountFromHDPath = async ( } }; -const retrieveAccountsForNetwork = async ( +export const retrieveAccountsForNetwork = async ( network: string, - count: string, + accountsIndices: string, + prefix: string = 'cosmos', ): Promise => { - const elementsArray = count.split(','); + const accountsIndexArray = accountsIndices.split(','); const loadedAccounts = await Promise.all( - elementsArray.map(async i => { - const pubKey = (await getPathKey(network, Number(i))).pubKey; - const address = (await getPathKey(network, Number(i))).address; + accountsIndexArray.map(async i => { + const pubKey = (await getPathKey(network, Number(i), prefix)).pubKey; + const address = (await getPathKey(network, Number(i), prefix)).address; const path = (await getPathKey(network, Number(i))).path; const hdPath = getHDPath(network, path); @@ -150,7 +151,9 @@ const retrieveAccountsForNetwork = async ( return loadedAccounts; }; -const retrieveAccounts = async (): Promise<{ +const retrieveAccounts = async ( + prefix: string = 'cosmos', +): Promise<{ ethLoadedAccounts?: Account[]; cosmosLoadedAccounts?: Account[]; }> => { @@ -163,13 +166,17 @@ const retrieveAccounts = async (): Promise<{ ? await retrieveAccountsForNetwork('eth', ethCounter) : undefined; const cosmosLoadedAccounts = cosmosCounter - ? await retrieveAccountsForNetwork('cosmos', cosmosCounter) + ? await retrieveAccountsForNetwork('cosmos', cosmosCounter, prefix) : undefined; return { ethLoadedAccounts, cosmosLoadedAccounts }; }; -const retrieveSingleAccount = async (network: string, address: string) => { +const retrieveSingleAccount = async ( + network: string, + address: string, + prefix: string = 'cosmos', +) => { let loadedAccounts; switch (network) { @@ -190,6 +197,7 @@ const retrieveSingleAccount = async (network: string, address: string) => { loadedAccounts = await retrieveAccountsForNetwork( network, cosmosCounter, + prefix, ); } break; @@ -228,6 +236,7 @@ const accountInfoFromHDPath = async ( | { privKey: string; pubKey: string; address: string; network: string } | undefined > => { + // TODO: move HDNode inside eth switch case const mnemonicStore = await getInternetCredentials('mnemonicServer'); if (!mnemonicStore) { throw new Error('Mnemonic not found!'); diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 801904f..c8cf406 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -10,7 +10,7 @@ import { setInternetCredentials, } from 'react-native-keychain'; -import { AccountData } from '@cosmjs/amino'; +import { AccountData, Secp256k1Wallet } from '@cosmjs/amino'; import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing'; import { stringToPath } from '@cosmjs/crypto'; @@ -44,6 +44,7 @@ export const getDirectWallet = async ( const getPathKey = async ( network: string, accountId: number, + prefix: string = 'cosmos', ): Promise<{ path: string; privKey: string; @@ -62,8 +63,23 @@ const getPathKey = async ( const pathkey = pathKeyVal.split(','); const path = pathkey[0]; const privKey = pathkey[1]; - const pubKey = pathkey[2]; - const address = pathkey[3]; + + let pubKey: string; + let address: string; + + if (network === 'eth') { + pubKey = pathkey[2]; + address = pathkey[3]; + } else { + // TODO: Store pubkey and address for cosmos instead of deriving + const wallet = await Secp256k1Wallet.fromKey( + Uint8Array.from(Buffer.from(privKey.split('0x')[1], 'hex')), + prefix, + ); + const currAccount = await wallet.getAccounts(); + pubKey = '0x' + Buffer.from(currAccount[0].pubkey).toString('hex'); + address = currAccount[0].address; + } return { path, privKey, pubKey, address }; }; -- 2.45.2 From 23fa5415aedd7ce0b9c4ac9e4bcad8078de53de0 Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Mon, 8 Apr 2024 14:34:47 +0530 Subject: [PATCH 39/64] Update namespace after adding network (#81) * Update namespace * Add eth accounts * Add isSubmitted * Send namespace to dApp based on selected network while pairing * Send transactions on configured chain * Fix modal UI * Use namespace by default for chainId * Use ethereum mainnet as default chain * Use method names from constants file * Combine required and optional namespaces * Fix chainId * Fix networksData * Remove cosmos denom * Remove todo * Use lowercase denom --------- Co-authored-by: Shreerang Kale --- src/components/Accounts.tsx | 104 +++++++---- src/components/PairingModal.tsx | 168 ++++++++++-------- src/context/AccountsContext.tsx | 19 +- src/screens/AddNetwork.tsx | 6 +- src/screens/ApproveTransaction.tsx | 21 +-- src/screens/HomeScreen.tsx | 5 +- src/utils/constants.ts | 1 - src/utils/wallet-connect/EIP155Data.ts | 6 - .../wallet-connect/WalletConnectRequests.ts | 13 +- 9 files changed, 202 insertions(+), 141 deletions(-) delete mode 100644 src/utils/constants.ts diff --git a/src/components/Accounts.tsx b/src/components/Accounts.tsx index 0139ff6..13cb6fd 100644 --- a/src/components/Accounts.tsx +++ b/src/components/Accounts.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import { ScrollView, TouchableOpacity, View } from 'react-native'; import { Button, List, Text, useTheme } from 'react-native-paper'; +import mergeWith from 'lodash/mergeWith'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; @@ -12,7 +13,8 @@ import HDPathDialog from './HDPathDialog'; import AccountDetails from './AccountDetails'; import { useAccounts } from '../context/AccountsContext'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; -import { usePrevious } from '../hooks/usePrevious'; +import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; +import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData'; const Accounts = ({ network, @@ -22,9 +24,7 @@ const Accounts = ({ const navigation = useNavigation>(); - const { accounts, setAccounts } = useAccounts(); - const prevEthAccountsRef = usePrevious(accounts.ethAccounts); - const prevCosmosAccountsRef = usePrevious(accounts.cosmosAccounts); + const { accounts, setAccounts, networksData, currentChainId } = useAccounts(); const [expanded, setExpanded] = useState(false); const [isAccountCreating, setIsAccountCreating] = useState(false); const [hdDialog, setHdDialog] = useState(false); @@ -59,40 +59,76 @@ const Accounts = ({ for (const topic in sessions) { const session = sessions[topic]; - const requiredNamespaces = session.requiredNamespaces; - const namespaces = session.namespaces; + const combinedNamespaces = mergeWith( + session.requiredNamespaces, + session.optionalNamespaces, + (obj, src) => + Array.isArray(obj) && Array.isArray(src) + ? [...src, ...obj] + : undefined, + ); - // Check if EIP155 namespace exists and Ethereum accounts have changed - if ( - namespaces.hasOwnProperty('eip155') && - prevEthAccountsRef !== accounts.ethAccounts - ) { - // Iterate through each chain ID in required EIP155 namespaces - requiredNamespaces.eip155.chains?.forEach(chainId => { - // Update Ethereum accounts in namespaces with chain prefix - namespaces.eip155.accounts = accounts.ethAccounts.map( - ethAccount => `${chainId}:${ethAccount.address}`, - ); - }); - // update session with modified namespace - await web3wallet!.updateSession({ topic, namespaces }); + const currentNetwork = networksData.find( + networkData => networkData.chainId === currentChainId, + ); + + let updatedNamespaces; + switch (currentNetwork?.networkType) { + case 'eth': + updatedNamespaces = { + eip155: { + chains: [currentNetwork.chainId], + // TODO: Debug optional namespace methods and events being required for approval + methods: [ + ...Object.values(EIP155_SIGNING_METHODS), + ...(combinedNamespaces.eip155?.methods ?? []), + ], + events: [...(combinedNamespaces.eip155?.events ?? [])], + accounts: accounts.ethAccounts.map(ethAccount => { + return `${currentChainId}:${ethAccount.address}`; + }), + }, + cosmos: { + chains: [], + methods: [], + events: [], + accounts: [], + }, + }; + break; + case 'cosmos': + updatedNamespaces = { + cosmos: { + chains: [currentNetwork.chainId], + methods: [ + ...Object.values(COSMOS_METHODS), + ...(combinedNamespaces.cosmos?.methods ?? []), + ], + events: [...(combinedNamespaces.cosmos?.events ?? [])], + accounts: accounts.cosmosAccounts.map(cosmosAccount => { + return `${currentChainId}:${cosmosAccount.address}`; + }), + }, + eip155: { + chains: [], + methods: [], + events: [], + accounts: [], + }, + }; + break; + default: + break; } - // Check if Cosmos namespace exists and Cosmos accounts have changed - if ( - namespaces.hasOwnProperty('cosmos') && - prevCosmosAccountsRef !== accounts.cosmosAccounts - ) { - // Iterate through each chain ID in required Cosmos namespaces - requiredNamespaces?.cosmos.chains?.forEach(chainId => { - // Iterate through each chain ID in required Cosmos namespaces - namespaces.cosmos.accounts = accounts.cosmosAccounts.map( - cosmosAccount => `${chainId}:${cosmosAccount.address}`, - ); - }); - // update session with modified namespace - await web3wallet!.updateSession({ topic, namespaces }); + if (!updatedNamespaces) { + return; } + + await web3wallet!.updateSession({ + topic, + namespaces: updatedNamespaces, + }); } }; // Call the updateSessions function when the 'accounts' dependency changes diff --git a/src/components/PairingModal.tsx b/src/components/PairingModal.tsx index fe3ea6b..fdf849c 100644 --- a/src/components/PairingModal.tsx +++ b/src/components/PairingModal.tsx @@ -11,8 +11,8 @@ import styles from '../styles/stylesheet'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { useAccounts } from '../context/AccountsContext'; import { useWalletConnect } from '../context/WalletConnectContext'; -import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data'; -import { COSMOS_CHAINS } from '../utils/wallet-connect/COSMOSData'; +import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; +import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData'; const PairingModal = ({ visible, @@ -21,7 +21,8 @@ const PairingModal = ({ setModalVisible, setToastVisible, }: PairingModalProps) => { - const { accounts, currentIndex } = useAccounts(); + const { accounts, networksData, currentChainId, currentIndex } = + useAccounts(); const [isLoading, setIsLoading] = useState(false); const dappName = currentProposal?.params?.proposer?.metadata.name; @@ -57,6 +58,7 @@ const PairingModal = ({ (obj, src) => Array.isArray(obj) && Array.isArray(src) ? [...src, ...obj] : undefined, ); + Object.keys(combinedNamespaces).forEach(key => { const { methods, events, chains } = combinedNamespaces[key]; @@ -79,12 +81,6 @@ const PairingModal = ({ return; } - // eip155 - const eip155Chains = Object.keys(EIP155_CHAINS); - - // cosmos - const cosmosChains = Object.keys(COSMOS_CHAINS); - // Set selected account as the first account in supported namespaces const sortedAccounts = Object.entries(accounts).reduce( (acc: AccountsState, [key, value]) => { @@ -105,49 +101,67 @@ const PairingModal = ({ { ethAccounts: [], cosmosAccounts: [] }, ); + const currentNetwork = networksData.find( + networkData => networkData.chainId === currentChainId, + ); + const { optionalNamespaces, requiredNamespaces } = currentProposal.params; - return { - eip155: { - chains: eip155Chains, - // TODO: Debug optional namespace methods and events being required for approval - methods: [ - ...(optionalNamespaces.eip155?.methods ?? []), - ...(requiredNamespaces.eip155?.methods ?? []), - ], - events: [ - ...(optionalNamespaces.eip155?.events ?? []), - ...(requiredNamespaces.eip155?.events ?? []), - ], - - accounts: eip155Chains - .map(chain => - sortedAccounts.ethAccounts.map( - account => `${chain}:${account.address}`, - ), - ) - .flat(), - }, - cosmos: { - chains: cosmosChains, - methods: [ - ...(optionalNamespaces.cosmos?.methods ?? []), - ...(requiredNamespaces.cosmos?.methods ?? []), - ], - events: [ - ...(optionalNamespaces.cosmos?.events ?? []), - ...(requiredNamespaces.cosmos?.events ?? []), - ], - accounts: cosmosChains - .map(chain => - sortedAccounts.cosmosAccounts.map( - account => `${chain}:${account.address}`, - ), - ) - .flat(), - }, - }; - }, [currentIndex, accounts, currentProposal]); + // TODO: Check with other dApps + switch (currentNetwork?.networkType) { + case 'eth': + return { + eip155: { + chains: [currentNetwork.chainId], + // TODO: Debug optional namespace methods and events being required for approval + methods: [ + ...Object.values(EIP155_SIGNING_METHODS), + ...(optionalNamespaces.eip155?.methods ?? []), + ...(requiredNamespaces.eip155?.methods ?? []), + ], + events: [ + ...(optionalNamespaces.eip155?.events ?? []), + ...(requiredNamespaces.eip155?.events ?? []), + ], + accounts: sortedAccounts.ethAccounts.map(ethAccount => { + return `${currentChainId}:${ethAccount.address}`; + }), + }, + cosmos: { + chains: [], + methods: [], + events: [], + accounts: [], + }, + }; + case 'cosmos': + return { + cosmos: { + chains: [currentNetwork.chainId], + methods: [ + ...Object.values(COSMOS_METHODS), + ...(optionalNamespaces.cosmos?.methods ?? []), + ...(requiredNamespaces.cosmos?.methods ?? []), + ], + events: [ + ...(optionalNamespaces.cosmos?.events ?? []), + ...(requiredNamespaces.cosmos?.events ?? []), + ], + accounts: sortedAccounts.cosmosAccounts.map(cosmosAccount => { + return `${currentChainId}:${cosmosAccount.address}`; + }), + }, + eip155: { + chains: [], + methods: [], + events: [], + accounts: [], + }, + }; + default: + break; + } + }, [accounts, currentProposal, networksData, currentChainId, currentIndex]); const namespaces = useMemo(() => { return ( @@ -229,29 +243,39 @@ const PairingModal = ({ {url} Connect to this site? - Chains: - {walletConnectData.walletConnectChains.map(chain => ( - - {chain} - - ))} - - Methods Requested: - {walletConnectData.walletConnectMethods.map(method => ( - - {method} - - ))} - - - Events Requested: - {walletConnectData.walletConnectEvents.map(event => ( - - {event} - - ))} - + {walletConnectData.walletConnectMethods.length > 0 && ( + + Chains: + {walletConnectData.walletConnectChains.map(chain => ( + + {chain} + + ))} + + )} + + {walletConnectData.walletConnectMethods.length > 0 && ( + + Methods Requested: + {walletConnectData.walletConnectMethods.map(method => ( + + {method} + + ))} + + )} + + {walletConnectData.walletConnectEvents.length > 0 && ( + + Events Requested: + {walletConnectData.walletConnectEvents.map(event => ( + + {event} + + ))} + + )} diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index 5314df9..b97e3a6 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -13,6 +13,8 @@ const AccountsContext = createContext<{ setNetworksData: (networksDataArray: NetworksDataState[]) => void; networkType: string; setNetworkType: (networkType: string) => void; + currentChainId: string; + setCurrentChainId: (currentChainId: string) => void; }>({ accounts: { ethAccounts: [], cosmosAccounts: [] }, setAccounts: () => {}, @@ -22,6 +24,8 @@ const AccountsContext = createContext<{ setNetworksData: () => {}, networkType: '', setNetworkType: () => {}, + currentChainId: '', + setCurrentChainId: () => {}, }); const useAccounts = () => { @@ -34,13 +38,12 @@ const AccountsProvider = ({ children }: { children: any }) => { ethAccounts: [], cosmosAccounts: [], }); - // TODO: Replace chainId values with testnet chainIds const [networksData, setNetworksData] = useState([ { - chainId: 'eip155:11155111', - networkName: EIP155_CHAINS['eip155:11155111'].name, + chainId: 'eip155:1', + networkName: EIP155_CHAINS['eip155:1'].name, networkType: 'eth', - rpcUrl: EIP155_CHAINS['eip155:11155111'].rpc, + rpcUrl: EIP155_CHAINS['eip155:1'].rpc, currencySymbol: 'ETH', }, { @@ -48,13 +51,17 @@ const AccountsProvider = ({ children }: { children: any }) => { networkName: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].name, networkType: 'cosmos', rpcUrl: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].rpc, - nativeDenom: 'ATOM', + nativeDenom: 'uatom', addressPrefix: 'cosmos', coinType: '118', }, ]); const [currentIndex, setCurrentIndex] = useState(0); const [networkType, setNetworkType] = useState('eth'); + const [currentChainId, setCurrentChainId] = useState( + networksData[0].chainId, + ); + return ( { setNetworksData, networkType, setNetworkType, + currentChainId, + setCurrentChainId, }}> {children} diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 439a6b0..3121b72 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -28,15 +28,16 @@ const AddNetwork = () => { const [networkType, setNetworkType] = useState('eth'); - // TODO: Update session when new network is added with updated addresses const updateNetworkType = (newNetworkType: string) => { setNetworkType(newNetworkType); }; const submit = useCallback( async (data: NetworksDataState) => { + const namespace = networkType === 'eth' ? 'eip155:' : 'cosmos:'; const updatedData = { ...data, + chainId: `${namespace}${data.chainId}`, networkType, }; setNetworksData([...networksData, updatedData]); @@ -45,9 +46,10 @@ const AddNetwork = () => { }, [navigation, networkType, networksData, setNetworksData], ); + return ( // TODO: get form data from json file - + { } const response = await approveWalletConnectRequest( + networksData, requestEvent, account, network, @@ -149,7 +148,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { } else { const cosmosBalance = await cosmosStargateClient?.getBalance( account.address, - COSMOS_DENOM, + requestedChain!.nativeDenom!.toLowerCase(), ); setBalance(cosmosBalance?.amount!); @@ -159,7 +158,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { if (account) { getAccountBalance(account); } - }, [account, provider, network, cosmosStargateClient]); + }, [account, provider, network, cosmosStargateClient, requestedChain]); useEffect(() => { navigation.setOptions({ @@ -204,7 +203,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { ); const client = await SigningStargateClient.connectWithSigner( - COSMOS_TESTNET_CHAINS[chainId as string].rpc, + requestedChain?.rpcUrl!, sender, ); @@ -240,9 +239,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { { {network === 'eth' && ( diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 02e68c7..678ab5d 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -36,6 +36,8 @@ const HomeScreen = () => { networkType, setNetworkType, networksData, + currentChainId, + setCurrentChainId, } = useAccounts(); const { setActiveSessions } = useWalletConnect(); @@ -60,9 +62,6 @@ const HomeScreen = () => { const [isWalletCreated, setIsWalletCreated] = useState(false); const [isWalletCreating, setIsWalletCreating] = useState(false); const [walletDialog, setWalletDialog] = useState(false); - const [currentChainId, setCurrentChainId] = useState( - networksData[0].chainId, - ); const [resetWalletDialog, setResetWalletDialog] = useState(false); const [isAccountsFetched, setIsAccountsFetched] = useState(false); const [phrase, setPhrase] = useState(''); diff --git a/src/utils/constants.ts b/src/utils/constants.ts deleted file mode 100644 index ce4100d..0000000 --- a/src/utils/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const COSMOS_DENOM = 'uatom'; diff --git a/src/utils/wallet-connect/EIP155Data.ts b/src/utils/wallet-connect/EIP155Data.ts index eb2acd1..6327c37 100644 --- a/src/utils/wallet-connect/EIP155Data.ts +++ b/src/utils/wallet-connect/EIP155Data.ts @@ -140,11 +140,5 @@ export const EIP155_CHAINS = { */ export const EIP155_SIGNING_METHODS = { PERSONAL_SIGN: 'personal_sign', - ETH_SIGN: 'eth_sign', - ETH_SIGN_TRANSACTION: 'eth_signTransaction', - ETH_SIGN_TYPED_DATA: 'eth_signTypedData', - ETH_SIGN_TYPED_DATA_V3: 'eth_signTypedData_v3', - ETH_SIGN_TYPED_DATA_V4: 'eth_signTypedData_v4', - ETH_SEND_RAW_TRANSACTION: 'eth_sendRawTransaction', ETH_SEND_TRANSACTION: 'eth_sendTransaction', }; diff --git a/src/utils/wallet-connect/WalletConnectRequests.ts b/src/utils/wallet-connect/WalletConnectRequests.ts index 62164c7..3b9f123 100644 --- a/src/utils/wallet-connect/WalletConnectRequests.ts +++ b/src/utils/wallet-connect/WalletConnectRequests.ts @@ -13,12 +13,12 @@ import { import { EIP155_SIGNING_METHODS } from './EIP155Data'; import { signDirectMessage, signEthMessage } from '../sign-message'; -import { Account } from '../../types'; +import { Account, NetworksDataState } from '../../types'; import { getMnemonic, getPathKey } from '../misc'; import { getCosmosAccounts } from '../accounts'; -import { COSMOS_DENOM } from '../constants'; export async function approveWalletConnectRequest( + networksData: NetworksDataState[], requestEvent: SignClientTypes.EventArguments['session_request'], account: Account, network: string, @@ -28,10 +28,15 @@ export async function approveWalletConnectRequest( const { params, id } = requestEvent; const { request } = params; + const chainId = requestEvent.params.chainId; + const requestedChain = networksData.find( + networkData => networkData.chainId === chainId, + ); + const path = (await getPathKey(network, account.counterId)).path; const mnemonic = await getMnemonic(); const cosmosAccount = await getCosmosAccounts(mnemonic, path); - const address = cosmosAccount.data.address; + const address = account.address; switch (request.method) { case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: @@ -101,7 +106,7 @@ export async function approveWalletConnectRequest( }); case 'cosmos_sendTokens': - const amount = coins(request.params[0].value, COSMOS_DENOM); + const amount = coins(request.params[0].value, requestedChain!.chainId); const gasPrice = GasPrice.fromString( request.params[0].gasPrice.toString(), ); -- 2.45.2 From 94bd8b6480a5c75b5334cd7c30210bbc19c448b3 Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:15:51 +0530 Subject: [PATCH 40/64] Persist network data (#84) * Store new network data * Store default networks in keystore (#86) * Add default nws in keystore * Fix duplicate networks * Display correct currency symbols for eth and cosmos tx * Fix currency display * Use wei for eth --- index.js | 17 ++++--- src/components/Accounts.tsx | 4 +- src/components/NetworkDropdown.tsx | 4 +- src/components/PairingModal.tsx | 5 ++- src/context/AccountsContext.tsx | 37 +--------------- src/context/NetworksContext.tsx | 71 ++++++++++++++++++++++++++++++ src/screens/AddNetwork.tsx | 11 +++-- src/screens/ApproveTransaction.tsx | 24 +++++++--- src/screens/HomeScreen.tsx | 6 +-- src/screens/SignRequest.tsx | 5 ++- src/types.ts | 1 + src/utils/accounts.ts | 34 +++++++++++++- src/utils/constants.ts | 23 ++++++++++ 13 files changed, 178 insertions(+), 64 deletions(-) create mode 100644 src/context/NetworksContext.tsx create mode 100644 src/utils/constants.ts diff --git a/index.js b/index.js index 5a8a4aa..e160e8c 100644 --- a/index.js +++ b/index.js @@ -7,6 +7,7 @@ import { NavigationContainer } from '@react-navigation/native'; import App from './src/App'; import { AccountsProvider } from './src/context/AccountsContext'; +import { NetworksProvider } from './src/context/NetworksContext'; import { WalletConnectProvider } from './src/context/WalletConnectContext'; import { name as appName } from './app.json'; @@ -23,13 +24,15 @@ export default function Main() { }; return ( - - - - - - - + + + + + + + + + ); } diff --git a/src/components/Accounts.tsx b/src/components/Accounts.tsx index 13cb6fd..a55e639 100644 --- a/src/components/Accounts.tsx +++ b/src/components/Accounts.tsx @@ -15,6 +15,7 @@ import { useAccounts } from '../context/AccountsContext'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData'; +import { useNetworks } from '../context/NetworksContext'; const Accounts = ({ network, @@ -24,7 +25,8 @@ const Accounts = ({ const navigation = useNavigation>(); - const { accounts, setAccounts, networksData, currentChainId } = useAccounts(); + const { accounts, setAccounts } = useAccounts(); + const { networksData, currentChainId } = useNetworks(); const [expanded, setExpanded] = useState(false); const [isAccountCreating, setIsAccountCreating] = useState(false); const [hdDialog, setHdDialog] = useState(false); diff --git a/src/components/NetworkDropdown.tsx b/src/components/NetworkDropdown.tsx index cc5d329..adabc58 100644 --- a/src/components/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown.tsx @@ -4,10 +4,10 @@ import { List } from 'react-native-paper'; import { NetworkDropdownProps, NetworksDataState } from '../types'; import styles from '../styles/stylesheet'; -import { useAccounts } from '../context/AccountsContext'; +import { useNetworks } from '../context/NetworksContext'; const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { - const { networksData } = useAccounts(); + const { networksData } = useNetworks(); const [expanded, setExpanded] = useState(false); const [selectedNetwork, setSelectedNetwork] = useState( diff --git a/src/components/PairingModal.tsx b/src/components/PairingModal.tsx index fdf849c..7781092 100644 --- a/src/components/PairingModal.tsx +++ b/src/components/PairingModal.tsx @@ -13,6 +13,7 @@ import { useAccounts } from '../context/AccountsContext'; import { useWalletConnect } from '../context/WalletConnectContext'; import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; import { COSMOS_METHODS } from '../utils/wallet-connect/COSMOSData'; +import { useNetworks } from '../context/NetworksContext'; const PairingModal = ({ visible, @@ -21,8 +22,8 @@ const PairingModal = ({ setModalVisible, setToastVisible, }: PairingModalProps) => { - const { accounts, networksData, currentChainId, currentIndex } = - useAccounts(); + const { accounts, currentIndex } = useAccounts(); + const { networksData, currentChainId } = useNetworks(); const [isLoading, setIsLoading] = useState(false); const dappName = currentProposal?.params?.proposer?.metadata.name; diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index b97e3a6..f0edd2c 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -1,31 +1,21 @@ import React, { createContext, useContext, useState } from 'react'; -import { AccountsState, NetworksDataState } from '../types'; -import { EIP155_CHAINS } from '../utils/wallet-connect/EIP155Data'; -import { COSMOS_TESTNET_CHAINS } from '../utils/wallet-connect/COSMOSData'; +import { AccountsState } from '../types'; const AccountsContext = createContext<{ accounts: AccountsState; setAccounts: (account: AccountsState) => void; currentIndex: number; setCurrentIndex: (index: number) => void; - networksData: NetworksDataState[]; - setNetworksData: (networksDataArray: NetworksDataState[]) => void; networkType: string; setNetworkType: (networkType: string) => void; - currentChainId: string; - setCurrentChainId: (currentChainId: string) => void; }>({ accounts: { ethAccounts: [], cosmosAccounts: [] }, setAccounts: () => {}, currentIndex: 0, setCurrentIndex: () => {}, - networksData: [], - setNetworksData: () => {}, networkType: '', setNetworkType: () => {}, - currentChainId: '', - setCurrentChainId: () => {}, }); const useAccounts = () => { @@ -38,29 +28,8 @@ const AccountsProvider = ({ children }: { children: any }) => { ethAccounts: [], cosmosAccounts: [], }); - const [networksData, setNetworksData] = useState([ - { - chainId: 'eip155:1', - networkName: EIP155_CHAINS['eip155:1'].name, - networkType: 'eth', - rpcUrl: EIP155_CHAINS['eip155:1'].rpc, - currencySymbol: 'ETH', - }, - { - chainId: 'cosmos:theta-testnet-001', - networkName: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].name, - networkType: 'cosmos', - rpcUrl: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].rpc, - nativeDenom: 'uatom', - addressPrefix: 'cosmos', - coinType: '118', - }, - ]); const [currentIndex, setCurrentIndex] = useState(0); const [networkType, setNetworkType] = useState('eth'); - const [currentChainId, setCurrentChainId] = useState( - networksData[0].chainId, - ); return ( { setAccounts, currentIndex, setCurrentIndex, - networksData, - setNetworksData, networkType, setNetworkType, - currentChainId, - setCurrentChainId, }}> {children} diff --git a/src/context/NetworksContext.tsx b/src/context/NetworksContext.tsx new file mode 100644 index 0000000..79af79d --- /dev/null +++ b/src/context/NetworksContext.tsx @@ -0,0 +1,71 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +import { NetworksDataState } from '../types'; +import { retrieveNetworksData, storeNetworkData } from '../utils/accounts'; +import { DEFAULTNETWORKS } from '../utils/constants'; + +const NetworksContext = createContext<{ + currentIndex: number; + setCurrentIndex: (index: number) => void; + networksData: NetworksDataState[]; + setNetworksData: React.Dispatch>; + networkType: string; + setNetworkType: (networkType: string) => void; + currentChainId?: string; + setCurrentChainId: (currentChainId: string) => void; +}>({ + currentIndex: 0, + setCurrentIndex: () => {}, + networksData: [], + setNetworksData: () => {}, + networkType: '', + setNetworkType: () => {}, + currentChainId: undefined, + setCurrentChainId: () => {}, +}); + +const useNetworks = () => { + const networksContext = useContext(NetworksContext); + return networksContext; +}; + +const NetworksProvider = ({ children }: { children: any }) => { + const [networksData, setNetworksData] = useState([]); + const [currentIndex, setCurrentIndex] = useState(0); + const [networkType, setNetworkType] = useState('eth'); + const [currentChainId, setCurrentChainId] = useState(); + + useEffect(() => { + const fetchData = async () => { + const retrievedNetworks = await retrieveNetworksData(); + if (retrievedNetworks.length === 0) { + for (const defaultNetwork of DEFAULTNETWORKS) { + await storeNetworkData(defaultNetwork); + } + } + const retrievedNewNetworks = await retrieveNetworksData(); + setNetworksData(retrievedNewNetworks); + setCurrentChainId(retrievedNewNetworks[0].chainId); + }; + + fetchData(); + }, []); + + return ( + + {children} + + ); +}; + +export { useNetworks, NetworksProvider }; diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 3121b72..fa413c6 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -8,8 +8,9 @@ import { useNavigation } from '@react-navigation/native'; import styles from '../styles/stylesheet'; import { NetworksDataState, StackParamsList } from '../types'; -import { useAccounts } from '../context/AccountsContext'; import { SelectNetworkType } from '../components/SelectNetworkType'; +import { storeNetworkData } from '../utils/accounts'; +import { useNetworks } from '../context/NetworksContext'; // TODO: Add validation to form inputs const AddNetwork = () => { @@ -24,7 +25,7 @@ const AddNetwork = () => { mode: 'onChange', }); - const { networksData, setNetworksData } = useAccounts(); + const { networksData, setNetworksData } = useNetworks(); const [networkType, setNetworkType] = useState('eth'); @@ -35,12 +36,14 @@ const AddNetwork = () => { const submit = useCallback( async (data: NetworksDataState) => { const namespace = networkType === 'eth' ? 'eip155:' : 'cosmos:'; - const updatedData = { + const newNetworkData = { ...data, chainId: `${namespace}${data.chainId}`, networkType, + isDefault: false, }; - setNetworksData([...networksData, updatedData]); + setNetworksData([...networksData, newNetworkData]); + await storeNetworkData(newNetworkData); navigation.navigate('Laconic'); }, diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index a080c19..ae20a9d 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -27,7 +27,7 @@ import { import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import DataBox from '../components/DataBox'; import { getPathKey } from '../utils/misc'; -import { useAccounts } from '../context/AccountsContext'; +import { useNetworks } from '../context/NetworksContext'; type SignRequestProps = NativeStackScreenProps< StackParamsList, @@ -35,7 +35,7 @@ type SignRequestProps = NativeStackScreenProps< >; const ApproveTransaction = ({ route }: SignRequestProps) => { - const { networksData } = useAccounts(); + const { networksData } = useNetworks(); const requestSession = route.params.requestSessionData; const requestName = requestSession.peer.metadata.name; @@ -239,24 +239,36 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { {transaction && ( {network === 'eth' && ( diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 678ab5d..ccf4909 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -17,6 +17,7 @@ import { useAccounts } from '../context/AccountsContext'; import { useWalletConnect } from '../context/WalletConnectContext'; import { NetworksDataState, StackParamsList } from '../types'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; +import { useNetworks } from '../context/NetworksContext'; const WCLogo = () => { return ( @@ -35,10 +36,9 @@ const HomeScreen = () => { setCurrentIndex, networkType, setNetworkType, - networksData, - currentChainId, - setCurrentChainId, } = useAccounts(); + + const { networksData, currentChainId, setCurrentChainId } = useNetworks(); const { setActiveSessions } = useWalletConnect(); const navigation = diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 5dd60be..5a7ccc0 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -21,12 +21,12 @@ import { } from '../utils/wallet-connect/WalletConnectRequests'; import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils'; import { EIP155_SIGNING_METHODS } from '../utils/wallet-connect/EIP155Data'; -import { useAccounts } from '../context/AccountsContext'; +import { useNetworks } from '../context/NetworksContext'; type SignRequestProps = NativeStackScreenProps; const SignRequest = ({ route }: SignRequestProps) => { - const { networksData } = useAccounts(); + const { networksData } = useNetworks(); const requestSession = route.params.requestSessionData; const requestName = requestSession?.peer?.metadata?.name; @@ -152,6 +152,7 @@ const SignRequest = ({ route }: SignRequestProps) => { } const response = await approveWalletConnectRequest( + networksData, requestEvent, account, network, diff --git a/src/types.ts b/src/types.ts index 2f17e30..b632815 100644 --- a/src/types.ts +++ b/src/types.ts @@ -63,6 +63,7 @@ export type NetworksDataState = { nativeDenom?: string; addressPrefix?: string; coinType?: string; + isDefault: boolean; }; export type SignMessageParams = { diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 7bdf899..168aed9 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -16,7 +16,7 @@ import { Secp256k1HdWallet } from '@cosmjs/amino'; import { AccountData } from '@cosmjs/proto-signing'; import { stringToPath } from '@cosmjs/crypto'; -import { Account, WalletDetails } from '../types'; +import { Account, NetworksDataState, WalletDetails } from '../types'; import { getHDPath, getPathKey, @@ -124,6 +124,36 @@ const addAccountFromHDPath = async ( } }; +const storeNetworkData = async ( + networkData: NetworksDataState, +): Promise => { + const networks = await getInternetCredentials('networks'); + const retrievedNetworks = + networks && networks.password ? JSON.parse(networks.password) : []; + let networkId = 0; + if (retrievedNetworks.length > 0) { + networkId = retrievedNetworks[retrievedNetworks.length - 1].networkId + 1; + } + + const updatedNetworks = [ + ...retrievedNetworks, + { networkId: networkId, ...networkData }, + ]; + await setInternetCredentials( + 'networks', + '_', + JSON.stringify(updatedNetworks), + ); +}; + +const retrieveNetworksData = async (): Promise => { + const networks = await getInternetCredentials('networks'); + const retrievedNetworks: NetworksDataState[] = + networks && networks.password ? JSON.parse(networks.password) : []; + + return retrievedNetworks; +}; + export const retrieveAccountsForNetwork = async ( network: string, accountsIndices: string, @@ -318,6 +348,8 @@ export { createWallet, addAccount, addAccountFromHDPath, + storeNetworkData, + retrieveNetworksData, retrieveAccounts, retrieveSingleAccount, resetWallet, diff --git a/src/utils/constants.ts b/src/utils/constants.ts new file mode 100644 index 0000000..19e7f6f --- /dev/null +++ b/src/utils/constants.ts @@ -0,0 +1,23 @@ +import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData'; +import { EIP155_CHAINS } from './wallet-connect/EIP155Data'; + +export const DEFAULTNETWORKS = [ + { + chainId: 'eip155:1', + networkName: EIP155_CHAINS['eip155:1'].name, + networkType: 'eth', + rpcUrl: EIP155_CHAINS['eip155:1'].rpc, + currencySymbol: 'ETH', + isDefault: true, + }, + { + chainId: 'cosmos:theta-testnet-001', + networkName: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].name, + networkType: 'cosmos', + rpcUrl: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].rpc, + nativeDenom: 'uatom', + addressPrefix: 'cosmos', + coinType: '118', + isDefault: true, + }, +]; -- 2.45.2 From 3809ce88b1eaff91470d9f27631092823d719e4d Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:55:20 +0530 Subject: [PATCH 41/64] Update keystore data structure (#88) * Update keystore data structure (#83) * Update create wallet and retrieve accounts functionality for updated data structure * Refactor accounts state * Use constant variable for cosmos * Update add accounts incrementally and with custom HD path for updated data structure (#85) * Change data structure * Reset wallet change * Fix signEthMessage * Fix sign request * Fix pairing with laconic pay dApp and sending tokens * Add accounts to configured networks * Update add account from hd path flow * Handle review changes --------- Co-authored-by: Shreerang Kale * Remove network type state * Refactor create wallet code (#89) * Refactor create wallet code * Create cosmos accounts with correct address prefix * Use networks data from state while creating wallet * Refactor code and add network id in types (#91) * Refactor add new networks component * Add selected network state in context * Remove returning account from create wallet --------- Co-authored-by: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> --- src/App.tsx | 14 +- src/components/Accounts.tsx | 87 ++---- src/components/HDPath.tsx | 8 +- src/components/HDPathDialog.tsx | 5 + src/components/PairingModal.tsx | 51 ++-- src/components/SelectNetworkType.tsx | 3 +- src/context/AccountsContext.tsx | 20 +- src/context/NetworksContext.tsx | 24 +- src/screens/AddNetwork.tsx | 117 ++++++-- src/screens/ApproveTransaction.tsx | 110 +++---- src/screens/HomeScreen.tsx | 84 ++---- src/screens/SignMessage.tsx | 10 +- src/screens/SignRequest.tsx | 60 ++-- src/types.ts | 34 +-- src/utils/accounts.ts | 280 +++++++++--------- src/utils/constants.ts | 13 +- src/utils/misc.ts | 122 ++++---- src/utils/sign-message.ts | 24 +- .../wallet-connect/WalletConnectRequests.ts | 29 +- 19 files changed, 552 insertions(+), 543 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index cd26e38..321e454 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -27,6 +27,7 @@ import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data'; import { getSignParamsMessage } from './utils/wallet-connect/Helpers'; import ApproveTransaction from './screens/ApproveTransaction'; import AddNetwork from './screens/AddNetwork'; +import { COSMOS, EIP155 } from './utils/constants'; const Stack = createNativeStackNavigator(); @@ -44,7 +45,7 @@ const App = (): React.JSX.Element => { const onSessionProposal = useCallback( async (proposal: SignClientTypes.EventArguments['session_proposal']) => { - if (!accounts.ethAccounts.length || !accounts.cosmosAccounts.length) { + if (!accounts.length || !accounts.length) { const { id } = proposal; await web3wallet!.rejectSession({ id, @@ -55,7 +56,7 @@ const App = (): React.JSX.Element => { setModalVisible(true); setCurrentProposal(proposal); }, - [accounts.ethAccounts, accounts.cosmosAccounts], + [accounts], ); const onSessionRequest = useCallback( @@ -68,7 +69,6 @@ const App = (): React.JSX.Element => { switch (request.method) { case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: navigation.navigate('ApproveTransaction', { - network: 'eth', transaction: request.params[0], requestEvent, requestSessionData, @@ -77,7 +77,7 @@ const App = (): React.JSX.Element => { case EIP155_SIGNING_METHODS.PERSONAL_SIGN: navigation.navigate('SignRequest', { - network: 'eth', + namespace: EIP155, address: request.params[1], message: getSignParamsMessage(request.params), requestEvent, @@ -103,7 +103,7 @@ const App = (): React.JSX.Element => { ), }; navigation.navigate('SignRequest', { - network: 'cosmos', + namespace: COSMOS, address: request.params.signerAddress, message: JSON.stringify(message, undefined, 2), requestEvent, @@ -113,7 +113,7 @@ const App = (): React.JSX.Element => { break; case 'cosmos_signAmino': navigation.navigate('SignRequest', { - network: 'cosmos', + namespace: COSMOS, address: request.params.signerAddress, message: request.params.signDoc.memo, requestEvent, @@ -124,7 +124,6 @@ const App = (): React.JSX.Element => { case 'cosmos_sendTokens': navigation.navigate('ApproveTransaction', { - network: 'cosmos', transaction: request.params[0], requestEvent, requestSessionData, @@ -173,7 +172,6 @@ const App = (): React.JSX.Element => { options={{ headerTitle: () => Sign Message, }} - initialParams={{ selectedNetwork: 'Ethereum' }} /> { +const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => { const navigation = useNavigation>(); const { accounts, setAccounts } = useAccounts(); - const { networksData, currentChainId } = useNetworks(); + const { selectedNetwork } = useNetworks(); const [expanded, setExpanded] = useState(false); const [isAccountCreating, setIsAccountCreating] = useState(false); const [hdDialog, setHdDialog] = useState(false); @@ -36,22 +33,7 @@ const Accounts = ({ const handlePress = () => setExpanded(!expanded); const updateAccounts = (account: Account) => { - switch (network) { - case 'eth': - setAccounts({ - ...accounts, - ethAccounts: [...accounts.ethAccounts, account], - }); - break; - case 'cosmos': - setAccounts({ - ...accounts, - cosmosAccounts: [...accounts.cosmosAccounts, account], - }); - break; - default: - console.error('Select a valid network!'); - } + setAccounts([...accounts, account]); }; useEffect(() => { @@ -70,24 +52,22 @@ const Accounts = ({ : undefined, ); - const currentNetwork = networksData.find( - networkData => networkData.chainId === currentChainId, - ); + const namespaceChainId = `${selectedNetwork?.namespace}:${selectedNetwork?.chainId}`; let updatedNamespaces; - switch (currentNetwork?.networkType) { - case 'eth': + switch (selectedNetwork?.namespace) { + case EIP155: updatedNamespaces = { eip155: { - chains: [currentNetwork.chainId], + chains: [namespaceChainId], // TODO: Debug optional namespace methods and events being required for approval methods: [ ...Object.values(EIP155_SIGNING_METHODS), ...(combinedNamespaces.eip155?.methods ?? []), ], events: [...(combinedNamespaces.eip155?.events ?? [])], - accounts: accounts.ethAccounts.map(ethAccount => { - return `${currentChainId}:${ethAccount.address}`; + accounts: accounts.map(ethAccount => { + return `${namespaceChainId}:${ethAccount.address}`; }), }, cosmos: { @@ -98,17 +78,17 @@ const Accounts = ({ }, }; break; - case 'cosmos': + case COSMOS: updatedNamespaces = { cosmos: { - chains: [currentNetwork.chainId], + chains: [namespaceChainId], methods: [ ...Object.values(COSMOS_METHODS), ...(combinedNamespaces.cosmos?.methods ?? []), ], events: [...(combinedNamespaces.cosmos?.events ?? [])], - accounts: accounts.cosmosAccounts.map(cosmosAccount => { - return `${currentChainId}:${cosmosAccount.address}`; + accounts: accounts.map(cosmosAccount => { + return `${namespaceChainId}:${cosmosAccount.address}`; }), }, eip155: { @@ -140,32 +120,21 @@ const Accounts = ({ const addAccountHandler = async () => { setIsAccountCreating(true); - const newAccount = await addAccount(network); + const newAccount = await addAccount(selectedNetwork!); setIsAccountCreating(false); if (newAccount) { updateAccounts(newAccount); - updateId(newAccount.counterId); + updateIndex(newAccount.index); } }; - const selectedAccounts: Account[] = (() => { - switch (network) { - case 'eth': - return accounts.ethAccounts; - case 'cosmos': - return accounts.cosmosAccounts; - default: - return []; - } - })(); - const renderAccountItems = () => - selectedAccounts.map(account => ( + accounts.map(account => ( { - updateId(account.counterId); + updateIndex(account.index); setExpanded(false); }} /> @@ -178,7 +147,7 @@ const Accounts = ({ visible={hdDialog} hideDialog={() => setHdDialog(false)} updateAccounts={updateAccounts} - updateIndex={updateId} + updateIndex={updateIndex} pathCode={pathCode} /> { setHdDialog(true); - setPathCode(network === 'eth' ? "m/44'/60'/" : "m/44'/118'/"); + // TODO: Use coin type while adding from HD path + setPathCode( + selectedNetwork!.namespace === EIP155 + ? "m/44'/60'/" + : "m/44'/118'/", + ); }}> Add Account from HD path - + { navigation.navigate('SignMessage', { - selectedNetwork: network, - accountInfo: selectedAccounts[currentIndex], + selectedNamespace: selectedNetwork!.namespace, + selectedChainId: selectedNetwork!.chainId, + accountInfo: accounts[currentIndex], }); }}> void; updateAccounts: (account: Account) => void; hideDialog: () => void; + selectedNetwork: NetworksDataState; }) => { const [isAccountCreating, setIsAccountCreating] = useState(false); const [path, setPath] = useState({ @@ -41,10 +43,10 @@ const HDPath = ({ pathCode + `${path.firstNumber}'/${path.secondNumber}/${path.thirdNumber}`; try { - const newAccount = await addAccountFromHDPath(hdPath); + const newAccount = await addAccountFromHDPath(hdPath, selectedNetwork); if (newAccount) { updateAccounts(newAccount); - updateIndex(newAccount.counterId); + updateIndex(newAccount.index); hideDialog(); } } catch (error) { diff --git a/src/components/HDPathDialog.tsx b/src/components/HDPathDialog.tsx index 918d6ba..6a8b168 100644 --- a/src/components/HDPathDialog.tsx +++ b/src/components/HDPathDialog.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { Portal, Dialog } from 'react-native-paper'; + import { HDPathDialogProps } from '../types'; import HDPath from './HDPath'; +import { useNetworks } from '../context/NetworksContext'; const HDPathDialog = ({ visible, @@ -10,12 +12,15 @@ const HDPathDialog = ({ updateAccounts, pathCode, }: HDPathDialogProps) => { + const { selectedNetwork } = useNetworks(); + return ( Add account from HD path { const { accounts, currentIndex } = useAccounts(); - const { networksData, currentChainId } = useNetworks(); + const { selectedNetwork } = useNetworks(); const [isLoading, setIsLoading] = useState(false); const dappName = currentProposal?.params?.proposer?.metadata.name; @@ -83,37 +84,21 @@ const PairingModal = ({ } // Set selected account as the first account in supported namespaces - const sortedAccounts = Object.entries(accounts).reduce( - (acc: AccountsState, [key, value]) => { - let newValue = [...value]; + const sortedAccounts = [ + accounts[currentIndex], + ...accounts.filter((account, index) => index !== currentIndex), + ]; - // TODO: Implement selectedAccount instead of currentIndex in AccountsContext - if (value.length > currentIndex) { - const currentAccount = newValue[currentIndex]; - const remainingAccounts = newValue.filter( - (_, index) => index !== currentIndex, - ); - newValue = [currentAccount, ...remainingAccounts]; - } - - acc[key as 'ethAccounts' | 'cosmosAccounts'] = newValue; - return acc; - }, - { ethAccounts: [], cosmosAccounts: [] }, - ); - - const currentNetwork = networksData.find( - networkData => networkData.chainId === currentChainId, - ); + const namespaceChainId = `${selectedNetwork?.namespace}:${selectedNetwork?.chainId}`; const { optionalNamespaces, requiredNamespaces } = currentProposal.params; // TODO: Check with other dApps - switch (currentNetwork?.networkType) { - case 'eth': + switch (selectedNetwork?.namespace) { + case EIP155: return { eip155: { - chains: [currentNetwork.chainId], + chains: [namespaceChainId], // TODO: Debug optional namespace methods and events being required for approval methods: [ ...Object.values(EIP155_SIGNING_METHODS), @@ -124,8 +109,8 @@ const PairingModal = ({ ...(optionalNamespaces.eip155?.events ?? []), ...(requiredNamespaces.eip155?.events ?? []), ], - accounts: sortedAccounts.ethAccounts.map(ethAccount => { - return `${currentChainId}:${ethAccount.address}`; + accounts: sortedAccounts.map(ethAccount => { + return `${namespaceChainId}:${ethAccount.address}`; }), }, cosmos: { @@ -135,10 +120,10 @@ const PairingModal = ({ accounts: [], }, }; - case 'cosmos': + case COSMOS: return { cosmos: { - chains: [currentNetwork.chainId], + chains: [namespaceChainId], methods: [ ...Object.values(COSMOS_METHODS), ...(optionalNamespaces.cosmos?.methods ?? []), @@ -148,8 +133,8 @@ const PairingModal = ({ ...(optionalNamespaces.cosmos?.events ?? []), ...(requiredNamespaces.cosmos?.events ?? []), ], - accounts: sortedAccounts.cosmosAccounts.map(cosmosAccount => { - return `${currentChainId}:${cosmosAccount.address}`; + accounts: sortedAccounts.map(cosmosAccount => { + return `${namespaceChainId}:${cosmosAccount.address}`; }), }, eip155: { @@ -162,7 +147,7 @@ const PairingModal = ({ default: break; } - }, [accounts, currentProposal, networksData, currentChainId, currentIndex]); + }, [accounts, currentProposal, currentIndex, selectedNetwork]); const namespaces = useMemo(() => { return ( diff --git a/src/components/SelectNetworkType.tsx b/src/components/SelectNetworkType.tsx index cb05253..32234c7 100644 --- a/src/components/SelectNetworkType.tsx +++ b/src/components/SelectNetworkType.tsx @@ -3,6 +3,7 @@ import { View } from 'react-native'; import { Text, List } from 'react-native-paper'; import styles from '../styles/stylesheet'; +import { COSMOS, EIP155 } from '../utils/constants'; const SelectNetworkType = ({ updateNetworkType, @@ -16,7 +17,7 @@ const SelectNetworkType = ({ const handleNetworkPress = (network: string) => { setSelectedNetwork(network); - updateNetworkType(network.toLowerCase()); + updateNetworkType(network === 'ETH' ? EIP155 : COSMOS); setExpanded(false); }; diff --git a/src/context/AccountsContext.tsx b/src/context/AccountsContext.tsx index f0edd2c..2d5fe5e 100644 --- a/src/context/AccountsContext.tsx +++ b/src/context/AccountsContext.tsx @@ -1,21 +1,17 @@ import React, { createContext, useContext, useState } from 'react'; -import { AccountsState } from '../types'; +import { Account } from '../types'; const AccountsContext = createContext<{ - accounts: AccountsState; - setAccounts: (account: AccountsState) => void; + accounts: Account[]; + setAccounts: (account: Account[]) => void; currentIndex: number; setCurrentIndex: (index: number) => void; - networkType: string; - setNetworkType: (networkType: string) => void; }>({ - accounts: { ethAccounts: [], cosmosAccounts: [] }, + accounts: [], setAccounts: () => {}, currentIndex: 0, setCurrentIndex: () => {}, - networkType: '', - setNetworkType: () => {}, }); const useAccounts = () => { @@ -24,12 +20,8 @@ const useAccounts = () => { }; const AccountsProvider = ({ children }: { children: any }) => { - const [accounts, setAccounts] = useState({ - ethAccounts: [], - cosmosAccounts: [], - }); + const [accounts, setAccounts] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); - const [networkType, setNetworkType] = useState('eth'); return ( { setAccounts, currentIndex, setCurrentIndex, - networkType, - setNetworkType, }}> {children} diff --git a/src/context/NetworksContext.tsx b/src/context/NetworksContext.tsx index 79af79d..ceb60ca 100644 --- a/src/context/NetworksContext.tsx +++ b/src/context/NetworksContext.tsx @@ -2,7 +2,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; import { NetworksDataState } from '../types'; import { retrieveNetworksData, storeNetworkData } from '../utils/accounts'; -import { DEFAULTNETWORKS } from '../utils/constants'; +import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants'; const NetworksContext = createContext<{ currentIndex: number; @@ -11,8 +11,10 @@ const NetworksContext = createContext<{ setNetworksData: React.Dispatch>; networkType: string; setNetworkType: (networkType: string) => void; - currentChainId?: string; - setCurrentChainId: (currentChainId: string) => void; + selectedNetwork?: NetworksDataState; + setSelectedNetwork: React.Dispatch< + React.SetStateAction + >; }>({ currentIndex: 0, setCurrentIndex: () => {}, @@ -20,8 +22,8 @@ const NetworksContext = createContext<{ setNetworksData: () => {}, networkType: '', setNetworkType: () => {}, - currentChainId: undefined, - setCurrentChainId: () => {}, + selectedNetwork: {} as NetworksDataState, + setSelectedNetwork: () => {}, }); const useNetworks = () => { @@ -32,20 +34,20 @@ const useNetworks = () => { const NetworksProvider = ({ children }: { children: any }) => { const [networksData, setNetworksData] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); - const [networkType, setNetworkType] = useState('eth'); - const [currentChainId, setCurrentChainId] = useState(); + const [networkType, setNetworkType] = useState(EIP155); + const [selectedNetwork, setSelectedNetwork] = useState(); useEffect(() => { const fetchData = async () => { const retrievedNetworks = await retrieveNetworksData(); if (retrievedNetworks.length === 0) { - for (const defaultNetwork of DEFAULTNETWORKS) { + for (const defaultNetwork of DEFAULT_NETWORKS) { await storeNetworkData(defaultNetwork); } } const retrievedNewNetworks = await retrieveNetworksData(); setNetworksData(retrievedNewNetworks); - setCurrentChainId(retrievedNewNetworks[0].chainId); + setSelectedNetwork(retrievedNetworks[0]); }; fetchData(); @@ -60,8 +62,8 @@ const NetworksProvider = ({ children }: { children: any }) => { setNetworksData, networkType, setNetworkType, - currentChainId, - setCurrentChainId, + selectedNetwork, + setSelectedNetwork, }}> {children} diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index fa413c6..09fdbd4 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -2,15 +2,22 @@ import React, { useCallback, useState } from 'react'; import { ScrollView } from 'react-native'; import { useForm, Controller } from 'react-hook-form'; import { TextInput, Button, HelperText } from 'react-native-paper'; +import { + getInternetCredentials, + setInternetCredentials, +} from 'react-native-keychain'; +import { HDNode } from 'ethers/lib/utils'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import { useNavigation } from '@react-navigation/native'; import styles from '../styles/stylesheet'; -import { NetworksDataState, StackParamsList } from '../types'; +import { NetworksDataState, NetworksFormData, StackParamsList } from '../types'; import { SelectNetworkType } from '../components/SelectNetworkType'; import { storeNetworkData } from '../utils/accounts'; import { useNetworks } from '../context/NetworksContext'; +import { COSMOS, EIP155 } from '../utils/constants'; +import { getCosmosAccounts } from '../utils/accounts'; // TODO: Add validation to form inputs const AddNetwork = () => { @@ -25,29 +32,79 @@ const AddNetwork = () => { mode: 'onChange', }); - const { networksData, setNetworksData } = useNetworks(); + const { setNetworksData } = useNetworks(); - const [networkType, setNetworkType] = useState('eth'); + const [namespace, setNamespace] = useState(EIP155); const updateNetworkType = (newNetworkType: string) => { - setNetworkType(newNetworkType); + setNamespace(newNetworkType); }; const submit = useCallback( - async (data: NetworksDataState) => { - const namespace = networkType === 'eth' ? 'eip155:' : 'cosmos:'; + async (data: NetworksFormData) => { const newNetworkData = { ...data, - chainId: `${namespace}${data.chainId}`, - networkType, - isDefault: false, + namespace, }; - setNetworksData([...networksData, newNetworkData]); - await storeNetworkData(newNetworkData); + + const mnemonicServer = await getInternetCredentials('mnemonicServer'); + const mnemonic = mnemonicServer && mnemonicServer.password; + + if (!mnemonic) { + throw new Error('Mnemonic not found'); + } + + const hdNode = HDNode.fromMnemonic(mnemonic); + + const hdPath = `m/44'/${newNetworkData.coinType}'/0'/0/0`; + const node = hdNode.derivePath(hdPath); + let address; + + switch (newNetworkData.namespace) { + case EIP155: + address = node.address; + break; + + case COSMOS: + address = ( + await getCosmosAccounts( + mnemonic, + hdPath, + newNetworkData.addressPrefix, + ) + ).data.address; + break; + + default: + throw new Error('Unsupported namespace'); + } + + const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`; + + const retrievedNetworksData = await storeNetworkData(newNetworkData); + setNetworksData(retrievedNetworksData); + + await Promise.all([ + setInternetCredentials( + `accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`, + '_', + accountInfo, + ), + setInternetCredentials( + `addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`, + '_', + '1', + ), + setInternetCredentials( + `accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`, + '_', + '0', + ), + ]); navigation.navigate('Laconic'); }, - [navigation, networkType, networksData, setNetworksData], + [navigation, namespace, setNetworksData], ); return ( @@ -123,8 +180,25 @@ const AddNetwork = () => { )} /> + ( + <> + onChange(value)} + /> + {errors.coinType?.message} + + )} + /> - {networkType === 'eth' ? ( + {namespace === EIP155 ? ( { )} /> - ( - <> - onChange(value)} - /> - {errors.coinType?.message} - - )} - /> )} diff --git a/src/components/CreateWallet.tsx b/src/components/CreateWallet.tsx index d3814e7..5ba0708 100644 --- a/src/components/CreateWallet.tsx +++ b/src/components/CreateWallet.tsx @@ -15,6 +15,7 @@ const CreateWallet = ({ diff --git a/src/components/HDPath.tsx b/src/components/HDPath.tsx index f9d6633..b590d1e 100644 --- a/src/components/HDPath.tsx +++ b/src/components/HDPath.tsx @@ -88,7 +88,8 @@ const HDPath = ({ diff --git a/src/components/PairingModal.tsx b/src/components/PairingModal.tsx index ae09041..a9b0b90 100644 --- a/src/components/PairingModal.tsx +++ b/src/components/PairingModal.tsx @@ -229,7 +229,11 @@ const PairingModal = ({ - diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 823583c..3ddb10c 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -56,20 +56,19 @@ const AddNetwork = () => { chain => chain.chainId === Number(chainId), ); if (!ethChainDetails) { - reset({ chainId: chainId }); return; } setValue('networkName', ethChainDetails.name); setValue('rpcUrl', ethChainDetails.rpc[0]); - setValue('blockExplorerUrl', ethChainDetails.explorers![0].url); + setValue('blockExplorerUrl', ethChainDetails.explorers?.[0].url || ''); setValue('coinType', String(ethChainDetails.slip44)); setValue('currencySymbol', ethChainDetails.nativeCurrency.symbol); + return; } const cosmosChainDetails = chains.find( ({ chain_id }) => chain_id === chainId, ); if (!cosmosChainDetails) { - reset({ chainId: chainId }); return; } setValue('networkName', cosmosChainDetails.pretty_name); @@ -152,6 +151,10 @@ const AddNetwork = () => { fetchChainDetails(watchChainId); }, [watchChainId, fetchChainDetails]); + useEffect(() => { + reset(); + }, [namespace, reset]); + return ( // TODO: get form data from json file diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 291c86f..9357145 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -282,7 +282,8 @@ const SignRequest = ({ route }: SignRequestProps) => { diff --git a/src/components/NetworkDropdown.tsx b/src/components/NetworkDropdown.tsx index c1a0a34..9c9fc7c 100644 --- a/src/components/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown.tsx @@ -11,9 +11,9 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { const [expanded, setExpanded] = useState(false); - const handleNetworkPress = (networksData: NetworksDataState) => { - updateNetwork(networksData); - setSelectedNetwork(networksData); + const handleNetworkPress = (networkData: NetworksDataState) => { + updateNetwork(networkData); + setSelectedNetwork(networkData); setExpanded(false); }; diff --git a/src/components/PairingModal.tsx b/src/components/PairingModal.tsx index a9b0b90..d29bc6a 100644 --- a/src/components/PairingModal.tsx +++ b/src/components/PairingModal.tsx @@ -172,7 +172,7 @@ const PairingModal = ({ return ( - + diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 3ddb10c..6562d48 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -156,7 +156,6 @@ const AddNetwork = () => { }, [namespace, reset]); return ( - // TODO: get form data from json file @@ -171,7 +170,7 @@ const AddNetwork = () => { value={value} label="Chain ID" onBlur={onBlur} - onChangeText={value => onChange(value)} + onChangeText={textValue => onChange(textValue)} /> {errors.chainId?.message} @@ -188,7 +187,7 @@ const AddNetwork = () => { label="Network Name" value={value} onBlur={onBlur} - onChangeText={value => onChange(value)} + onChangeText={textValue => onChange(textValue)} /> {errors.networkName?.message} @@ -205,7 +204,7 @@ const AddNetwork = () => { label="New RPC URL" onBlur={onBlur} value={value} - onChangeText={value => onChange(value)} + onChangeText={textValue => onChange(textValue)} /> {errors.rpcUrl?.message} @@ -223,7 +222,7 @@ const AddNetwork = () => { value={value} label="Block Explorer URL (Optional)" onBlur={onBlur} - onChangeText={value => onChange(value)} + onChangeText={textValue => onChange(textValue)} /> {errors.blockExplorerUrl?.message} @@ -260,7 +259,7 @@ const AddNetwork = () => { value={value} label="Currency Symbol" onBlur={onBlur} - onChangeText={value => onChange(value)} + onChangeText={textValue => onChange(textValue)} /> {errors.currencySymbol?.message} @@ -281,7 +280,7 @@ const AddNetwork = () => { value={value} label="Native Denom" onBlur={onBlur} - onChangeText={value => onChange(value)} + onChangeText={textValue => onChange(textValue)} /> {errors.nativeDenom?.message} @@ -300,7 +299,7 @@ const AddNetwork = () => { value={value} label="Address Prefix" onBlur={onBlur} - onChangeText={value => onChange(value)} + onChangeText={textValue => onChange(textValue)} /> {errors.addressPrefix?.message} diff --git a/src/screens/AddSession.tsx b/src/screens/AddSession.tsx index 723298e..a54c006 100644 --- a/src/screens/AddSession.tsx +++ b/src/screens/AddSession.tsx @@ -102,7 +102,7 @@ const AddSession = () => { value={currentWCURI} numberOfLines={4} multiline={true} - style={{ padding: 10 }} + style={styles.walletConnectUriText} /> diff --git a/src/screens/ApproveTransaction.tsx b/src/screens/ApproveTransaction.tsx index d6412dd..b428b2f 100644 --- a/src/screens/ApproveTransaction.tsx +++ b/src/screens/ApproveTransaction.tsx @@ -175,7 +175,10 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { }; useEffect(() => { - const getAccountBalance = async (account: Account) => { + const getAccountBalance = async () => { + if (!account) { + return; + } if (namespace === EIP155) { const fetchedBalance = provider && (await provider.getBalance(account.address)); @@ -191,9 +194,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => { } }; - if (account) { - getAccountBalance(account); - } + getAccountBalance(); }, [account, provider, namespace, cosmosStargateClient, requestedChain]); useEffect(() => { diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index b6db8be..453e29b 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -22,7 +22,7 @@ import { useNetworks } from '../context/NetworksContext'; const WCLogo = () => { return ( ); @@ -40,6 +40,7 @@ const HomeScreen = () => { useEffect(() => { if (accounts.length > 0) { navigation.setOptions({ + // eslint-disable-next-line react/no-unstable-nested-components headerRight: () => ( diff --git a/yarn.lock b/yarn.lock index 4af45ab..6d3259b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1692,6 +1692,11 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@hookform/resolvers@^3.3.4": + version "3.3.4" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.4.tgz#de9b668c2835eb06892290192de6e2a5c906229b" + integrity sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ== + "@humanwhocodes/config-array@^0.11.13": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -9361,3 +9366,8 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +zod@^3.22.4: + version "3.22.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" + integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== -- 2.45.2 From e98dac7a5b1d1ae9cfb8a3fcca5f3258164b0e7d Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Thu, 18 Apr 2024 13:53:20 +0530 Subject: [PATCH 52/64] Fix spinner not showing up on starting wallet (#102) * Fix spinner not showing up on starting wallet * Remove unnecessary dependancies --- src/App.tsx | 2 +- src/components/Accounts.tsx | 4 ++-- src/components/NetworkDropdown.tsx | 2 +- src/components/PairingModal.tsx | 2 +- src/context/NetworksContext.tsx | 10 +++++----- src/screens/AddNetwork.tsx | 4 ++-- src/screens/SignRequest.tsx | 4 ++-- 7 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 07f5ba4..89be187 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -75,7 +75,7 @@ const App = (): React.JSX.Element => { case NETWORK_METHODS.GET_NETWORKS: const retrievedNetworks = await retrieveNetworksData(); const currentNetworkId = networksData.find( - networkData => networkData.networkId === selectedNetwork.networkId, + networkData => networkData.networkId === selectedNetwork!.networkId, )?.networkId; const formattedResponse = formatJsonRpcResult(id, { diff --git a/src/components/Accounts.tsx b/src/components/Accounts.tsx index 3568504..d43b586 100644 --- a/src/components/Accounts.tsx +++ b/src/components/Accounts.tsx @@ -54,7 +54,7 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => { optionalNamespaces, requiredNamespaces, networksData, - selectedNetwork, + selectedNetwork!, accounts, currentIndex, ); @@ -145,7 +145,7 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => { mode="contained" onPress={() => { setHdDialog(true); - setPathCode(`m/44'/${selectedNetwork.coinType}'/`); + setPathCode(`m/44'/${selectedNetwork!.coinType}'/`); }}> Add Account from HD path diff --git a/src/components/NetworkDropdown.tsx b/src/components/NetworkDropdown.tsx index b672285..12f7e76 100644 --- a/src/components/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown.tsx @@ -20,7 +20,7 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { return ( setExpanded(!expanded)}> {networksData.map(networkData => ( diff --git a/src/components/PairingModal.tsx b/src/components/PairingModal.tsx index d29bc6a..e100759 100644 --- a/src/components/PairingModal.tsx +++ b/src/components/PairingModal.tsx @@ -100,7 +100,7 @@ const PairingModal = ({ optionalNamespaces, requiredNamespaces, networksData, - selectedNetwork, + selectedNetwork!, accounts, currentIndex, ); diff --git a/src/context/NetworksContext.tsx b/src/context/NetworksContext.tsx index 75f28be..8a023ad 100644 --- a/src/context/NetworksContext.tsx +++ b/src/context/NetworksContext.tsx @@ -11,8 +11,10 @@ const NetworksContext = createContext<{ setNetworksData: React.Dispatch>; networkType: string; setNetworkType: (networkType: string) => void; - selectedNetwork: NetworksDataState; - setSelectedNetwork: React.Dispatch>; + selectedNetwork?: NetworksDataState; + setSelectedNetwork: React.Dispatch< + React.SetStateAction + >; }>({ currentIndex: 0, setCurrentIndex: () => {}, @@ -33,9 +35,7 @@ const NetworksProvider = ({ children }: { children: any }) => { const [networksData, setNetworksData] = useState([]); const [currentIndex, setCurrentIndex] = useState(0); const [networkType, setNetworkType] = useState(EIP155); - const [selectedNetwork, setSelectedNetwork] = useState( - {} as NetworksDataState, - ); + const [selectedNetwork, setSelectedNetwork] = useState(); useEffect(() => { const fetchData = async () => { diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 608299e..23cf623 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -85,7 +85,7 @@ const AddNetwork = () => { setValue('networkName', ethChainDetails.name); setValue('rpcUrl', ethChainDetails.rpc[0]); setValue('blockExplorerUrl', ethChainDetails.explorers?.[0].url || ''); - setValue('coinType', String(ethChainDetails.slip44)); + setValue('coinType', String(ethChainDetails.slip44 ?? '60')); setValue('currencySymbol', ethChainDetails.nativeCurrency.symbol); return; } @@ -99,7 +99,7 @@ const AddNetwork = () => { setValue('rpcUrl', cosmosChainDetails.apis?.rpc?.[0]?.address || ''); setValue('blockExplorerUrl', cosmosChainDetails.explorers?.[0].url || ''); setValue('addressPrefix', cosmosChainDetails.bech32_prefix); - setValue('coinType', String(cosmosChainDetails.slip44)); + setValue('coinType', String(cosmosChainDetails.slip44 ?? '118')); setValue('nativeDenom', cosmosChainDetails.fees?.fee_tokens[0].denom || ''); }, CHAINID_DEBOUNCE_DELAY); diff --git a/src/screens/SignRequest.tsx b/src/screens/SignRequest.tsx index 861272f..8464e92 100644 --- a/src/screens/SignRequest.tsx +++ b/src/screens/SignRequest.tsx @@ -26,7 +26,7 @@ import { useNetworks } from '../context/NetworksContext'; type SignRequestProps = NativeStackScreenProps; const SignRequest = ({ route }: SignRequestProps) => { - const { networksData, selectedNetwork } = useNetworks(); + const { networksData } = useNetworks(); const requestSession = route.params.requestSessionData; const requestName = requestSession?.peer?.metadata?.name; @@ -146,7 +146,7 @@ const SignRequest = ({ route }: SignRequestProps) => { route.params.address, route.params.message, ); - }, [retrieveData, sanitizePath, route, networksData, selectedNetwork]); + }, [retrieveData, sanitizePath, route, networksData]); const handleWalletConnectRequest = async () => { const { requestEvent } = route.params || {}; -- 2.45.2 From b9d3eef7070956a842c20802553f3fae93bf046f Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:40:43 +0530 Subject: [PATCH 53/64] Send only network id and name to paired dApp (#104) --- src/App.tsx | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 89be187..5b8d70c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -29,7 +29,6 @@ import { getSignParamsMessage } from './utils/wallet-connect/helpers'; import ApproveTransaction from './screens/ApproveTransaction'; import AddNetwork from './screens/AddNetwork'; import { COSMOS, EIP155 } from './utils/constants'; -import { retrieveNetworksData } from './utils/accounts'; import { useNetworks } from './context/NetworksContext'; import { NETWORK_METHODS } from './utils/wallet-connect/common-data'; @@ -73,14 +72,20 @@ const App = (): React.JSX.Element => { web3wallet!.engine.signClient.session.get(topic); switch (request.method) { case NETWORK_METHODS.GET_NETWORKS: - const retrievedNetworks = await retrieveNetworksData(); const currentNetworkId = networksData.find( networkData => networkData.networkId === selectedNetwork!.networkId, )?.networkId; + const networkNamesData = networksData.map(networkData => { + return { + id: networkData.networkId, + name: networkData.networkName, + }; + }); + const formattedResponse = formatJsonRpcResult(id, { - retrievedNetworks, currentNetworkId, + networkNamesData, }); await web3wallet!.respondSessionRequest({ @@ -90,7 +95,10 @@ const App = (): React.JSX.Element => { break; case NETWORK_METHODS.CHANGE_NETWORK: - const network = request.params[0]; + const networkNameData = request.params[0]; + const network = networksData.find( + networkData => networkData.networkId === networkNameData.id, + ); setCurrentIndex(0); setSelectedNetwork(network); -- 2.45.2 From 455703f91cdffc3c8b627cf3803c010360b2e95f Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Thu, 18 Apr 2024 16:51:27 +0530 Subject: [PATCH 54/64] Display pop-up when chains are not supported (#103) * Check for unsupported namespace * Add check for common walletchains and networks chains * Format code * Display unsupported chains * Display Dapp info in modal * Refactor code * Reject request if chains are not supported * Reject sessions * Remove isVisible state --- src/components/PairingModal.tsx | 192 ++++++++++++++++++---------- src/utils/wallet-connect/helpers.ts | 10 ++ 2 files changed, 136 insertions(+), 66 deletions(-) diff --git a/src/components/PairingModal.tsx b/src/components/PairingModal.tsx index e100759..31abc13 100644 --- a/src/components/PairingModal.tsx +++ b/src/components/PairingModal.tsx @@ -24,6 +24,7 @@ const PairingModal = ({ const { accounts, currentIndex } = useAccounts(); const { selectedNetwork, networksData } = useNetworks(); const [isLoading, setIsLoading] = useState(false); + const [chainError, setChainError] = useState(''); const dappName = currentProposal?.params?.proposer?.metadata.name; const url = currentProposal?.params?.proposer?.metadata.url; @@ -96,20 +97,43 @@ const PairingModal = ({ const { optionalNamespaces, requiredNamespaces } = currentProposal.params; - const nameSpaces = await getNamespaces( - optionalNamespaces, - requiredNamespaces, - networksData, - selectedNetwork!, - accounts, - currentIndex, - ); + try { + const nameSpaces = await getNamespaces( + optionalNamespaces, + requiredNamespaces, + networksData, + selectedNetwork!, + accounts, + currentIndex, + ); + setSupportedNamespaces(nameSpaces); + } catch (err) { + setChainError((err as Error).message); - setSupportedNamespaces(nameSpaces); + const { id } = currentProposal; + await web3wallet!.rejectSession({ + id, + reason: getSdkError('UNSUPPORTED_CHAINS'), + }); + setCurrentProposal(undefined); + setWalletConnectData({ + walletConnectMethods: [], + walletConnectEvents: [], + walletConnectChains: [], + }); + } }; getSupportedNamespaces(); - }, [currentProposal, networksData, selectedNetwork, accounts, currentIndex]); + }, [ + currentProposal, + networksData, + selectedNetwork, + accounts, + currentIndex, + setCurrentProposal, + setModalVisible, + ]); const namespaces = useMemo(() => { return ( @@ -152,6 +176,11 @@ const PairingModal = ({ } }; + const handleClose = () => { + setChainError(''); + setModalVisible(false); + }; + const handleReject = async () => { if (currentProposal) { const { id } = currentProposal; @@ -171,10 +200,10 @@ const PairingModal = ({ }; return ( - - - - + + {chainError !== '' ? ( + + {icon && ( <> @@ -190,60 +219,91 @@ const PairingModal = ({ {dappName} {url} - - Connect to this site? - - {walletConnectData.walletConnectMethods.length > 0 && ( - - Chains: - {walletConnectData.walletConnectChains.map(chain => ( - - {chain} - - ))} - - )} - - {walletConnectData.walletConnectMethods.length > 0 && ( - - Methods Requested: - {walletConnectData.walletConnectMethods.map(method => ( - - {method} - - ))} - - )} - - {walletConnectData.walletConnectEvents.length > 0 && ( - - Events Requested: - {walletConnectData.walletConnectEvents.map(event => ( - - {event} - - ))} - - )} + {chainError} + + + - - - - - - - - - + + ) : ( + + + + + + {icon && ( + <> + {icon.endsWith('.svg') ? ( + + + + ) : ( + + )} + + )} + + {dappName} + {url} + + Connect to this site? + + {walletConnectData.walletConnectMethods.length > 0 && ( + + Chains: + {walletConnectData.walletConnectChains.map(chain => ( + + {chain} + + ))} + + )} + + {walletConnectData.walletConnectMethods.length > 0 && ( + + Methods Requested: + {walletConnectData.walletConnectMethods.map(method => ( + + {method} + + ))} + + )} + + {walletConnectData.walletConnectEvents.length > 0 && ( + + Events Requested: + {walletConnectData.walletConnectEvents.map(event => ( + + {event} + + ))} + + )} + + + + + + + + + + + + )} + ); }; diff --git a/src/utils/wallet-connect/helpers.ts b/src/utils/wallet-connect/helpers.ts index 306ff64..a732cc2 100644 --- a/src/utils/wallet-connect/helpers.ts +++ b/src/utils/wallet-connect/helpers.ts @@ -65,6 +65,16 @@ export const getNamespaces = async ( if (!(walletConnectChains.length > 0)) { return; } + // Check for unsupported chains + const networkChains = networksData.map( + network => `${network.namespace}:${network.chainId}`, + ); + if (!walletConnectChains.every(chain => networkChains.includes(chain))) { + const unsupportedChains = walletConnectChains.filter( + chain => !networkChains.includes(chain), + ); + throw new Error(`Unsupported chains : ${unsupportedChains.join(',')}`); + } // Get required networks const requiredNetworks = networksData.filter(network => -- 2.45.2 From cd03fb6e841b2d8d66ea3e3167237f80ba291f7c Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:13:35 +0530 Subject: [PATCH 55/64] Add custom form validation messages (#106) * Fix empty fields and url error msg * Use constants for error msgs --- src/screens/AddNetwork.tsx | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 23cf623..daafb7a 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -24,23 +24,32 @@ import { COSMOS, EIP155, CHAINID_DEBOUNCE_DELAY } from '../utils/constants'; import { getCosmosAccounts } from '../utils/accounts'; import ETH_CHAINS from '../assets/ethereum-chains.json'; +const EMPTY_FIELD_ERROR = 'Field cannot be empty'; +const INVALID_URL_ERROR = 'Invalid URL'; + const ethNetworkDataSchema = z.object({ - chainId: z.string().min(1), - networkName: z.string().min(1), - rpcUrl: z.string().url(), - blockExplorerUrl: z.string().url().or(z.literal('')), - coinType: z.string().regex(/^\d+$/).min(1), - currencySymbol: z.string().min(1), + chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), + networkName: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), + rpcUrl: z.string().url({ message: INVALID_URL_ERROR }), + blockExplorerUrl: z + .string() + .url({ message: INVALID_URL_ERROR }) + .or(z.literal('')), + coinType: z.string().nonempty({ message: EMPTY_FIELD_ERROR }).regex(/^\d+$/), + currencySymbol: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), }); const cosmosNetworkDataSchema = z.object({ - chainId: z.string().min(1), - networkName: z.string().min(1), - rpcUrl: z.string().url(), - blockExplorerUrl: z.string().url().or(z.literal('')), - coinType: z.string().regex(/^\d+$/).min(1), - nativeDenom: z.string().min(1), - addressPrefix: z.string().min(1), + chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), + networkName: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), + rpcUrl: z.string().url({ message: INVALID_URL_ERROR }), + blockExplorerUrl: z + .string() + .url({ message: INVALID_URL_ERROR }) + .or(z.literal('')), + coinType: z.string().nonempty({ message: EMPTY_FIELD_ERROR }).regex(/^\d+$/), + nativeDenom: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), + addressPrefix: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), }); const AddNetwork = () => { -- 2.45.2 From 1172e67f5fd93209e38c48bf7d982c2f6a5cd3c8 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Fri, 19 Apr 2024 11:34:19 +0530 Subject: [PATCH 56/64] Remove configured networks on reset (#105) --- src/components/SelectNetworkType.tsx | 2 +- src/screens/HomeScreen.tsx | 24 +++++++++++++++++++----- src/utils/accounts.ts | 17 +++++++++-------- 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/components/SelectNetworkType.tsx b/src/components/SelectNetworkType.tsx index 32234c7..e26558f 100644 --- a/src/components/SelectNetworkType.tsx +++ b/src/components/SelectNetworkType.tsx @@ -26,7 +26,7 @@ const SelectNetworkType = ({ Select Network Type diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 453e29b..91df6bc 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -32,7 +32,8 @@ const HomeScreen = () => { const { accounts, setAccounts, currentIndex, setCurrentIndex } = useAccounts(); - const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks(); + const { networksData, selectedNetwork, setSelectedNetwork, setNetworksData } = + useNetworks(); const { setActiveSessions } = useWalletConnect(); const navigation = @@ -86,15 +87,21 @@ const HomeScreen = () => { fetchAccounts(); setWalletDialog(true); setPhrase(mnemonic); + setSelectedNetwork(networksData[0]); } }; - const confirmResetWallet = async () => { - await resetWallet(); + const confirmResetWallet = useCallback(async () => { + const updatedNetworks = networksData.filter( + networkData => networkData.isDefault, + ); + setNetworksData(updatedNetworks); + setSelectedNetwork(undefined); setIsWalletCreated(false); setIsWalletCreating(false); setAccounts([]); setCurrentIndex(0); + await resetWallet(updatedNetworks); const sessions = web3wallet!.getActiveSessions(); Object.keys(sessions).forEach(async sessionId => { @@ -106,7 +113,14 @@ const HomeScreen = () => { setActiveSessions({}); hideResetDialog(); - }; + }, [ + networksData, + setAccounts, + setActiveSessions, + setCurrentIndex, + setNetworksData, + setSelectedNetwork, + ]); const updateNetwork = (networkData: NetworksDataState) => { setSelectedNetwork(networkData); @@ -128,7 +142,7 @@ const HomeScreen = () => { Loading... - ) : isWalletCreated ? ( + ) : isWalletCreated && selectedNetwork ? ( <> diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 0eff299..e46cca0 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -186,13 +186,13 @@ const retrieveAccounts = async ( `accountIndices/${currentNetworkData.namespace}:${currentNetworkData.chainId}`, ); const accountIndices = accountIndicesServer && accountIndicesServer.password; - - const loadedAccounts = accountIndices - ? await retrieveAccountsForNetwork( - `${currentNetworkData.namespace}:${currentNetworkData.chainId}`, - accountIndices, - ) - : undefined; + const loadedAccounts = + accountIndices !== false + ? await retrieveAccountsForNetwork( + `${currentNetworkData.namespace}:${currentNetworkData.chainId}`, + accountIndices, + ) + : undefined; return loadedAccounts; }; @@ -225,12 +225,13 @@ const retrieveSingleAccount = async ( return loadedAccounts.find(account => account.address === address); }; -const resetWallet = async () => { +const resetWallet = async (networks: NetworksDataState[]) => { try { await Promise.all([ resetInternetCredentials('mnemonicServer'), resetKeyServers(EIP155), resetKeyServers(COSMOS), + setInternetCredentials('networks', '_', JSON.stringify(networks)), ]); } catch (error) { console.error('Error resetting wallet:', error); -- 2.45.2 From 8c0751f84bb2f21316db40fbda50ce5b17b40db2 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Fri, 19 Apr 2024 16:21:21 +0530 Subject: [PATCH 57/64] Add details to setup wallet connect in readme (#58) * Add details to setup wallet connect in readme * Add hyperlinks for wallet connect terms * Handle review changes * Add keystore data structure and troubleshooting steps in readme * Update readme * Add pre-commit lint hook * Resolve linter warnings * Handle review changes * Fix heading level in readme --- .husky/pre-commit | 1 + README.md | 32 +++- development.md | 160 ++++++++++++++++++ package.json | 6 +- src/components/SelectNetworkType.tsx | 8 +- src/styles/stylesheet.js | 4 + .../wallet-connect/WalletConnectUtils.tsx | 2 +- yarn.lock | 5 + 8 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 .husky/pre-commit create mode 100644 development.md diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 0000000..39abe4d --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +yarn lint diff --git a/README.md b/README.md index 5027bd4..37c8aca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # laconic-wallet -### Install +## Install - Install [Node](https://nodejs.org/en/download/package-manager/) @@ -87,7 +87,7 @@ ``` cp .env.example .env ``` - + - In [.env](./.env) file add WalletConnect project id. You can generate your own ProjectId at https://cloud.walletconnect.com ``` @@ -134,3 +134,31 @@ 5. Press `a` to run the application on android You should see both the apps running on your emulator or physical device. + +## Flow for the app + +- User scans QR Code on dApp from wallet to connect + +- After clicking on pair button, dApp emits an event 'session_proposal' + +- Wallet listens to this event and opens a modal to either accept or reject the proposal + + - Modal shows information about methods, chains and events the dApp is requesting for + + - This information is taken from [namespaces](https://docs.walletconnect.com/advanced/glossary#namespaces) object received from dApp + +- On accepting, wallet sends the [namespaces](https://docs.walletconnect.com/advanced/glossary#namespaces) object with information about the accounts that are present in the wallet in response + +- Once [session](https://docs.walletconnect.com/advanced/glossary#session) is established, it is shown in the active sessions page + +- In this way, wallet can connect to multiple dApps + +## Troubleshooting + +- To clean the buid + + ``` + cd adroid + + ./gradlew clean + ``` diff --git a/development.md b/development.md new file mode 100644 index 0000000..a4eb585 --- /dev/null +++ b/development.md @@ -0,0 +1,160 @@ +# Development + +## WalletConnect details + +- Docs - https://docs.walletconnect.com/api/sign/overview + +- Doc for terminologies - https://docs.walletconnect.com/advanced/glossary + +- Function for creating web3 wallet + + ```js + export let web3wallet: IWeb3Wallet; + export let core: ICore; + + export async function createWeb3Wallet() { + const core = new Core({ + projectId: Config.WALLET_CONNECT_PROJECT_ID, + }); + + web3wallet = await Web3Wallet.init({ + core, + metadata: { + name: 'Laconic Wallet', + description: 'Laconic Wallet', + url: 'https://wallet.laconic.com/', + icons: ['https://avatars.githubusercontent.com/u/92608123'], + }, + }); + } + ``` + +- Hook used for intializing web3 wallet + + ```js + export default function useInitialization() { + const [initialized, setInitialized] = useState(false); + + const onInitialize = useCallback(async () => { + try { + await createWeb3Wallet(); + setInitialized(true); + } catch (err: unknown) { + console.log('Error for initializing', err); + } + }, []); + + useEffect(() => { + if (!initialized) { + onInitialize(); + } + }, [initialized, onInitialize]); + + return initialized; + } + ``` + +- Once user clicks on pair, this function is triggered + + ```js + export async function web3WalletPair(params: { uri: string }) { + return await web3wallet.core.pairing.pair({ uri: params.uri }); + } + ``` + +- In a useEffect, we keep on listening to events that are emitted by dapp and do actions based on it + + ```js + useEffect(() => { + web3wallet?.on('session_proposal', onSessionProposal); + web3wallet?.on('session_request', onSessionRequest); + web3wallet?.on('session_delete', onSessionDelete); + return () => { + web3wallet?.off('session_proposal', onSessionProposal); + web3wallet?.off('session_request', onSessionRequest); + web3wallet?.off('session_delete', onSessionDelete); + } + }) + ``` +- Signing messages + + - Cosmos methods info + + - The signDoc format for signAmino and signDirect is different + + - Reference - https://docs.leapwallet.io/cosmos/for-dapps-connect-to-leap/api-reference + + - For signDirect, the message is protobuf encoded , hence to sign it , we have to first convert it to uint8Array + + ```js + const bodyBytesArray = Uint8Array.from( + Buffer.from(request.params.signDoc.bodyBytes, 'hex'), + ); + const authInfoBytesArray = Uint8Array.from( + Buffer.from(request.params.signDoc.authInfoBytes, 'hex'), + ); + ``` + + - This will give us correct signature + +## Data structure of keystore + + ```json + { + // Accounts data -> hdpath, privateKey, publicKey, address + "accounts/eip155:1/0":{ + "username": "", + "password": "m/44'/60'/0'/0/0,0x0654623fe7a0e3d74f518e22818c1cfd58517e80a232df5d6d20a3afd99fd3bb,0x02cced178c903835bb29e337fa227ba0204a3285eb35797b766ed975b478b4beb6,0xe5fA0Dd92659e287e5e3Fa582Ee20fcf74fb1116" + }, + "accounts/cosmos:cosmoshub-4/0":{ + "username": "", + "password": "m/44'/118'/0'/0/0,0xbb9ccc3029178a61ba847c22108318ba119220451c99e6358efd7b85d7d49ed5,0x03a8002f56f99126f930ca3ac1963731e3f08df30822031db7c1dd50851bdfcc3c,cosmos1s5a5ls3d6xmks5fc8tst9x7tx2jv8mfjmn0aq8" + }, + + // Networks Data + "networks":{ + "username":"", + "password":[ + { + "networkId":"1", + "namespace": "eip155", + "chainId": "1", + "networkName": "Ethereum", + "rpcUrl": "https://cloudflare-eth.com/", + "currencySymbol": "ETH", + "coinType": "60" + }, + { + "networkId":"2", + "namespace": "cosmos", + "chainId": "theta-testnet-001", + "networkName": "Cosmos Hub Testnet", + "rpcUrl": "https://rpc-t.cosmos.nodestake.top", + "nativeDenom": "ATOM", + "addressPrefix": "cosmos", + "coinType": "118" + } + ] + }, + + // Stores count of total accounts for specific chain + "addAccountCounter/eip155:1":{ + "username": "", + "password": "3" + }, + "addAccountCounter/cosmos:cosmoshub-4":{ + "username": "", + "password": "3" + }, + + // Stores ids of accounts for specific chain + "accountIndices/eip155:1":{ + "username": "", + "password": "0,1,2" + }, + "accountIndices/cosmos:cosmoshub-4":{ + "username": "", + "password": "0,1,2" + } + } + ``` diff --git a/package.json b/package.json index b952e15..61705ec 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,11 @@ "scripts": { "android": "react-native run-android", "ios": "react-native run-ios", - "lint": "eslint .", + "lint": "eslint . --max-warnings=0", "start": "react-native start", "test": "jest", - "postinstall": "patch-package" + "postinstall": "patch-package", + "prepare": "husky" }, "dependencies": { "@cosmjs/amino": "^0.32.3", @@ -66,6 +67,7 @@ "babel-jest": "^29.6.3", "babel-plugin-module-resolver": "^5.0.0", "eslint": "^8.19.0", + "husky": "^9.0.11", "jest": "^29.6.3", "metro-babel7-plugin-react-transform": "^0.54.1", "prettier": "2.8.8", diff --git a/src/components/SelectNetworkType.tsx b/src/components/SelectNetworkType.tsx index e26558f..dc76fef 100644 --- a/src/components/SelectNetworkType.tsx +++ b/src/components/SelectNetworkType.tsx @@ -23,13 +23,7 @@ const SelectNetworkType = ({ return ( - - Select Network Type - + Select Network Type Date: Fri, 19 Apr 2024 16:25:53 +0530 Subject: [PATCH 58/64] Implement Edit network form (#107) * Add edit network form * Set selected network when networks are updated * Disable buttons and add spinner after submitting * Display previous values in Edit network form * Use error msgs form constants file * Reset default networks on reset --- src/App.tsx | 8 ++ src/components/Accounts.tsx | 15 ++++ src/context/NetworksContext.tsx | 21 +++-- src/screens/AddNetwork.tsx | 22 +++-- src/screens/EditNetwork.tsx | 148 ++++++++++++++++++++++++++++++++ src/screens/HomeScreen.tsx | 10 +-- src/styles/stylesheet.js | 4 +- src/types.ts | 3 + src/utils/accounts.ts | 4 +- src/utils/constants.ts | 3 + 10 files changed, 213 insertions(+), 25 deletions(-) create mode 100644 src/screens/EditNetwork.tsx diff --git a/src/App.tsx b/src/App.tsx index 5b8d70c..d08f201 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -28,6 +28,7 @@ import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data'; import { getSignParamsMessage } from './utils/wallet-connect/helpers'; import ApproveTransaction from './screens/ApproveTransaction'; import AddNetwork from './screens/AddNetwork'; +import EditNetwork from './screens/EditNetwork'; import { COSMOS, EIP155 } from './utils/constants'; import { useNetworks } from './context/NetworksContext'; import { NETWORK_METHODS } from './utils/wallet-connect/common-data'; @@ -281,6 +282,13 @@ const App = (): React.JSX.Element => { title: 'Add Network', }} /> + { + + { + navigation.navigate('EditNetwork', { + selectedNetwork: selectedNetwork!, + }); + }}> + + Edit Network + + + + {!selectedNetwork!.isDefault && ( void; networksData: NetworksDataState[]; setNetworksData: React.Dispatch>; networkType: string; @@ -16,8 +14,6 @@ const NetworksContext = createContext<{ React.SetStateAction >; }>({ - currentIndex: 0, - setCurrentIndex: () => {}, networksData: [], setNetworksData: () => {}, networkType: '', @@ -33,7 +29,6 @@ const useNetworks = () => { const NetworksProvider = ({ children }: { children: any }) => { const [networksData, setNetworksData] = useState([]); - const [currentIndex, setCurrentIndex] = useState(0); const [networkType, setNetworkType] = useState(EIP155); const [selectedNetwork, setSelectedNetwork] = useState(); @@ -50,14 +45,22 @@ const NetworksProvider = ({ children }: { children: any }) => { setSelectedNetwork(retrievedNewNetworks[0]); }; - fetchData(); - }, []); + if (networksData.length === 0) { + fetchData(); + } + }, [networksData]); + + useEffect(() => { + setSelectedNetwork(prevSelectedNetwork => { + return networksData.find( + networkData => networkData.networkId === prevSelectedNetwork?.networkId, + ); + }); + }, [networksData]); return ( { const { setNetworksData } = useNetworks(); const [namespace, setNamespace] = useState(EIP155); + const [isLoading, setIsLoading] = useState(false); const networksFormDataSchema = namespace === EIP155 ? ethNetworkDataSchema : cosmosNetworkDataSchema; @@ -114,6 +118,7 @@ const AddNetwork = () => { const submit = useCallback( async (data: z.infer) => { + setIsLoading(true); const newNetworkData = { ...data, namespace, @@ -176,6 +181,7 @@ const AddNetwork = () => { ), ]); + setIsLoading(false); navigation.navigate('Laconic'); }, [navigation, namespace, setNetworksData], @@ -358,8 +364,12 @@ const AddNetwork = () => { /> )} - ); diff --git a/src/screens/EditNetwork.tsx b/src/screens/EditNetwork.tsx new file mode 100644 index 0000000..ef787c8 --- /dev/null +++ b/src/screens/EditNetwork.tsx @@ -0,0 +1,148 @@ +import React, { useCallback, useState } from 'react'; +import { ScrollView, View } from 'react-native'; +import { useForm, Controller } from 'react-hook-form'; +import { TextInput, Button, HelperText, Text } from 'react-native-paper'; +import { setInternetCredentials } from 'react-native-keychain'; +import { z } from 'zod'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { + NativeStackNavigationProp, + NativeStackScreenProps, +} from '@react-navigation/native-stack'; +import { useNavigation } from '@react-navigation/native'; + +import { StackParamsList } from '../types'; +import styles from '../styles/stylesheet'; +import { retrieveNetworksData } from '../utils/accounts'; +import { useNetworks } from '../context/NetworksContext'; +import { EMPTY_FIELD_ERROR, INVALID_URL_ERROR } from '../utils/constants'; + +const networksFormDataSchema = z.object({ + networkName: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), + rpcUrl: z.string().url({ message: INVALID_URL_ERROR }), + blockExplorerUrl: z + .string() + .url({ message: INVALID_URL_ERROR }) + .or(z.literal('')), +}); + +type EditNetworkProps = NativeStackScreenProps; + +const EditNetwork = ({ route }: EditNetworkProps) => { + const [isLoading, setIsLoading] = useState(false); + + const { setNetworksData } = useNetworks(); + const navigation = + useNavigation>(); + + const { + control, + formState: { errors }, + handleSubmit, + } = useForm>({ + mode: 'onChange', + resolver: zodResolver(networksFormDataSchema), + }); + const networkData = route.params.selectedNetwork; + + const submit = useCallback( + async (data: z.infer) => { + setIsLoading(true); + + const retrievedNetworksData = await retrieveNetworksData(); + + const newNetworkData = { ...networkData, ...data }; + const index = retrievedNetworksData.findIndex( + network => network.networkId === networkData.networkId, + ); + + retrievedNetworksData.splice(index, 1, newNetworkData); + + await setInternetCredentials( + 'networks', + '_', + JSON.stringify(retrievedNetworksData), + ); + + setNetworksData(retrievedNetworksData); + + setIsLoading(false); + navigation.navigate('Laconic'); + }, + [networkData, navigation, setNetworksData], + ); + + return ( + + + + Edit {networkData?.networkName} details + + + ( + <> + onChange(textValue)} + /> + {errors.networkName?.message} + + )} + /> + ( + <> + onChange(textValue)} + /> + {errors.rpcUrl?.message} + + )} + /> + + ( + <> + onChange(textValue)} + /> + + {errors.blockExplorerUrl?.message} + + + )} + /> + + + ); +}; + +export default EditNetwork; diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index 91df6bc..1e09b37 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -92,16 +92,13 @@ const HomeScreen = () => { }; const confirmResetWallet = useCallback(async () => { - const updatedNetworks = networksData.filter( - networkData => networkData.isDefault, - ); - setNetworksData(updatedNetworks); - setSelectedNetwork(undefined); setIsWalletCreated(false); setIsWalletCreating(false); setAccounts([]); setCurrentIndex(0); - await resetWallet(updatedNetworks); + setNetworksData([]); + setSelectedNetwork(undefined); + await resetWallet(); const sessions = web3wallet!.getActiveSessions(); Object.keys(sessions).forEach(async sessionId => { @@ -114,7 +111,6 @@ const HomeScreen = () => { hideResetDialog(); }, [ - networksData, setAccounts, setActiveSessions, setCurrentIndex, diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index e925b5d..e5a579e 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -204,7 +204,9 @@ const styles = StyleSheet.create({ }, subHeading: { textAlign: 'center', - fontWeight: '600', + fontWeight: 'bold', + marginBottom: 10, + marginTop: 10, }, centerText: { textAlign: 'center', diff --git a/src/types.ts b/src/types.ts index f2f4975..5aafdb3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -26,6 +26,9 @@ export type StackParamsList = { WalletConnect: undefined; AddSession: undefined; AddNetwork: undefined; + EditNetwork: { + selectedNetwork: NetworksDataState; + }; }; export type Account = { diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index e46cca0..cfa0d52 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -225,13 +225,13 @@ const retrieveSingleAccount = async ( return loadedAccounts.find(account => account.address === address); }; -const resetWallet = async (networks: NetworksDataState[]) => { +const resetWallet = async () => { try { await Promise.all([ resetInternetCredentials('mnemonicServer'), resetKeyServers(EIP155), resetKeyServers(COSMOS), - setInternetCredentials('networks', '_', JSON.stringify(networks)), + setInternetCredentials('networks', '_', JSON.stringify([])), ]); } catch (error) { console.error('Error resetting wallet:', error); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 87c304b..aea8e6a 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -26,3 +26,6 @@ export const DEFAULT_NETWORKS = [ ]; export const CHAINID_DEBOUNCE_DELAY = 250; + +export const EMPTY_FIELD_ERROR = 'Field cannot be empty'; +export const INVALID_URL_ERROR = 'Invalid URL'; -- 2.45.2 From ad202f46fc47ecebcf10c3dc47634eff708013a0 Mon Sep 17 00:00:00 2001 From: IshaVenikar <145848618+IshaVenikar@users.noreply.github.com> Date: Mon, 22 Apr 2024 10:53:28 +0530 Subject: [PATCH 59/64] Fix Edit network form (#108) * Make block explorer url optional * Make review changes * Remove log --- src/screens/AddNetwork.tsx | 11 ++++------- src/screens/EditNetwork.tsx | 15 +++++---------- src/utils/constants.ts | 2 ++ 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index c1ab657..0abf8d7 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -62,14 +62,13 @@ const AddNetwork = () => { const { setNetworksData } = useNetworks(); const [namespace, setNamespace] = useState(EIP155); - const [isLoading, setIsLoading] = useState(false); const networksFormDataSchema = namespace === EIP155 ? ethNetworkDataSchema : cosmosNetworkDataSchema; const { control, - formState: { errors }, + formState: { errors, isSubmitting }, handleSubmit, setValue, reset, @@ -118,7 +117,6 @@ const AddNetwork = () => { const submit = useCallback( async (data: z.infer) => { - setIsLoading(true); const newNetworkData = { ...data, namespace, @@ -181,7 +179,6 @@ const AddNetwork = () => { ), ]); - setIsLoading(false); navigation.navigate('Laconic'); }, [navigation, namespace, setNetworksData], @@ -366,10 +363,10 @@ const AddNetwork = () => { )} ); diff --git a/src/screens/EditNetwork.tsx b/src/screens/EditNetwork.tsx index ef787c8..63c48c0 100644 --- a/src/screens/EditNetwork.tsx +++ b/src/screens/EditNetwork.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from 'react'; +import React, { useCallback } from 'react'; import { ScrollView, View } from 'react-native'; import { useForm, Controller } from 'react-hook-form'; import { TextInput, Button, HelperText, Text } from 'react-native-paper'; @@ -30,15 +30,13 @@ const networksFormDataSchema = z.object({ type EditNetworkProps = NativeStackScreenProps; const EditNetwork = ({ route }: EditNetworkProps) => { - const [isLoading, setIsLoading] = useState(false); - const { setNetworksData } = useNetworks(); const navigation = useNavigation>(); const { control, - formState: { errors }, + formState: { errors, isSubmitting }, handleSubmit, } = useForm>({ mode: 'onChange', @@ -48,8 +46,6 @@ const EditNetwork = ({ route }: EditNetworkProps) => { const submit = useCallback( async (data: z.infer) => { - setIsLoading(true); - const retrievedNetworksData = await retrieveNetworksData(); const newNetworkData = { ...networkData, ...data }; @@ -67,7 +63,6 @@ const EditNetwork = ({ route }: EditNetworkProps) => { setNetworksData(retrievedNetworksData); - setIsLoading(false); navigation.navigate('Laconic'); }, [networkData, navigation, setNetworksData], @@ -136,10 +131,10 @@ const EditNetwork = ({ route }: EditNetworkProps) => { /> ); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index aea8e6a..4170a6c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -9,6 +9,7 @@ export const DEFAULT_NETWORKS = [ networkName: EIP155_CHAINS['eip155:1'].name, namespace: EIP155, rpcUrl: EIP155_CHAINS['eip155:1'].rpc, + blockExplorerUrl: '', currencySymbol: 'ETH', coinType: '60', isDefault: true, @@ -18,6 +19,7 @@ export const DEFAULT_NETWORKS = [ networkName: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].name, namespace: COSMOS, rpcUrl: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].rpc, + blockExplorerUrl: '', nativeDenom: 'uatom', addressPrefix: 'cosmos', coinType: '118', -- 2.45.2 From b1a0831e7892a74fa56763babcb7039d4bae16e9 Mon Sep 17 00:00:00 2001 From: shreerang6921 <68148922+shreerang6921@users.noreply.github.com> Date: Tue, 23 Apr 2024 10:22:30 +0530 Subject: [PATCH 60/64] Take gas limit and fees values from user while approving transaction (#109) * Take gas limit and fees from user * Update text input ui * Use gasPrice from networks data * Use default gas limit from env * Use default gas price if not found in registry * Remove appended denom in gas price * Use gas limit from env * Show error dialog when transaction fails * Calculate gas limit and gas price if not received from dapp * Update example env * Improve syntax --------- Co-authored-by: IshaVenikar --- .env.example | 2 + react-native-config.d.ts | 2 + src/components/TxErrorDialog.tsx | 28 +++ src/screens/AddNetwork.tsx | 36 ++++ src/screens/ApproveTransaction.tsx | 197 ++++++++++++++---- src/screens/EditNetwork.tsx | 29 ++- src/screens/SignRequest.tsx | 4 +- src/styles/stylesheet.js | 1 + src/types.ts | 1 + src/utils/accounts.ts | 5 +- src/utils/constants.ts | 1 + .../wallet-connect/WalletConnectRequests.ts | 58 +++--- 12 files changed, 290 insertions(+), 74 deletions(-) create mode 100644 src/components/TxErrorDialog.tsx diff --git a/.env.example b/.env.example index c039d12..9687d13 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,3 @@ WALLET_CONNECT_PROJECT_ID= +DEFAULT_GAS_LIMIT= +DEFAULT_GAS_PRICE= diff --git a/react-native-config.d.ts b/react-native-config.d.ts index db50fa4..8f78aab 100644 --- a/react-native-config.d.ts +++ b/react-native-config.d.ts @@ -2,6 +2,8 @@ declare module 'react-native-config' { export interface NativeConfig { WALLET_CONNECT_PROJECT_ID: string; + DEFAULT_GAS_LIMIT: string; + DEFAULT_GAS_PRICE: string; } export const Config: NativeConfig; diff --git a/src/components/TxErrorDialog.tsx b/src/components/TxErrorDialog.tsx new file mode 100644 index 0000000..e110760 --- /dev/null +++ b/src/components/TxErrorDialog.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { Button, Dialog, Portal, Text } from 'react-native-paper'; + +const TxErrorDialog = ({ + error, + visible, + hideDialog, +}: { + error: string; + visible: boolean; + hideDialog: () => void; +}) => { + return ( + + + Error sending transaction + + {error} + + + + + + + ); +}; + +export default TxErrorDialog; diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 0abf8d7..a2b5947 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -53,6 +53,10 @@ const cosmosNetworkDataSchema = z.object({ coinType: z.string().nonempty({ message: EMPTY_FIELD_ERROR }).regex(/^\d+$/), nativeDenom: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), addressPrefix: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), + gasPrice: z + .string() + .nonempty({ message: EMPTY_FIELD_ERROR }) + .regex(/^\d+(\.\d+)?$/), }); const AddNetwork = () => { @@ -113,6 +117,13 @@ const AddNetwork = () => { setValue('addressPrefix', cosmosChainDetails.bech32_prefix); setValue('coinType', String(cosmosChainDetails.slip44 ?? '118')); setValue('nativeDenom', cosmosChainDetails.fees?.fee_tokens[0].denom || ''); + setValue( + 'gasPrice', + String( + cosmosChainDetails.fees?.fee_tokens[0].average_gas_price || + String(process.env.DEFAULT_GAS_PRICE), + ), + ); }, CHAINID_DEBOUNCE_DELAY); const submit = useCallback( @@ -359,6 +370,31 @@ const AddNetwork = () => { )} /> + ( + <> + + + { + ( + errors as FieldErrors< + z.infer + > + ).gasPrice?.message + } + + + )} + /> )}