forked from cerc-io/laconic-wallet
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 <shreerangkale@gmail.com> * 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>
This commit is contained in:
parent
94bd8b6480
commit
3809ce88b1
14
src/App.tsx
14
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<StackParamsList>();
|
||||
|
||||
@ -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: () => <Text variant="titleLarge">Sign Message</Text>,
|
||||
}}
|
||||
initialParams={{ selectedNetwork: 'Ethereum' }}
|
||||
/>
|
||||
<Stack.Screen
|
||||
name="SignRequest"
|
||||
|
@ -16,17 +16,14 @@ 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';
|
||||
import { COSMOS, EIP155 } from '../utils/constants';
|
||||
|
||||
const Accounts = ({
|
||||
network,
|
||||
currentIndex,
|
||||
updateIndex: updateId,
|
||||
}: AccountsProps) => {
|
||||
const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => {
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||
|
||||
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 => (
|
||||
<List.Item
|
||||
key={account.counterId}
|
||||
title={`Account ${account.counterId + 1}`}
|
||||
key={account.index}
|
||||
title={`Account ${account.index + 1}`}
|
||||
onPress={() => {
|
||||
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}
|
||||
/>
|
||||
<List.Accordion
|
||||
@ -202,20 +171,26 @@ const Accounts = ({
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
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
|
||||
</Button>
|
||||
</View>
|
||||
|
||||
<AccountDetails account={selectedAccounts[currentIndex]} />
|
||||
<AccountDetails account={accounts[currentIndex]} />
|
||||
|
||||
<View style={styles.signLink}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
navigation.navigate('SignMessage', {
|
||||
selectedNetwork: network,
|
||||
accountInfo: selectedAccounts[currentIndex],
|
||||
selectedNamespace: selectedNetwork!.namespace,
|
||||
selectedChainId: selectedNetwork!.chainId,
|
||||
accountInfo: accounts[currentIndex],
|
||||
});
|
||||
}}>
|
||||
<Text
|
||||
|
@ -3,7 +3,7 @@ import { ScrollView, View, Text } from 'react-native';
|
||||
import { Button, TextInput } from 'react-native-paper';
|
||||
|
||||
import { addAccountFromHDPath } from '../utils/accounts';
|
||||
import { Account, PathState } from '../types';
|
||||
import { Account, NetworksDataState, PathState } from '../types';
|
||||
import styles from '../styles/stylesheet';
|
||||
|
||||
const HDPath = ({
|
||||
@ -11,11 +11,13 @@ const HDPath = ({
|
||||
updateAccounts,
|
||||
updateIndex,
|
||||
hideDialog,
|
||||
selectedNetwork,
|
||||
}: {
|
||||
pathCode: string;
|
||||
updateIndex: (index: number) => void;
|
||||
updateAccounts: (account: Account) => void;
|
||||
hideDialog: () => void;
|
||||
selectedNetwork: NetworksDataState;
|
||||
}) => {
|
||||
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
||||
const [path, setPath] = useState<PathState>({
|
||||
@ -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) {
|
||||
|
@ -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 (
|
||||
<Portal>
|
||||
<Dialog visible={visible} onDismiss={hideDialog}>
|
||||
<Dialog.Title>Add account from HD path</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<HDPath
|
||||
selectedNetwork={selectedNetwork!}
|
||||
pathCode={pathCode}
|
||||
updateIndex={updateIndex}
|
||||
updateAccounts={updateAccounts}
|
||||
|
@ -6,7 +6,7 @@ import mergeWith from 'lodash/mergeWith';
|
||||
|
||||
import { buildApprovedNamespaces, getSdkError } from '@walletconnect/utils';
|
||||
|
||||
import { AccountsState, PairingModalProps } from '../types';
|
||||
import { PairingModalProps } from '../types';
|
||||
import styles from '../styles/stylesheet';
|
||||
import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||
import { useAccounts } from '../context/AccountsContext';
|
||||
@ -14,6 +14,7 @@ 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';
|
||||
import { COSMOS, EIP155 } from '../utils/constants';
|
||||
|
||||
const PairingModal = ({
|
||||
visible,
|
||||
@ -23,7 +24,7 @@ const PairingModal = ({
|
||||
setToastVisible,
|
||||
}: PairingModalProps) => {
|
||||
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 (
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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<AccountsState>({
|
||||
ethAccounts: [],
|
||||
cosmosAccounts: [],
|
||||
});
|
||||
const [accounts, setAccounts] = useState<Account[]>([]);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||
const [networkType, setNetworkType] = useState<string>('eth');
|
||||
|
||||
return (
|
||||
<AccountsContext.Provider
|
||||
@ -38,8 +30,6 @@ const AccountsProvider = ({ children }: { children: any }) => {
|
||||
setAccounts,
|
||||
currentIndex,
|
||||
setCurrentIndex,
|
||||
networkType,
|
||||
setNetworkType,
|
||||
}}>
|
||||
{children}
|
||||
</AccountsContext.Provider>
|
||||
|
@ -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<React.SetStateAction<NetworksDataState[]>>;
|
||||
networkType: string;
|
||||
setNetworkType: (networkType: string) => void;
|
||||
currentChainId?: string;
|
||||
setCurrentChainId: (currentChainId: string) => void;
|
||||
selectedNetwork?: NetworksDataState;
|
||||
setSelectedNetwork: React.Dispatch<
|
||||
React.SetStateAction<NetworksDataState | undefined>
|
||||
>;
|
||||
}>({
|
||||
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<NetworksDataState[]>([]);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||
const [networkType, setNetworkType] = useState<string>('eth');
|
||||
const [currentChainId, setCurrentChainId] = useState<string>();
|
||||
const [networkType, setNetworkType] = useState<string>(EIP155);
|
||||
const [selectedNetwork, setSelectedNetwork] = useState<NetworksDataState>();
|
||||
|
||||
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}
|
||||
</NetworksContext.Provider>
|
||||
|
@ -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<string>('eth');
|
||||
const [namespace, setNamespace] = useState<string>(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 = () => {
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="coinType"
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, onBlur, value } }) => (
|
||||
<>
|
||||
<TextInput
|
||||
mode="outlined"
|
||||
value={value}
|
||||
label="Coin Type"
|
||||
onBlur={onBlur}
|
||||
onChangeText={value => onChange(value)}
|
||||
/>
|
||||
<HelperText type="error">{errors.coinType?.message}</HelperText>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<SelectNetworkType updateNetworkType={updateNetworkType} />
|
||||
{networkType === 'eth' ? (
|
||||
{namespace === EIP155 ? (
|
||||
<Controller
|
||||
control={control}
|
||||
name="currencySymbol"
|
||||
@ -184,23 +258,6 @@ const AddNetwork = () => {
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="coinType"
|
||||
defaultValue=""
|
||||
render={({ field: { onChange, onBlur, value } }) => (
|
||||
<>
|
||||
<TextInput
|
||||
mode="outlined"
|
||||
value={value}
|
||||
label="Coin Type"
|
||||
onBlur={onBlur}
|
||||
onChangeText={value => onChange(value)}
|
||||
/>
|
||||
<HelperText type="error">{errors.coinType?.message}</HelperText>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Button
|
||||
|
@ -28,6 +28,7 @@ import { web3wallet } from '../utils/wallet-connect/WalletConnectUtils';
|
||||
import DataBox from '../components/DataBox';
|
||||
import { getPathKey } from '../utils/misc';
|
||||
import { useNetworks } from '../context/NetworksContext';
|
||||
import { COSMOS, EIP155 } from '../utils/constants';
|
||||
|
||||
type SignRequestProps = NativeStackScreenProps<
|
||||
StackParamsList,
|
||||
@ -41,7 +42,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
const requestName = requestSession.peer.metadata.name;
|
||||
const requestIcon = requestSession.peer.metadata.icons[0];
|
||||
const requestURL = requestSession.peer.metadata.url;
|
||||
const network = route.params.network;
|
||||
// TODO: Remove and access namespace from requestEvent
|
||||
const transaction = route.params.transaction;
|
||||
const requestEvent = route.params.requestEvent;
|
||||
const chainId = requestEvent.params.chainId;
|
||||
@ -54,18 +55,53 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
useState<SigningStargateClient>();
|
||||
|
||||
const requestedChain = networksData.find(
|
||||
networkData => networkData.chainId === chainId,
|
||||
networkData =>
|
||||
`${networkData.namespace}:${networkData.chainId}` === chainId,
|
||||
);
|
||||
const namespace = requestedChain!.namespace;
|
||||
|
||||
useEffect(() => {
|
||||
if (namespace !== COSMOS) {
|
||||
return;
|
||||
}
|
||||
|
||||
const setClient = async () => {
|
||||
if (!account) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cosmosPrivKey = (
|
||||
await getPathKey(
|
||||
`${requestedChain?.namespace}:${requestedChain?.chainId}`,
|
||||
account.index,
|
||||
)
|
||||
).privKey;
|
||||
|
||||
const sender = await DirectSecp256k1Wallet.fromKey(
|
||||
Buffer.from(cosmosPrivKey.split('0x')[1], 'hex'),
|
||||
requestedChain?.addressPrefix,
|
||||
);
|
||||
|
||||
const client = await SigningStargateClient.connectWithSigner(
|
||||
requestedChain?.rpcUrl!,
|
||||
sender,
|
||||
);
|
||||
|
||||
setCosmosStargateClient(client);
|
||||
};
|
||||
|
||||
setClient();
|
||||
}, [account, requestedChain, chainId, namespace]);
|
||||
|
||||
const provider = useMemo(() => {
|
||||
if (network === 'eth') {
|
||||
if (namespace === EIP155) {
|
||||
if (!requestedChain) {
|
||||
throw new Error('Requested chain not supported');
|
||||
}
|
||||
|
||||
return new providers.JsonRpcProvider(requestedChain.rpcUrl);
|
||||
}
|
||||
}, [requestedChain, network]);
|
||||
}, [requestedChain, namespace]);
|
||||
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||
@ -73,9 +109,9 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
const retrieveData = useCallback(
|
||||
async (requestAddress: string) => {
|
||||
const requestAccount = await retrieveSingleAccount(
|
||||
network,
|
||||
requestedChain!.namespace,
|
||||
requestedChain!.chainId,
|
||||
requestAddress,
|
||||
requestedChain?.addressPrefix,
|
||||
);
|
||||
if (!requestAccount) {
|
||||
navigation.navigate('InvalidPath');
|
||||
@ -85,11 +121,11 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
setAccount(requestAccount);
|
||||
setIsLoading(false);
|
||||
},
|
||||
[navigation, network, requestedChain],
|
||||
[navigation, requestedChain],
|
||||
);
|
||||
|
||||
const gasFees = useMemo(() => {
|
||||
if (network === 'eth') {
|
||||
if (namespace === EIP155) {
|
||||
return BigNumber.from(transaction.gasLimit)
|
||||
.mul(BigNumber.from(transaction.gasPrice))
|
||||
.toString();
|
||||
@ -99,7 +135,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
|
||||
return cosmosFees.amount[0].amount;
|
||||
}
|
||||
}, [transaction, network]);
|
||||
}, [transaction, namespace]);
|
||||
|
||||
useEffect(() => {
|
||||
retrieveData(transaction.from!);
|
||||
@ -115,9 +151,10 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
networksData,
|
||||
requestEvent,
|
||||
account,
|
||||
network,
|
||||
namespace!,
|
||||
requestedChain!.chainId,
|
||||
'',
|
||||
network === 'eth' ? provider : cosmosStargateClient,
|
||||
namespace === EIP155 ? provider : cosmosStargateClient,
|
||||
);
|
||||
|
||||
const { topic } = requestEvent;
|
||||
@ -140,7 +177,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
const getAccountBalance = async (account: Account) => {
|
||||
if (network === 'eth') {
|
||||
if (namespace === EIP155) {
|
||||
const fetchedBalance =
|
||||
provider && (await provider.getBalance(account.address));
|
||||
|
||||
@ -158,7 +195,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
if (account) {
|
||||
getAccountBalance(account);
|
||||
}
|
||||
}, [account, provider, network, cosmosStargateClient, requestedChain]);
|
||||
}, [account, provider, namespace, cosmosStargateClient, requestedChain]);
|
||||
|
||||
useEffect(() => {
|
||||
navigation.setOptions({
|
||||
@ -184,35 +221,6 @@ 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'),
|
||||
requestedChain?.addressPrefix,
|
||||
);
|
||||
|
||||
const client = await SigningStargateClient.connectWithSigner(
|
||||
requestedChain?.rpcUrl!,
|
||||
sender,
|
||||
);
|
||||
|
||||
setCosmosStargateClient(client);
|
||||
};
|
||||
|
||||
setClient();
|
||||
}, [account, requestedChain, chainId, network]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
@ -240,14 +248,12 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
</View>
|
||||
<DataBox
|
||||
label={`Balance (${
|
||||
requestedChain!.networkType === 'eth'
|
||||
? 'wei'
|
||||
: requestedChain!.nativeDenom
|
||||
}`}
|
||||
namespace === EIP155 ? 'wei' : requestedChain!.nativeDenom
|
||||
})`}
|
||||
data={
|
||||
balance === '' || balance === undefined
|
||||
? 'Loading balance...'
|
||||
: `${balance})`
|
||||
: `${balance}`
|
||||
}
|
||||
/>
|
||||
{transaction && (
|
||||
@ -255,9 +261,7 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
<DataBox label="To" data={transaction.to!} />
|
||||
<DataBox
|
||||
label={`Amount (${
|
||||
requestedChain!.networkType === 'eth'
|
||||
? 'wei'
|
||||
: requestedChain!.nativeDenom
|
||||
namespace === EIP155 ? 'wei' : requestedChain!.nativeDenom
|
||||
})`}
|
||||
data={BigNumber.from(
|
||||
transaction.value?.toString(),
|
||||
@ -265,13 +269,11 @@ const ApproveTransaction = ({ route }: SignRequestProps) => {
|
||||
/>
|
||||
<DataBox
|
||||
label={`Gas Fees (${
|
||||
requestedChain!.networkType === 'eth'
|
||||
? 'wei'
|
||||
: requestedChain!.nativeDenom
|
||||
namespace === EIP155 ? 'wei' : requestedChain!.nativeDenom
|
||||
})`}
|
||||
data={gasFees!}
|
||||
/>
|
||||
{network === 'eth' && (
|
||||
{namespace === EIP155 && (
|
||||
<DataBox label="Data" data={transaction.data!} />
|
||||
)}
|
||||
</View>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { View, ActivityIndicator, Image } from 'react-native';
|
||||
import { Button, Text } from 'react-native-paper';
|
||||
|
||||
@ -29,22 +29,16 @@ const WCLogo = () => {
|
||||
};
|
||||
|
||||
const HomeScreen = () => {
|
||||
const {
|
||||
accounts,
|
||||
setAccounts,
|
||||
currentIndex,
|
||||
setCurrentIndex,
|
||||
networkType,
|
||||
setNetworkType,
|
||||
} = useAccounts();
|
||||
const { accounts, setAccounts, currentIndex, setCurrentIndex } =
|
||||
useAccounts();
|
||||
|
||||
const { networksData, currentChainId, setCurrentChainId } = useNetworks();
|
||||
const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks();
|
||||
const { setActiveSessions } = useWalletConnect();
|
||||
|
||||
const navigation =
|
||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||
useEffect(() => {
|
||||
if (accounts.ethAccounts.length > 0) {
|
||||
if (accounts.length > 0) {
|
||||
navigation.setOptions({
|
||||
headerRight: () => (
|
||||
<Button onPress={() => navigation.navigate('WalletConnect')}>
|
||||
@ -69,16 +63,27 @@ const HomeScreen = () => {
|
||||
const hideWalletDialog = () => setWalletDialog(false);
|
||||
const hideResetDialog = () => setResetWalletDialog(false);
|
||||
|
||||
const fetchAccounts = useCallback(async () => {
|
||||
if (!selectedNetwork) {
|
||||
return;
|
||||
}
|
||||
|
||||
const loadedAccounts = await retrieveAccounts(selectedNetwork);
|
||||
|
||||
if (loadedAccounts) {
|
||||
setAccounts(loadedAccounts);
|
||||
setIsWalletCreated(true);
|
||||
}
|
||||
|
||||
setIsAccountsFetched(true);
|
||||
}, [selectedNetwork, setAccounts]);
|
||||
|
||||
const createWalletHandler = async () => {
|
||||
setIsWalletCreating(true);
|
||||
const { mnemonic, ethAccounts, cosmosAccounts } = await createWallet();
|
||||
if (ethAccounts && cosmosAccounts) {
|
||||
setAccounts({
|
||||
ethAccounts: [...accounts.ethAccounts, ethAccounts],
|
||||
cosmosAccounts: [...accounts.cosmosAccounts, cosmosAccounts],
|
||||
});
|
||||
const mnemonic = await createWallet(networksData);
|
||||
if (mnemonic) {
|
||||
fetchAccounts();
|
||||
setWalletDialog(true);
|
||||
setIsWalletCreated(true);
|
||||
setPhrase(mnemonic);
|
||||
}
|
||||
};
|
||||
@ -87,10 +92,7 @@ const HomeScreen = () => {
|
||||
await resetWallet();
|
||||
setIsWalletCreated(false);
|
||||
setIsWalletCreating(false);
|
||||
setAccounts({
|
||||
ethAccounts: [],
|
||||
cosmosAccounts: [],
|
||||
});
|
||||
setAccounts([]);
|
||||
setCurrentIndex(0);
|
||||
const sessions = web3wallet!.getActiveSessions();
|
||||
|
||||
@ -103,12 +105,10 @@ const HomeScreen = () => {
|
||||
setActiveSessions({});
|
||||
|
||||
hideResetDialog();
|
||||
setNetworkType('eth');
|
||||
};
|
||||
|
||||
const updateNetwork = (networksData: NetworksDataState) => {
|
||||
setNetworkType(networksData.networkType);
|
||||
setCurrentChainId(networksData.chainId);
|
||||
setSelectedNetwork(networksData);
|
||||
setCurrentIndex(0);
|
||||
};
|
||||
|
||||
@ -117,34 +117,8 @@ const HomeScreen = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const fetchAccounts = async () => {
|
||||
if (!currentChainId) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
setIsAccountsFetched(true);
|
||||
};
|
||||
|
||||
fetchAccounts();
|
||||
}, [currentChainId, networksData, setAccounts]);
|
||||
}, [networksData, setAccounts, selectedNetwork, fetchAccounts]);
|
||||
|
||||
return (
|
||||
<View style={styles.appContainer}>
|
||||
@ -157,11 +131,7 @@ const HomeScreen = () => {
|
||||
<>
|
||||
<NetworkDropdown updateNetwork={updateNetwork} />
|
||||
<View style={styles.accountComponent}>
|
||||
<Accounts
|
||||
network={networkType}
|
||||
currentIndex={currentIndex}
|
||||
updateIndex={updateIndex}
|
||||
/>
|
||||
<Accounts currentIndex={currentIndex} updateIndex={updateIndex} />
|
||||
</View>
|
||||
<View style={styles.resetContainer}>
|
||||
<Button
|
||||
|
@ -12,7 +12,8 @@ import AccountDetails from '../components/AccountDetails';
|
||||
type SignProps = NativeStackScreenProps<StackParamsList, 'SignMessage'>;
|
||||
|
||||
const SignMessage = ({ route }: SignProps) => {
|
||||
const network = route.params.selectedNetwork;
|
||||
const namespace = route.params.selectedNamespace;
|
||||
const chainId = route.params.selectedChainId;
|
||||
const account = route.params.accountInfo;
|
||||
|
||||
const [message, setMessage] = useState<string>('');
|
||||
@ -20,8 +21,9 @@ const SignMessage = ({ route }: SignProps) => {
|
||||
const signMessageHandler = async () => {
|
||||
const signedMessage = await signMessage({
|
||||
message,
|
||||
network,
|
||||
accountId: account.counterId,
|
||||
namespace,
|
||||
chainId,
|
||||
accountId: account.index,
|
||||
});
|
||||
Alert.alert('Signature', signedMessage);
|
||||
};
|
||||
@ -31,7 +33,7 @@ const SignMessage = ({ route }: SignProps) => {
|
||||
<View style={styles.accountInfo}>
|
||||
<View>
|
||||
<Text variant="titleMedium">
|
||||
{account && `Account ${account.counterId + 1}`}
|
||||
{account && `Account ${account.index + 1}`}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.accountContainer}>
|
||||
|
@ -26,7 +26,7 @@ import { useNetworks } from '../context/NetworksContext';
|
||||
type SignRequestProps = NativeStackScreenProps<StackParamsList, 'SignRequest'>;
|
||||
|
||||
const SignRequest = ({ route }: SignRequestProps) => {
|
||||
const { networksData } = useNetworks();
|
||||
const { networksData, selectedNetwork } = useNetworks();
|
||||
|
||||
const requestSession = route.params.requestSessionData;
|
||||
const requestName = requestSession?.peer?.metadata?.name;
|
||||
@ -35,7 +35,8 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
||||
|
||||
const [account, setAccount] = useState<Account>();
|
||||
const [message, setMessage] = useState<string>('');
|
||||
const [network, setNetwork] = useState<string>('');
|
||||
const [namespace, setNamespace] = useState<string>('');
|
||||
const [chainId, setChainId] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isApproving, setIsApproving] = useState(false);
|
||||
const [isRejecting, setIsRejecting] = useState(false);
|
||||
@ -68,20 +69,15 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
||||
|
||||
const retrieveData = useCallback(
|
||||
async (
|
||||
requestNetwork: string,
|
||||
requestNamespace: string,
|
||||
requestChainId: string,
|
||||
requestAddress: string,
|
||||
requestMessage: string,
|
||||
) => {
|
||||
const currentChain = networksData.find(networkData => {
|
||||
if (networkData.addressPrefix) {
|
||||
return requestAddress.includes(networkData.addressPrefix);
|
||||
}
|
||||
});
|
||||
|
||||
const requestAccount = await retrieveSingleAccount(
|
||||
requestNetwork,
|
||||
requestNamespace,
|
||||
requestChainId,
|
||||
requestAddress,
|
||||
currentChain?.addressPrefix,
|
||||
);
|
||||
if (!requestAccount) {
|
||||
navigation.navigate('InvalidPath');
|
||||
@ -94,21 +90,23 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
||||
if (requestMessage !== message) {
|
||||
setMessage(decodeURIComponent(requestMessage));
|
||||
}
|
||||
if (requestNetwork !== network) {
|
||||
setNetwork(requestNetwork);
|
||||
if (requestNamespace !== namespace) {
|
||||
setNamespace(requestNamespace);
|
||||
}
|
||||
if (requestChainId !== chainId) {
|
||||
setChainId(requestChainId);
|
||||
}
|
||||
setIsLoading(false);
|
||||
},
|
||||
[account, message, navigation, network, networksData],
|
||||
[account, message, navigation, namespace, chainId],
|
||||
);
|
||||
|
||||
const sanitizePath = useCallback(
|
||||
(path: string) => {
|
||||
const regex = /^\/sign\/(eth|cosmos)\/(.+)\/(.+)$/;
|
||||
const regex = /^\/sign\/(eip155|cosmos)\/(.+)\/(.+)$/;
|
||||
const match = path.match(regex);
|
||||
|
||||
if (match) {
|
||||
const [network, address, message] = match;
|
||||
const [, network, address, message] = match;
|
||||
return {
|
||||
network,
|
||||
address,
|
||||
@ -127,18 +125,28 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
||||
const sanitizedRoute = sanitizePath(route.path);
|
||||
sanitizedRoute &&
|
||||
retrieveData(
|
||||
route.params.network,
|
||||
route.params.address,
|
||||
route.params.message,
|
||||
sanitizedRoute.network,
|
||||
// TODO: Take chainId from route.path
|
||||
selectedNetwork!.chainId!,
|
||||
sanitizedRoute.address,
|
||||
sanitizedRoute.message,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const requestEvent = route.params.requestEvent;
|
||||
const requestChainId = requestEvent?.params.chainId;
|
||||
|
||||
const requestedChain = networksData.find(
|
||||
networkData => networkData.chainId === requestChainId?.split(':')[1],
|
||||
);
|
||||
|
||||
retrieveData(
|
||||
route.params.network,
|
||||
requestedChain!.namespace,
|
||||
requestedChain!.chainId,
|
||||
route.params.address,
|
||||
route.params.message,
|
||||
);
|
||||
}, [retrieveData, sanitizePath, route]);
|
||||
}, [retrieveData, sanitizePath, route, networksData, selectedNetwork]);
|
||||
|
||||
const handleWalletConnectRequest = async () => {
|
||||
const { requestEvent } = route.params || {};
|
||||
@ -155,7 +163,8 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
||||
networksData,
|
||||
requestEvent,
|
||||
account,
|
||||
network,
|
||||
namespace,
|
||||
chainId,
|
||||
message,
|
||||
);
|
||||
|
||||
@ -170,8 +179,9 @@ const SignRequest = ({ route }: SignRequestProps) => {
|
||||
if (message) {
|
||||
const signedMessage = await signMessage({
|
||||
message,
|
||||
network,
|
||||
accountId: account.counterId,
|
||||
namespace,
|
||||
chainId,
|
||||
accountId: account.index,
|
||||
});
|
||||
Alert.alert('Signature', signedMessage);
|
||||
}
|
||||
|
34
src/types.ts
34
src/types.ts
@ -5,16 +5,19 @@ import { Web3WalletTypes } from '@walletconnect/web3wallet';
|
||||
|
||||
export type StackParamsList = {
|
||||
Laconic: undefined;
|
||||
SignMessage: { selectedNetwork: string; accountInfo: Account };
|
||||
SignMessage: {
|
||||
selectedNamespace: string;
|
||||
selectedChainId: string;
|
||||
accountInfo: Account;
|
||||
};
|
||||
SignRequest: {
|
||||
network: string;
|
||||
namespace: string;
|
||||
address: string;
|
||||
message: string;
|
||||
requestEvent?: Web3WalletTypes.SessionRequest;
|
||||
requestSessionData?: SessionTypes.Struct;
|
||||
};
|
||||
ApproveTransaction: {
|
||||
network: string;
|
||||
transaction: PopulatedTransaction;
|
||||
requestEvent: Web3WalletTypes.SessionRequest;
|
||||
requestSessionData: SessionTypes.Struct;
|
||||
@ -26,20 +29,13 @@ export type StackParamsList = {
|
||||
};
|
||||
|
||||
export type Account = {
|
||||
counterId: number;
|
||||
index: number;
|
||||
pubKey: string;
|
||||
address: string;
|
||||
hdPath: string;
|
||||
};
|
||||
|
||||
export type WalletDetails = {
|
||||
mnemonic: string;
|
||||
ethAccounts: Account | undefined;
|
||||
cosmosAccounts: Account | undefined;
|
||||
};
|
||||
|
||||
export type AccountsProps = {
|
||||
network: string;
|
||||
currentIndex: number;
|
||||
updateIndex: (index: number) => void;
|
||||
};
|
||||
@ -48,27 +44,27 @@ export type NetworkDropdownProps = {
|
||||
updateNetwork: (networksData: NetworksDataState) => void;
|
||||
};
|
||||
|
||||
export type AccountsState = {
|
||||
ethAccounts: Account[];
|
||||
cosmosAccounts: Account[];
|
||||
};
|
||||
|
||||
export type NetworksDataState = {
|
||||
export type NetworksFormData = {
|
||||
networkName: string;
|
||||
rpcUrl: string;
|
||||
chainId: string;
|
||||
currencySymbol?: string;
|
||||
blockExplorerUrl?: string;
|
||||
networkType: string;
|
||||
namespace: string;
|
||||
nativeDenom?: string;
|
||||
addressPrefix?: string;
|
||||
coinType?: string;
|
||||
isDefault: boolean;
|
||||
};
|
||||
|
||||
export interface NetworksDataState extends NetworksFormData {
|
||||
networkId: string;
|
||||
}
|
||||
|
||||
export type SignMessageParams = {
|
||||
message: string;
|
||||
network: string;
|
||||
namespace: string;
|
||||
chainId: string;
|
||||
accountId: number;
|
||||
};
|
||||
|
||||
|
@ -16,78 +16,76 @@ import { Secp256k1HdWallet } from '@cosmjs/amino';
|
||||
import { AccountData } from '@cosmjs/proto-signing';
|
||||
import { stringToPath } from '@cosmjs/crypto';
|
||||
|
||||
import { Account, NetworksDataState, WalletDetails } from '../types';
|
||||
import { Account, NetworksDataState, NetworksFormData } from '../types';
|
||||
import {
|
||||
getHDPath,
|
||||
getPathKey,
|
||||
resetKeyServers,
|
||||
updateGlobalCounter,
|
||||
updateAccountIndices,
|
||||
} from './misc';
|
||||
import { COSMOS, EIP155 } from './constants';
|
||||
|
||||
const createWallet = async (): Promise<WalletDetails> => {
|
||||
try {
|
||||
const mnemonic = utils.entropyToMnemonic(utils.randomBytes(16));
|
||||
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
|
||||
const createWallet = async (
|
||||
networksData: NetworksDataState[],
|
||||
): Promise<string> => {
|
||||
const mnemonic = utils.entropyToMnemonic(utils.randomBytes(16));
|
||||
await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic);
|
||||
|
||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||
const ethNode = hdNode.derivePath("m/44'/60'/0'/0/0");
|
||||
const cosmosNode = hdNode.derivePath("m/44'/118'/0'/0/0");
|
||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||
|
||||
const ethAddress = ethNode.address;
|
||||
const cosmosAddress = (await getCosmosAccounts(mnemonic, "0'/0/0")).data
|
||||
.address;
|
||||
for (const network of networksData) {
|
||||
const hdPath = `m/44'/${network.coinType}'/0'/0/0`;
|
||||
const node = hdNode.derivePath(hdPath);
|
||||
let address;
|
||||
|
||||
const ethAccountInfo = `${"0'/0/0"},${ethNode.privateKey},${
|
||||
ethNode.publicKey
|
||||
},${ethAddress}`;
|
||||
const cosmosAccountInfo = `${"0'/0/0"},${cosmosNode.privateKey},${
|
||||
cosmosNode.publicKey
|
||||
},${cosmosAddress}`;
|
||||
switch (network.namespace) {
|
||||
case EIP155:
|
||||
address = node.address;
|
||||
break;
|
||||
|
||||
case COSMOS:
|
||||
address = (
|
||||
await getCosmosAccounts(mnemonic, hdPath, network.addressPrefix)
|
||||
).data.address;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Unsupported namespace');
|
||||
}
|
||||
|
||||
const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`;
|
||||
|
||||
await Promise.all([
|
||||
setInternetCredentials(
|
||||
'eth:keyServer:0',
|
||||
'eth:pathKey:0',
|
||||
ethAccountInfo,
|
||||
`accounts/${network.namespace}:${network.chainId}/0`,
|
||||
'_',
|
||||
accountInfo,
|
||||
),
|
||||
setInternetCredentials(
|
||||
'cosmos:keyServer:0',
|
||||
'cosmos:pathKey:0',
|
||||
cosmosAccountInfo,
|
||||
`addAccountCounter/${network.namespace}:${network.chainId}`,
|
||||
'_',
|
||||
'1',
|
||||
),
|
||||
setInternetCredentials(
|
||||
`accountIndices/${network.namespace}:${network.chainId}`,
|
||||
'_',
|
||||
'0',
|
||||
),
|
||||
setInternetCredentials('eth:accountIndices', 'ethCounter', '0'),
|
||||
setInternetCredentials('cosmos:accountIndices', 'cosmosCounter', '0'),
|
||||
setInternetCredentials('eth:globalCounter', 'ethGlobal', '0'),
|
||||
setInternetCredentials('cosmos:globalCounter', 'cosmosGlobal', '0'),
|
||||
]);
|
||||
|
||||
const ethAccounts = {
|
||||
counterId: 0,
|
||||
pubKey: ethNode.publicKey,
|
||||
address: ethAddress,
|
||||
hdPath: "m/44'/60'/0'/0/0",
|
||||
};
|
||||
|
||||
const cosmosAccounts = {
|
||||
counterId: 0,
|
||||
pubKey: cosmosNode.publicKey,
|
||||
address: cosmosAddress,
|
||||
hdPath: "m/44'/118'/0'/0/0",
|
||||
};
|
||||
|
||||
return { mnemonic, ethAccounts, cosmosAccounts };
|
||||
} catch (error) {
|
||||
console.error('Error creating HD wallet:', error);
|
||||
return { mnemonic: '', ethAccounts: undefined, cosmosAccounts: undefined };
|
||||
}
|
||||
|
||||
return mnemonic;
|
||||
};
|
||||
|
||||
const addAccount = async (network: string): Promise<Account | undefined> => {
|
||||
const addAccount = async (
|
||||
networkData: NetworksDataState,
|
||||
): Promise<Account | undefined> => {
|
||||
try {
|
||||
const id = await getNextAccountId(network);
|
||||
const hdPath = getHDPath(network, `0'/0/${id}`);
|
||||
const accounts = await addAccountFromHDPath(hdPath);
|
||||
await updateAccountIndices(network, id);
|
||||
const namespaceChainId = `${networkData.namespace}:${networkData.chainId}`;
|
||||
const id = await getNextAccountId(namespaceChainId);
|
||||
const hdPath = getHDPath(namespaceChainId, `0'/0/${id}`);
|
||||
const accounts = await addAccountFromHDPath(hdPath, networkData);
|
||||
await updateAccountCounter(namespaceChainId, id);
|
||||
return accounts;
|
||||
} catch (error) {
|
||||
console.error('Error creating account:', error);
|
||||
@ -96,37 +94,40 @@ const addAccount = async (network: string): Promise<Account | undefined> => {
|
||||
|
||||
const addAccountFromHDPath = async (
|
||||
hdPath: string,
|
||||
networkData: NetworksDataState,
|
||||
): Promise<Account | undefined> => {
|
||||
try {
|
||||
const account = await accountInfoFromHDPath(hdPath);
|
||||
const account = await accountInfoFromHDPath(
|
||||
hdPath,
|
||||
networkData.addressPrefix,
|
||||
);
|
||||
if (!account) {
|
||||
throw new Error('Error while creating account');
|
||||
}
|
||||
|
||||
const parts = hdPath.split('/');
|
||||
const path = parts.slice(-3).join('/');
|
||||
const { privKey, pubKey, address } = account;
|
||||
|
||||
const { privKey, pubKey, address, network } = account;
|
||||
const namespaceChainId = `${networkData.namespace}:${networkData.chainId}`;
|
||||
|
||||
const counterId = (await updateGlobalCounter(network)).counterId;
|
||||
const index = (await updateAccountIndices(namespaceChainId)).index;
|
||||
|
||||
await Promise.all([
|
||||
setInternetCredentials(
|
||||
`${network}:keyServer:${counterId}`,
|
||||
`${network}:pathKey:${counterId}`,
|
||||
`${path},${privKey},${pubKey},${address}`,
|
||||
`accounts/${namespaceChainId}/${index}`,
|
||||
'_',
|
||||
`${hdPath},${privKey},${pubKey},${address}`,
|
||||
),
|
||||
]);
|
||||
|
||||
return { counterId, pubKey, address, hdPath };
|
||||
return { index, pubKey, address, hdPath };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const storeNetworkData = async (
|
||||
networkData: NetworksDataState,
|
||||
): Promise<void> => {
|
||||
networkData: NetworksFormData,
|
||||
): Promise<NetworksDataState[]> => {
|
||||
const networks = await getInternetCredentials('networks');
|
||||
const retrievedNetworks =
|
||||
networks && networks.password ? JSON.parse(networks.password) : [];
|
||||
@ -135,7 +136,7 @@ const storeNetworkData = async (
|
||||
networkId = retrievedNetworks[retrievedNetworks.length - 1].networkId + 1;
|
||||
}
|
||||
|
||||
const updatedNetworks = [
|
||||
const updatedNetworks: NetworksDataState[] = [
|
||||
...retrievedNetworks,
|
||||
{ networkId: networkId, ...networkData },
|
||||
];
|
||||
@ -144,6 +145,8 @@ const storeNetworkData = async (
|
||||
'_',
|
||||
JSON.stringify(updatedNetworks),
|
||||
);
|
||||
|
||||
return updatedNetworks;
|
||||
};
|
||||
|
||||
const retrieveNetworksData = async (): Promise<NetworksDataState[]> => {
|
||||
@ -155,24 +158,23 @@ const retrieveNetworksData = async (): Promise<NetworksDataState[]> => {
|
||||
};
|
||||
|
||||
export const retrieveAccountsForNetwork = async (
|
||||
network: string,
|
||||
namespaceChainId: string,
|
||||
accountsIndices: string,
|
||||
prefix: string = 'cosmos',
|
||||
): Promise<Account[]> => {
|
||||
const accountsIndexArray = accountsIndices.split(',');
|
||||
|
||||
const loadedAccounts = await Promise.all(
|
||||
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);
|
||||
const { address, path, pubKey } = await getPathKey(
|
||||
namespaceChainId,
|
||||
Number(i),
|
||||
);
|
||||
|
||||
const account: Account = {
|
||||
counterId: Number(i),
|
||||
pubKey: pubKey,
|
||||
address: address,
|
||||
hdPath: hdPath,
|
||||
index: Number(i),
|
||||
pubKey,
|
||||
address,
|
||||
hdPath: path,
|
||||
};
|
||||
return account;
|
||||
}),
|
||||
@ -182,77 +184,57 @@ export const retrieveAccountsForNetwork = async (
|
||||
};
|
||||
|
||||
const retrieveAccounts = async (
|
||||
prefix: string = 'cosmos',
|
||||
): Promise<{
|
||||
ethLoadedAccounts?: Account[];
|
||||
cosmosLoadedAccounts?: Account[];
|
||||
}> => {
|
||||
const ethServer = await getInternetCredentials('eth:globalCounter');
|
||||
const ethCounter = ethServer && ethServer.password;
|
||||
const cosmosServer = await getInternetCredentials('cosmos:globalCounter');
|
||||
const cosmosCounter = cosmosServer && cosmosServer.password;
|
||||
currentNetworkData: NetworksDataState,
|
||||
): Promise<Account[] | undefined> => {
|
||||
const accountIndicesServer = await getInternetCredentials(
|
||||
`accountIndices/${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
|
||||
);
|
||||
const accountIndices = accountIndicesServer && accountIndicesServer.password;
|
||||
|
||||
const ethLoadedAccounts = ethCounter
|
||||
? await retrieveAccountsForNetwork('eth', ethCounter)
|
||||
: undefined;
|
||||
const cosmosLoadedAccounts = cosmosCounter
|
||||
? await retrieveAccountsForNetwork('cosmos', cosmosCounter, prefix)
|
||||
const loadedAccounts = accountIndices
|
||||
? await retrieveAccountsForNetwork(
|
||||
`${currentNetworkData.namespace}:${currentNetworkData.chainId}`,
|
||||
accountIndices,
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return { ethLoadedAccounts, cosmosLoadedAccounts };
|
||||
return loadedAccounts;
|
||||
};
|
||||
|
||||
const retrieveSingleAccount = async (
|
||||
network: string,
|
||||
namespace: string,
|
||||
chainId: string,
|
||||
address: string,
|
||||
prefix: string = 'cosmos',
|
||||
) => {
|
||||
let loadedAccounts;
|
||||
|
||||
switch (network) {
|
||||
case 'eth':
|
||||
const ethServer = await getInternetCredentials('eth:globalCounter');
|
||||
const ethCounter = ethServer && ethServer.password;
|
||||
const accountIndicesServer = await getInternetCredentials(
|
||||
`accountIndices/${namespace}:${chainId}`,
|
||||
);
|
||||
const accountIndices = accountIndicesServer && accountIndicesServer.password;
|
||||
|
||||
if (ethCounter) {
|
||||
loadedAccounts = await retrieveAccountsForNetwork(network, ethCounter);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'cosmos':
|
||||
const cosmosServer = await getInternetCredentials('cosmos:globalCounter');
|
||||
const cosmosCounter = cosmosServer && cosmosServer.password;
|
||||
|
||||
if (cosmosCounter) {
|
||||
loadedAccounts = await retrieveAccountsForNetwork(
|
||||
network,
|
||||
cosmosCounter,
|
||||
prefix,
|
||||
);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
if (!accountIndices) {
|
||||
throw new Error('Indices for given chain not found');
|
||||
}
|
||||
|
||||
if (loadedAccounts) {
|
||||
return loadedAccounts.find(account => account.address === address);
|
||||
loadedAccounts = await retrieveAccountsForNetwork(
|
||||
`${namespace}:${chainId}`,
|
||||
accountIndices,
|
||||
);
|
||||
|
||||
if (!loadedAccounts) {
|
||||
throw new Error('Accounts for given chain not found');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
return loadedAccounts.find(account => account.address === address);
|
||||
};
|
||||
|
||||
const resetWallet = async () => {
|
||||
try {
|
||||
await Promise.all([
|
||||
resetInternetCredentials('mnemonicServer'),
|
||||
resetKeyServers('eth'),
|
||||
resetKeyServers('cosmos'),
|
||||
resetInternetCredentials('eth:accountIndices'),
|
||||
resetInternetCredentials('cosmos:accountIndices'),
|
||||
resetInternetCredentials('eth:globalCounter'),
|
||||
resetInternetCredentials('cosmos:globalCounter'),
|
||||
resetKeyServers(EIP155),
|
||||
resetKeyServers(COSMOS),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Error resetting wallet:', error);
|
||||
@ -262,11 +244,10 @@ const resetWallet = async () => {
|
||||
|
||||
const accountInfoFromHDPath = async (
|
||||
hdPath: string,
|
||||
prefix: string = COSMOS,
|
||||
): Promise<
|
||||
| { privKey: string; pubKey: string; address: string; network: string }
|
||||
| undefined
|
||||
{ privKey: string; pubKey: string; address: string } | undefined
|
||||
> => {
|
||||
// TODO: move HDNode inside eth switch case
|
||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||
if (!mnemonicStore) {
|
||||
throw new Error('Mnemonic not found!');
|
||||
@ -280,62 +261,65 @@ const accountInfoFromHDPath = async (
|
||||
const pubKey = node.publicKey;
|
||||
|
||||
const parts = hdPath.split('/');
|
||||
const path = parts.slice(-3).join('/');
|
||||
const coinType = parts[2];
|
||||
|
||||
let network: string;
|
||||
let address: string;
|
||||
|
||||
switch (coinType) {
|
||||
case "60'":
|
||||
network = 'eth';
|
||||
address = node.address;
|
||||
break;
|
||||
case "118'":
|
||||
network = 'cosmos';
|
||||
address = (await getCosmosAccounts(mnemonic, path)).data.address;
|
||||
address = (await getCosmosAccounts(mnemonic, hdPath, prefix)).data
|
||||
.address;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
return { privKey, pubKey, address, network };
|
||||
return { privKey, pubKey, address };
|
||||
};
|
||||
|
||||
const getNextAccountId = async (network: string): Promise<number> => {
|
||||
const idStore = await getInternetCredentials(`${network}:accountIndices`);
|
||||
const getNextAccountId = async (namespaceChainId: string): Promise<number> => {
|
||||
const idStore = await getInternetCredentials(
|
||||
`addAccountCounter/${namespaceChainId}`,
|
||||
);
|
||||
if (!idStore) {
|
||||
throw new Error('Account id not found');
|
||||
}
|
||||
|
||||
const accountIds = idStore.password;
|
||||
const ids = accountIds.split(',').map(Number);
|
||||
return ids[ids.length - 1] + 1;
|
||||
const accountCounter = idStore.password;
|
||||
const nextCounter = Number(accountCounter);
|
||||
return nextCounter;
|
||||
};
|
||||
|
||||
const updateAccountIndices = async (
|
||||
network: string,
|
||||
const updateAccountCounter = async (
|
||||
namespaceChainId: string,
|
||||
id: number,
|
||||
): Promise<void> => {
|
||||
const idStore = await getInternetCredentials(`${network}:accountIndices`);
|
||||
const idStore = await getInternetCredentials(
|
||||
`addAccountCounter/${namespaceChainId}`,
|
||||
);
|
||||
if (!idStore) {
|
||||
throw new Error('Account id not found');
|
||||
}
|
||||
|
||||
const updatedIndices = `${idStore.password},${id.toString()}`;
|
||||
await resetInternetCredentials(`${network}:accountIndices`);
|
||||
const updatedCounter = String(id + 1);
|
||||
await resetInternetCredentials(`addAccountCounter/${namespaceChainId}`);
|
||||
await setInternetCredentials(
|
||||
`${network}:accountIndices`,
|
||||
`${network}Counter`,
|
||||
updatedIndices,
|
||||
`addAccountCounter/${namespaceChainId}`,
|
||||
'_',
|
||||
updatedCounter,
|
||||
);
|
||||
};
|
||||
|
||||
const getCosmosAccounts = async (
|
||||
mnemonic: string,
|
||||
path: string,
|
||||
prefix: string = COSMOS,
|
||||
): Promise<{ cosmosWallet: Secp256k1HdWallet; data: AccountData }> => {
|
||||
const cosmosWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, {
|
||||
hdPaths: [stringToPath(`m/44'/118'/${path}`)],
|
||||
hdPaths: [stringToPath(path)],
|
||||
prefix,
|
||||
});
|
||||
|
||||
const accountsData = await cosmosWallet.getAccounts();
|
||||
@ -355,6 +339,6 @@ export {
|
||||
resetWallet,
|
||||
accountInfoFromHDPath,
|
||||
getNextAccountId,
|
||||
updateAccountIndices,
|
||||
updateAccountCounter,
|
||||
getCosmosAccounts,
|
||||
};
|
||||
|
@ -1,19 +1,22 @@
|
||||
import { COSMOS_TESTNET_CHAINS } from './wallet-connect/COSMOSData';
|
||||
import { EIP155_CHAINS } from './wallet-connect/EIP155Data';
|
||||
|
||||
export const DEFAULTNETWORKS = [
|
||||
export const EIP155 = 'eip155';
|
||||
export const COSMOS = 'cosmos';
|
||||
export const DEFAULT_NETWORKS = [
|
||||
{
|
||||
chainId: 'eip155:1',
|
||||
chainId: '1',
|
||||
networkName: EIP155_CHAINS['eip155:1'].name,
|
||||
networkType: 'eth',
|
||||
namespace: EIP155,
|
||||
rpcUrl: EIP155_CHAINS['eip155:1'].rpc,
|
||||
currencySymbol: 'ETH',
|
||||
coinType: '60',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
chainId: 'cosmos:theta-testnet-001',
|
||||
chainId: 'theta-testnet-001',
|
||||
networkName: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].name,
|
||||
networkType: 'cosmos',
|
||||
namespace: COSMOS,
|
||||
rpcUrl: COSMOS_TESTNET_CHAINS['cosmos:theta-testnet-001'].rpc,
|
||||
nativeDenom: 'uatom',
|
||||
addressPrefix: 'cosmos',
|
||||
|
@ -10,9 +10,11 @@ import {
|
||||
setInternetCredentials,
|
||||
} from 'react-native-keychain';
|
||||
|
||||
import { AccountData, Secp256k1Wallet } from '@cosmjs/amino';
|
||||
import { AccountData } from '@cosmjs/amino';
|
||||
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
|
||||
import { stringToPath } from '@cosmjs/crypto';
|
||||
import { EIP155 } from './constants';
|
||||
import { NetworksDataState } from '../types';
|
||||
|
||||
const getMnemonic = async (): Promise<string> => {
|
||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||
@ -24,8 +26,9 @@ const getMnemonic = async (): Promise<string> => {
|
||||
return mnemonic;
|
||||
};
|
||||
|
||||
const getHDPath = (network: string, path: string): string => {
|
||||
return network === 'eth' ? `m/44'/60'/${path}` : `m/44'/118'/${path}`;
|
||||
const getHDPath = (namespaceChainId: string, path: string): string => {
|
||||
const namespace = namespaceChainId.split(':')[0];
|
||||
return namespace === EIP155 ? `m/44'/60'/${path}` : `m/44'/118'/${path}`;
|
||||
};
|
||||
|
||||
export const getDirectWallet = async (
|
||||
@ -42,9 +45,8 @@ export const getDirectWallet = async (
|
||||
};
|
||||
|
||||
const getPathKey = async (
|
||||
network: string,
|
||||
namespaceChainId: string,
|
||||
accountId: number,
|
||||
prefix: string = 'cosmos',
|
||||
): Promise<{
|
||||
path: string;
|
||||
privKey: string;
|
||||
@ -52,7 +54,7 @@ const getPathKey = async (
|
||||
address: string;
|
||||
}> => {
|
||||
const pathKeyStore = await getInternetCredentials(
|
||||
`${network}:keyServer:${accountId}`,
|
||||
`accounts/${namespaceChainId}/${accountId}`,
|
||||
);
|
||||
|
||||
if (!pathKeyStore) {
|
||||
@ -63,84 +65,94 @@ const getPathKey = async (
|
||||
const pathkey = pathKeyVal.split(',');
|
||||
const path = pathkey[0];
|
||||
const privKey = pathkey[1];
|
||||
|
||||
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;
|
||||
}
|
||||
const pubKey = pathkey[2];
|
||||
const address = pathkey[3];
|
||||
|
||||
return { path, privKey, pubKey, address };
|
||||
};
|
||||
|
||||
const getGlobalCounter = async (
|
||||
network: string,
|
||||
const getAccountIndices = async (
|
||||
namespaceChainId: string,
|
||||
): Promise<{
|
||||
accountCounter: string;
|
||||
counterIds: number[];
|
||||
counterId: number;
|
||||
accountIndices: string;
|
||||
indices: number[];
|
||||
index: number;
|
||||
}> => {
|
||||
const counterStore = await getInternetCredentials(`${network}:globalCounter`);
|
||||
const counterStore = await getInternetCredentials(
|
||||
`accountIndices/${namespaceChainId}`,
|
||||
);
|
||||
|
||||
if (!counterStore) {
|
||||
throw new Error('Error while fetching counter');
|
||||
}
|
||||
|
||||
let accountCounter = counterStore.password;
|
||||
const counterIds = accountCounter.split(',').map(Number);
|
||||
const counterId = counterIds[counterIds.length - 1] + 1;
|
||||
let accountIndices = counterStore.password;
|
||||
const indices = accountIndices.split(',').map(Number);
|
||||
const index = indices[indices.length - 1] + 1;
|
||||
|
||||
return { accountCounter, counterIds, counterId };
|
||||
return { accountIndices, indices, index };
|
||||
};
|
||||
|
||||
const updateGlobalCounter = async (
|
||||
network: string,
|
||||
): Promise<{ accountCounter: string; counterId: number }> => {
|
||||
const globalCounterData = await getGlobalCounter(network);
|
||||
const accountCounter = globalCounterData.accountCounter;
|
||||
const counterId = globalCounterData.counterId;
|
||||
const updatedAccountCounter = `${accountCounter},${counterId.toString()}`;
|
||||
const updateAccountIndices = async (
|
||||
namespaceChainId: string,
|
||||
): Promise<{ accountIndices: string; index: number }> => {
|
||||
const accountIndicesData = await getAccountIndices(namespaceChainId);
|
||||
const accountIndices = accountIndicesData.accountIndices;
|
||||
const index = accountIndicesData.index;
|
||||
const updatedAccountIndices = `${accountIndices},${index.toString()}`;
|
||||
|
||||
await resetInternetCredentials(`${network}:globalCounter`);
|
||||
await resetInternetCredentials(`accountIndices/${namespaceChainId}`);
|
||||
await setInternetCredentials(
|
||||
`${network}:globalCounter`,
|
||||
`${network}Global`,
|
||||
updatedAccountCounter,
|
||||
`accountIndices/${namespaceChainId}`,
|
||||
'_',
|
||||
updatedAccountIndices,
|
||||
);
|
||||
|
||||
return { accountCounter: updatedAccountCounter, counterId };
|
||||
return { accountIndices: updatedAccountIndices, index };
|
||||
};
|
||||
|
||||
const resetKeyServers = async (prefix: string) => {
|
||||
const idStore = await getInternetCredentials(`${prefix}:accountIndices`);
|
||||
if (!idStore) {
|
||||
throw new Error('Account id not found.');
|
||||
const resetKeyServers = async (namespace: string) => {
|
||||
const networksServer = await getInternetCredentials('networks');
|
||||
if (!networksServer) {
|
||||
throw new Error('Networks not found.');
|
||||
}
|
||||
|
||||
const accountIds = idStore.password;
|
||||
const ids = accountIds.split(',').map(Number);
|
||||
const id = ids[ids.length - 1];
|
||||
const networksData: NetworksDataState[] = JSON.parse(networksServer.password);
|
||||
const filteredNetworks = networksData.filter(
|
||||
network => network.namespace === namespace,
|
||||
);
|
||||
|
||||
for (let i = 0; i <= id; i++) {
|
||||
await resetInternetCredentials(`${prefix}:keyServer:${i}`);
|
||||
if (filteredNetworks.length === 0) {
|
||||
throw new Error(`No networks found for namespace ${namespace}.`);
|
||||
}
|
||||
|
||||
filteredNetworks.forEach(async network => {
|
||||
const { chainId } = network;
|
||||
const namespaceChainId = `${namespace}:${chainId}`;
|
||||
|
||||
const idStore = await getInternetCredentials(
|
||||
`accountIndices/${namespaceChainId}`,
|
||||
);
|
||||
if (!idStore) {
|
||||
throw new Error(`Account indices not found for ${namespaceChainId}.`);
|
||||
}
|
||||
|
||||
const accountIds = idStore.password;
|
||||
const ids = accountIds.split(',').map(Number);
|
||||
const latestId = Math.max(...ids);
|
||||
|
||||
for (let i = 0; i <= latestId; i++) {
|
||||
await resetInternetCredentials(`accounts/${namespaceChainId}/${i}`);
|
||||
}
|
||||
await resetInternetCredentials(`addAccountCounter/${namespaceChainId}`);
|
||||
await resetInternetCredentials(`accountIndices/${namespaceChainId}`);
|
||||
});
|
||||
};
|
||||
|
||||
export {
|
||||
getMnemonic,
|
||||
getPathKey,
|
||||
updateGlobalCounter,
|
||||
updateAccountIndices,
|
||||
getHDPath,
|
||||
resetKeyServers,
|
||||
};
|
||||
|
@ -10,19 +10,21 @@ import { SignDoc } from 'cosmjs-types/cosmos/tx/v1beta1/tx';
|
||||
import { SignMessageParams } from '../types';
|
||||
import { getDirectWallet, getMnemonic, getPathKey } from './misc';
|
||||
import { getCosmosAccounts } from './accounts';
|
||||
import { COSMOS, EIP155 } from './constants';
|
||||
|
||||
const signMessage = async ({
|
||||
message,
|
||||
network,
|
||||
namespace,
|
||||
chainId,
|
||||
accountId,
|
||||
}: SignMessageParams): Promise<string | undefined> => {
|
||||
const path = (await getPathKey(network, accountId)).path;
|
||||
const path = await getPathKey(`${namespace}:${chainId}`, accountId);
|
||||
|
||||
switch (network) {
|
||||
case 'eth':
|
||||
return await signEthMessage(message, accountId);
|
||||
case 'cosmos':
|
||||
return await signCosmosMessage(message, path);
|
||||
switch (namespace) {
|
||||
case EIP155:
|
||||
return await signEthMessage(message, accountId, chainId);
|
||||
case COSMOS:
|
||||
return await signCosmosMessage(message, path.path);
|
||||
default:
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
@ -31,9 +33,11 @@ const signMessage = async ({
|
||||
const signEthMessage = async (
|
||||
message: string,
|
||||
accountId: number,
|
||||
chainId: string,
|
||||
): Promise<string | undefined> => {
|
||||
try {
|
||||
const privKey = (await getPathKey('eth', accountId)).privKey;
|
||||
const privKey = (await getPathKey(`${EIP155}:${chainId}`, accountId))
|
||||
.privKey;
|
||||
const wallet = new Wallet(privKey);
|
||||
const signature = await wallet.signMessage(message);
|
||||
|
||||
@ -83,12 +87,12 @@ const signCosmosMessage = async (
|
||||
};
|
||||
|
||||
const signDirectMessage = async (
|
||||
network: string,
|
||||
namespaceChainId: string,
|
||||
accountId: number,
|
||||
signDoc: SignDoc,
|
||||
): Promise<string | undefined> => {
|
||||
try {
|
||||
const path = (await getPathKey(network, accountId)).path;
|
||||
const path = (await getPathKey(namespaceChainId, accountId)).path;
|
||||
const mnemonic = await getMnemonic();
|
||||
const { directWallet, data } = await getDirectWallet(mnemonic, path);
|
||||
|
||||
|
@ -21,19 +21,21 @@ export async function approveWalletConnectRequest(
|
||||
networksData: NetworksDataState[],
|
||||
requestEvent: SignClientTypes.EventArguments['session_request'],
|
||||
account: Account,
|
||||
network: string,
|
||||
namespace: string,
|
||||
chainId: string,
|
||||
message?: string,
|
||||
provider?: providers.JsonRpcProvider | SigningStargateClient,
|
||||
) {
|
||||
const { params, id } = requestEvent;
|
||||
const { request } = params;
|
||||
|
||||
const chainId = requestEvent.params.chainId;
|
||||
const requestChainId = requestEvent.params.chainId;
|
||||
const requestedChain = networksData.find(
|
||||
networkData => networkData.chainId === chainId,
|
||||
networkData => networkData.chainId === requestChainId.split(':')[1],
|
||||
);
|
||||
|
||||
const path = (await getPathKey(network, account.counterId)).path;
|
||||
const path = (await getPathKey(`${namespace}:${chainId}`, account.index))
|
||||
.path;
|
||||
const mnemonic = await getMnemonic();
|
||||
const cosmosAccount = await getCosmosAccounts(mnemonic, path);
|
||||
const address = account.address;
|
||||
@ -44,7 +46,9 @@ export async function approveWalletConnectRequest(
|
||||
throw new Error('JSON RPC provider not found');
|
||||
}
|
||||
|
||||
const privKey = (await getPathKey('eth', account.counterId)).privKey;
|
||||
const privKey = (
|
||||
await getPathKey(`${namespace}:${chainId}`, account.index)
|
||||
).privKey;
|
||||
const wallet = new Wallet(privKey);
|
||||
const sendTransaction = request.params[0];
|
||||
|
||||
@ -64,7 +68,11 @@ export async function approveWalletConnectRequest(
|
||||
throw new Error('Message to be signed not found');
|
||||
}
|
||||
|
||||
const ethSignature = await signEthMessage(message, account.counterId);
|
||||
const ethSignature = await signEthMessage(
|
||||
message,
|
||||
account.index,
|
||||
chainId,
|
||||
);
|
||||
return formatJsonRpcResult(id, ethSignature);
|
||||
|
||||
case 'cosmos_signDirect':
|
||||
@ -78,8 +86,8 @@ export async function approveWalletConnectRequest(
|
||||
);
|
||||
|
||||
const cosmosDirectSignature = await signDirectMessage(
|
||||
network,
|
||||
account.counterId,
|
||||
`${namespace}:${chainId}`,
|
||||
account.index,
|
||||
{
|
||||
...request.params.signDoc,
|
||||
bodyBytes: bodyBytesArray,
|
||||
@ -106,7 +114,10 @@ export async function approveWalletConnectRequest(
|
||||
});
|
||||
|
||||
case 'cosmos_sendTokens':
|
||||
const amount = coins(request.params[0].value, requestedChain!.chainId);
|
||||
const amount = coins(
|
||||
request.params[0].value,
|
||||
requestedChain!.nativeDenom!,
|
||||
);
|
||||
const gasPrice = GasPrice.fromString(
|
||||
request.params[0].gasPrice.toString(),
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user