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] 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(), );