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 <shreerangkale@gmail.com>
This commit is contained in:
IshaVenikar 2024-04-08 14:34:47 +05:30 committed by Nabarun Gogoi
parent e4fe88939c
commit 23fa5415ae
9 changed files with 202 additions and 141 deletions

View File

@ -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<NativeStackNavigationProp<StackParamsList>>();
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

View File

@ -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 = ({
<Text variant="bodyMedium">{url}</Text>
<View style={styles.marginVertical8} />
<Text variant="titleMedium">Connect to this site?</Text>
<Text variant="titleMedium">Chains:</Text>
{walletConnectData.walletConnectChains.map(chain => (
<Text style={styles.centerText} key={chain}>
{chain}
</Text>
))}
<View style={styles.marginVertical8}>
<Text variant="titleMedium">Methods Requested:</Text>
{walletConnectData.walletConnectMethods.map(method => (
<Text style={styles.centerText} key={method}>
{method}
</Text>
))}
</View>
<View style={styles.marginVertical8}>
<Text variant="titleMedium">Events Requested:</Text>
{walletConnectData.walletConnectEvents.map(event => (
<Text style={styles.centerText} key={event}>
{event}
</Text>
))}
</View>
{walletConnectData.walletConnectMethods.length > 0 && (
<View>
<Text variant="titleMedium">Chains:</Text>
{walletConnectData.walletConnectChains.map(chain => (
<Text style={styles.centerText} key={chain}>
{chain}
</Text>
))}
</View>
)}
{walletConnectData.walletConnectMethods.length > 0 && (
<View style={styles.marginVertical8}>
<Text variant="titleMedium">Methods Requested:</Text>
{walletConnectData.walletConnectMethods.map(method => (
<Text style={styles.centerText} key={method}>
{method}
</Text>
))}
</View>
)}
{walletConnectData.walletConnectEvents.length > 0 && (
<View style={styles.marginVertical8}>
<Text variant="titleMedium">Events Requested:</Text>
{walletConnectData.walletConnectEvents.map(event => (
<Text style={styles.centerText} key={event}>
{event}
</Text>
))}
</View>
)}
</View>
</ScrollView>

View File

@ -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<NetworksDataState[]>([
{
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<number>(0);
const [networkType, setNetworkType] = useState<string>('eth');
const [currentChainId, setCurrentChainId] = useState<string>(
networksData[0].chainId,
);
return (
<AccountsContext.Provider
value={{
@ -66,6 +73,8 @@ const AccountsProvider = ({ children }: { children: any }) => {
setNetworksData,
networkType,
setNetworkType,
currentChainId,
setCurrentChainId,
}}>
{children}
</AccountsContext.Provider>

View File

@ -28,15 +28,16 @@ const AddNetwork = () => {
const [networkType, setNetworkType] = useState<string>('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
<ScrollView contentContainerStyle={styles.addNetwork}>
<ScrollView contentContainerStyle={styles.signPage}>
<Controller
control={control}
defaultValue=""

View File

@ -27,8 +27,6 @@ import {
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
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<
@ -114,6 +112,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
}
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) => {
</View>
</View>
<DataBox
label={`Balance ${
network === 'eth' ? '(Wei)' : `(${COSMOS_DENOM})`
}`}
label={`Balance ${requestedChain!.nativeDenom!}`}
data={
balance === '' || balance === undefined
? 'Loading balance...'
@ -253,17 +250,13 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
<View style={styles.approveTransaction}>
<DataBox label="To" data={transaction.to!} />
<DataBox
label={`Amount ${
network === 'eth' ? '(Wei)' : `(${COSMOS_DENOM})`
}`}
label={`Amount ${requestedChain!.nativeDenom!}`}
data={BigNumber.from(
transaction.value?.toString(),
).toString()}
/>
<DataBox
label={`Gas Fees ${
network === 'eth' ? '(Wei)' : `(${COSMOS_DENOM})`
}`}
label={`Gas Fees ${requestedChain!.nativeDenom!}`}
data={gasFees!}
/>
{network === 'eth' && (

View File

@ -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<boolean>(false);
const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false);
const [walletDialog, setWalletDialog] = useState<boolean>(false);
const [currentChainId, setCurrentChainId] = useState<string>(
networksData[0].chainId,
);
const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false);
const [isAccountsFetched, setIsAccountsFetched] = useState<boolean>(false);
const [phrase, setPhrase] = useState('');

View File

@ -1 +0,0 @@
export const COSMOS_DENOM = 'uatom';

View File

@ -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',
};

View File

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