forked from cerc-io/laconic-wallet
Hard-code the prefix of the hd paths and refactor functions
This commit is contained in:
parent
63edfdd990
commit
9a6e52de90
@ -6,7 +6,7 @@ import { useNavigation } from '@react-navigation/native';
|
||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||
|
||||
import { AccountsProps, StackParamsList, Account } from '../types';
|
||||
import { addAccount } from '../utils';
|
||||
import { addAccount } from '../utils/Accounts';
|
||||
import styles from '../styles/stylesheet';
|
||||
import HDPathDialog from './HDPathDialog';
|
||||
|
||||
@ -23,6 +23,7 @@ const Accounts: React.FC<AccountsProps> = ({
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
const [isAccountCreating, setIsAccountCreating] = useState(false);
|
||||
const [hdDialog, setHdDialog] = useState(false);
|
||||
const [pathCode, setPathCode] = useState('');
|
||||
|
||||
const handlePress = () => setExpanded(!expanded);
|
||||
|
||||
@ -67,6 +68,7 @@ const Accounts: React.FC<AccountsProps> = ({
|
||||
hideDialog={() => setHdDialog(false)}
|
||||
updateAccounts={updateAccounts}
|
||||
updateIndex={updateId}
|
||||
pathCode={pathCode} // Pass pathCode here
|
||||
/>
|
||||
<List.Accordion
|
||||
title={`Account ${currentIndex + 1}`}
|
||||
@ -89,6 +91,16 @@ const Accounts: React.FC<AccountsProps> = ({
|
||||
mode="contained"
|
||||
onPress={() => {
|
||||
setHdDialog(true);
|
||||
switch (network) {
|
||||
case 'eth':
|
||||
setPathCode("m/44'/60'/");
|
||||
break;
|
||||
case 'cosmos':
|
||||
setPathCode("m/44'/118'/");
|
||||
break;
|
||||
default:
|
||||
setPathCode('');
|
||||
}
|
||||
}}>
|
||||
Add Account from HD path
|
||||
</Button>
|
||||
|
@ -1,15 +1,16 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ScrollView, View } from 'react-native';
|
||||
import { ScrollView, View, Text } from 'react-native';
|
||||
import { Button, TextInput } from 'react-native-paper';
|
||||
|
||||
import { addAccountFromHDPath } from '../utils';
|
||||
import { addAccountFromHDPath } from '../utils/Accounts';
|
||||
import { Account } from '../types';
|
||||
|
||||
const HDPath = ({
|
||||
pathCode,
|
||||
updateAccounts,
|
||||
updateIndex,
|
||||
hideDialog,
|
||||
}: {
|
||||
pathCode: string;
|
||||
updateIndex: (index: number) => void;
|
||||
updateAccounts: (account: Account) => void;
|
||||
hideDialog: () => void;
|
||||
@ -19,7 +20,8 @@ const HDPath = ({
|
||||
|
||||
const createFromHDPathHandler = async () => {
|
||||
setIsAccountCreating(true);
|
||||
const newAccount = await addAccountFromHDPath(path);
|
||||
const hdPath = pathCode + path;
|
||||
const newAccount = await addAccountFromHDPath(hdPath);
|
||||
setIsAccountCreating(false);
|
||||
|
||||
if (newAccount) {
|
||||
@ -31,11 +33,17 @@ const HDPath = ({
|
||||
|
||||
return (
|
||||
<ScrollView style={{ marginTop: 24, paddingHorizontal: 24 }}>
|
||||
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
|
||||
<Text style={{ color: 'black', fontSize: 18, padding: 10 }}>
|
||||
{pathCode}
|
||||
</Text>
|
||||
<TextInput
|
||||
mode="outlined"
|
||||
onChangeText={text => setPath(text)}
|
||||
value={path}
|
||||
style={{ flex: 1 }}
|
||||
/>
|
||||
</View>
|
||||
<View style={{ marginTop: 20, width: 200, alignSelf: 'center' }}>
|
||||
<Button
|
||||
mode="contained"
|
||||
|
@ -8,6 +8,7 @@ const HDPathDialog = ({
|
||||
hideDialog,
|
||||
updateIndex,
|
||||
updateAccounts,
|
||||
pathCode,
|
||||
}: HDPathDialogProps) => {
|
||||
return (
|
||||
<Portal>
|
||||
@ -15,6 +16,7 @@ const HDPathDialog = ({
|
||||
<Dialog.Title>Add account from HD path</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
<HDPath
|
||||
pathCode={pathCode}
|
||||
updateIndex={updateIndex}
|
||||
updateAccounts={updateAccounts}
|
||||
hideDialog={hideDialog}
|
||||
|
@ -2,7 +2,7 @@ import React, { useState } from 'react';
|
||||
import { Alert, View } from 'react-native';
|
||||
import { Button } from 'react-native-paper';
|
||||
|
||||
import { createWallet, resetWallet } from '../utils';
|
||||
import { createWallet, resetWallet } from '../utils/Accounts';
|
||||
import { DialogComponent } from './Dialog';
|
||||
import { NetworkDropdown } from './NetworkDropdown';
|
||||
import { Account, AccountsState } from '../types';
|
||||
|
@ -5,8 +5,8 @@ import { Button, Text, TextInput } from 'react-native-paper';
|
||||
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
||||
|
||||
import { StackParamsList } from '../types';
|
||||
import { signMessage } from '../utils';
|
||||
import styles from '../styles/stylesheet';
|
||||
import { signMessage } from '../utils/SignMessage';
|
||||
|
||||
type SignProps = NativeStackScreenProps<StackParamsList, 'SignMessage'>;
|
||||
|
||||
|
1
types.ts
1
types.ts
@ -61,6 +61,7 @@ export type ResetDialogProps = {
|
||||
};
|
||||
|
||||
export type HDPathDialogProps = {
|
||||
pathCode: string;
|
||||
visible: boolean;
|
||||
hideDialog: () => void;
|
||||
updateIndex: (index: number) => void;
|
||||
|
406
utils.ts
406
utils.ts
@ -1,406 +0,0 @@
|
||||
/* Importing this library provides react native with a secure random source.
|
||||
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
|
||||
import 'react-native-get-random-values';
|
||||
import '@ethersproject/shims';
|
||||
|
||||
import { Wallet, utils } from 'ethers';
|
||||
import { HDNode } from 'ethers/lib/utils';
|
||||
import {
|
||||
setInternetCredentials,
|
||||
getInternetCredentials,
|
||||
resetInternetCredentials,
|
||||
} from 'react-native-keychain';
|
||||
|
||||
import { AccountData, Secp256k1HdWallet } from '@cosmjs/amino';
|
||||
import { stringToPath } from '@cosmjs/crypto';
|
||||
|
||||
import { Account, SignMessageParams, WalletDetails } from './types';
|
||||
|
||||
const createWallet = async (): Promise<WalletDetails> => {
|
||||
try {
|
||||
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 ethAddress = ethNode.address;
|
||||
const cosmosAddress = (await getCosmosAccounts(mnemonic, `0'/0/0`)).data
|
||||
.address;
|
||||
|
||||
const ethAccountInfo = `${0},${ethNode.privateKey}`;
|
||||
const cosmosAccountInfo = `${0},${cosmosNode.privateKey}`;
|
||||
|
||||
await setInternetCredentials(
|
||||
'eth:keyServer:0',
|
||||
'eth:pathKey:0',
|
||||
ethAccountInfo,
|
||||
);
|
||||
await setInternetCredentials(
|
||||
'cosmos:keyServer:0',
|
||||
'cosmos:pathKey:0',
|
||||
cosmosAccountInfo,
|
||||
);
|
||||
|
||||
await setInternetCredentials('eth:accountIndices', 'ethCounter', '0');
|
||||
await setInternetCredentials('cosmos:accountIndices', 'cosmosCounter', '0');
|
||||
|
||||
await setInternetCredentials('eth:globalCounter', 'ethGlobal', '0');
|
||||
await 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 };
|
||||
}
|
||||
};
|
||||
|
||||
const addAccount = async (network: string): Promise<Account | undefined> => {
|
||||
try {
|
||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||
if (!mnemonicStore) {
|
||||
throw new Error('Mnemonic not found!');
|
||||
}
|
||||
|
||||
const mnemonic = mnemonicStore.password;
|
||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||
|
||||
const idStore = await getInternetCredentials(`${network}:accountIndices`);
|
||||
if (!idStore) {
|
||||
throw new Error('Account id not found');
|
||||
}
|
||||
|
||||
const accountIds = idStore.password;
|
||||
const ids = accountIds.split(',').map(Number);
|
||||
const id = ids[ids.length - 1] + 1;
|
||||
|
||||
const hdPath =
|
||||
network === 'eth' ? `m/44'/60'/0'/0/${id}` : `m/44'/118'/0'/0/${id}`;
|
||||
|
||||
const node = hdNode.derivePath(hdPath);
|
||||
const privKey = node.privateKey;
|
||||
const pubKey = node.publicKey;
|
||||
|
||||
let address: string;
|
||||
|
||||
switch (network) {
|
||||
case 'eth':
|
||||
address = node.address;
|
||||
break;
|
||||
case 'cosmos':
|
||||
address = (await getCosmosAccounts(mnemonic, `0'/0/${id}`)).data
|
||||
.address;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
|
||||
let indices = idStore.password;
|
||||
indices += `,${id.toString()}`;
|
||||
|
||||
await resetInternetCredentials(`${network}:accountIndices`);
|
||||
await setInternetCredentials(
|
||||
`${network}:accountIndices`,
|
||||
`${network}Counter`,
|
||||
indices,
|
||||
);
|
||||
|
||||
const counterStore = await getInternetCredentials(
|
||||
`${network}:globalCounter`,
|
||||
);
|
||||
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;
|
||||
accountCounter += `,${counterId.toString()}`;
|
||||
|
||||
await resetInternetCredentials(`${network}:globalCounter`);
|
||||
|
||||
await setInternetCredentials(
|
||||
`${network}:globalCounter`,
|
||||
`${network}Global`,
|
||||
accountCounter,
|
||||
);
|
||||
|
||||
await setInternetCredentials(
|
||||
`${network}:keyServer:${counterId}`,
|
||||
`${network}:pathKey:${counterId}`,
|
||||
`0'/0/${id},${privKey}`,
|
||||
);
|
||||
|
||||
return { counterId, pubKey, address, hdPath };
|
||||
} catch (error) {
|
||||
console.error('Error creating account:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const addAccountFromHDPath = async (
|
||||
hdPath: string,
|
||||
): Promise<
|
||||
| { counterId: number; pubKey: string; address: string; hdPath: string }
|
||||
| undefined
|
||||
> => {
|
||||
try {
|
||||
const account = await accountInfoFromHDPath(hdPath);
|
||||
if (!account) {
|
||||
throw new Error('Error while creating account');
|
||||
}
|
||||
|
||||
const { privKey, pubKey, address, network } = account;
|
||||
|
||||
const parts = hdPath.split('/');
|
||||
const coinType = parts[2];
|
||||
const path = parts.slice(-3).join('/');
|
||||
|
||||
const counterStore = await getInternetCredentials(
|
||||
`${network}:globalCounter`,
|
||||
);
|
||||
|
||||
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;
|
||||
accountCounter += `,${counterId.toString()}`;
|
||||
|
||||
await resetInternetCredentials(`${network}:globalCounter`);
|
||||
|
||||
await setInternetCredentials(
|
||||
`${network}:globalCounter`,
|
||||
`${network}Global`,
|
||||
accountCounter,
|
||||
);
|
||||
|
||||
const accountInfo = `${path},${privKey}`;
|
||||
|
||||
switch (coinType) {
|
||||
case "60'":
|
||||
await setInternetCredentials(
|
||||
`eth:keyServer:${counterId}`,
|
||||
`eth:pathKey:${counterId}`,
|
||||
accountInfo,
|
||||
);
|
||||
break;
|
||||
case "118'":
|
||||
await setInternetCredentials(
|
||||
`cosmos:keyServer${counterId}`,
|
||||
`cosmos:pathKey:${counterId}`,
|
||||
accountInfo,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return { counterId, pubKey, address, hdPath };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const accountInfoFromHDPath = async (
|
||||
hdPath: string,
|
||||
): Promise<
|
||||
| { privKey: string; pubKey: string; address: string; network: string }
|
||||
| undefined
|
||||
> => {
|
||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||
if (!mnemonicStore) {
|
||||
throw new Error('Mnemonic not found!');
|
||||
}
|
||||
|
||||
const mnemonic = mnemonicStore.password;
|
||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||
const node = hdNode.derivePath(hdPath);
|
||||
|
||||
const privKey = node.privateKey;
|
||||
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;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
return { privKey, pubKey, address, network };
|
||||
};
|
||||
|
||||
const signMessage = async ({
|
||||
message,
|
||||
network,
|
||||
accountId,
|
||||
}: SignMessageParams): Promise<string | undefined> => {
|
||||
const pathKeyStore = await getInternetCredentials(
|
||||
`${network}:keyServer:${accountId}`,
|
||||
);
|
||||
|
||||
if (!pathKeyStore) {
|
||||
throw new Error('Error while fetching counter');
|
||||
}
|
||||
|
||||
const pathKeyVal = pathKeyStore.password;
|
||||
const pathkey = pathKeyVal.split(',');
|
||||
const hdPath = pathkey[0];
|
||||
|
||||
switch (network) {
|
||||
case 'eth':
|
||||
return await signEthMessage(message, accountId);
|
||||
case 'cosmos':
|
||||
return await signCosmosMessage(message, hdPath);
|
||||
default:
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
};
|
||||
|
||||
const signEthMessage = async (
|
||||
message: string,
|
||||
accountId: number,
|
||||
): Promise<string | undefined> => {
|
||||
try {
|
||||
const keyCred = await getInternetCredentials(`eth:keyServer:${accountId}`);
|
||||
|
||||
if (!keyCred) {
|
||||
throw new Error('Failed to retrieve internet credentials');
|
||||
}
|
||||
|
||||
const pathKey = keyCred.password;
|
||||
const privKeyVal = pathKey.split(',');
|
||||
const privKey = privKeyVal[privKeyVal.length - 1];
|
||||
const wallet = new Wallet(privKey);
|
||||
const signature = await wallet.signMessage(message);
|
||||
|
||||
return signature;
|
||||
} catch (error) {
|
||||
console.error('Error signing Ethereum message:', error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const signCosmosMessage = async (
|
||||
message: string,
|
||||
hdPath: string,
|
||||
): Promise<string | undefined> => {
|
||||
try {
|
||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||
|
||||
if (!mnemonicStore) {
|
||||
throw new Error('Mnemonic not found');
|
||||
}
|
||||
|
||||
const mnemonic = mnemonicStore.password;
|
||||
const cosmosAccount = await getCosmosAccounts(mnemonic, hdPath);
|
||||
const cosmosSignature = await cosmosAccount.cosmosWallet.signAmino(
|
||||
cosmosAccount.data.address,
|
||||
{
|
||||
chain_id: '',
|
||||
account_number: '0',
|
||||
sequence: '0',
|
||||
fee: {
|
||||
gas: '0',
|
||||
amount: [],
|
||||
},
|
||||
msgs: [
|
||||
{
|
||||
type: 'sign/MsgSignData',
|
||||
value: {
|
||||
signer: cosmosAccount.data.address,
|
||||
data: btoa(message),
|
||||
},
|
||||
},
|
||||
],
|
||||
memo: '',
|
||||
},
|
||||
);
|
||||
|
||||
return cosmosSignature.signature.signature;
|
||||
} catch (error) {
|
||||
console.error('Error signing Cosmos message:', error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const getCosmosAccounts = async (
|
||||
mnemonic: string,
|
||||
hdPath: string,
|
||||
): Promise<{ cosmosWallet: Secp256k1HdWallet; data: AccountData }> => {
|
||||
const cosmosWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, {
|
||||
hdPaths: [stringToPath(`m/44'/118'/${hdPath}`)],
|
||||
});
|
||||
|
||||
const accountsData = await cosmosWallet.getAccounts();
|
||||
const data = accountsData[0];
|
||||
|
||||
return { cosmosWallet, data };
|
||||
};
|
||||
|
||||
const resetKeyServers = async (prefix: string) => {
|
||||
const idStore = await getInternetCredentials(`${prefix}:accountIndices`);
|
||||
if (!idStore) {
|
||||
throw new Error('Account id not found.');
|
||||
}
|
||||
|
||||
const accountIds = idStore.password;
|
||||
const ids = accountIds.split(',').map(Number);
|
||||
const id = ids[ids.length - 1];
|
||||
|
||||
for (let i = 0; i <= id; i++) {
|
||||
await resetInternetCredentials(`${prefix}:keyServer:${i}`);
|
||||
}
|
||||
};
|
||||
|
||||
const resetWallet = async () => {
|
||||
try {
|
||||
await resetInternetCredentials('mnemonicServer');
|
||||
|
||||
await resetKeyServers('eth');
|
||||
await resetKeyServers('cosmos');
|
||||
|
||||
await resetInternetCredentials('eth:accountIndices');
|
||||
await resetInternetCredentials('cosmos:accountIndices');
|
||||
|
||||
await resetInternetCredentials('eth:globalCounter');
|
||||
await resetInternetCredentials('cosmos:globalCounter');
|
||||
} catch (error) {
|
||||
console.error('Error resetting wallet:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
createWallet,
|
||||
addAccount,
|
||||
signMessage,
|
||||
resetWallet,
|
||||
addAccountFromHDPath,
|
||||
};
|
168
utils/Accounts.ts
Normal file
168
utils/Accounts.ts
Normal file
@ -0,0 +1,168 @@
|
||||
/* Importing this library provides react native with a secure random source.
|
||||
For more information, "visit https://docs.ethers.org/v5/cookbook/react-native/#cookbook-reactnative-security" */
|
||||
import 'react-native-get-random-values';
|
||||
import '@ethersproject/shims';
|
||||
|
||||
import { utils } from 'ethers';
|
||||
import { HDNode } from 'ethers/lib/utils';
|
||||
import {
|
||||
setInternetCredentials,
|
||||
resetInternetCredentials,
|
||||
} from 'react-native-keychain';
|
||||
|
||||
import { Account, WalletDetails } from '../types';
|
||||
import {
|
||||
accountInfoFromHDPath,
|
||||
getAddress,
|
||||
getCosmosAccounts,
|
||||
getHDPath,
|
||||
getMnemonic,
|
||||
getNextAccountId,
|
||||
resetKeyServers,
|
||||
updateAccountIndices,
|
||||
updateGlobalCounter,
|
||||
} from './utils';
|
||||
|
||||
const createWallet = async (): Promise<WalletDetails> => {
|
||||
try {
|
||||
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 ethAddress = ethNode.address;
|
||||
const cosmosAddress = (await getCosmosAccounts(mnemonic, `0'/0/0`)).data
|
||||
.address;
|
||||
|
||||
const ethAccountInfo = `${`0'/0/0`},${ethNode.privateKey}`;
|
||||
const cosmosAccountInfo = `${`0'/0/0`},${cosmosNode.privateKey}`;
|
||||
|
||||
await Promise.all([
|
||||
setInternetCredentials(
|
||||
'eth:keyServer:0',
|
||||
'eth:pathKey:0',
|
||||
ethAccountInfo,
|
||||
),
|
||||
setInternetCredentials(
|
||||
'cosmos:keyServer:0',
|
||||
'cosmos:pathKey:0',
|
||||
cosmosAccountInfo,
|
||||
),
|
||||
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 };
|
||||
}
|
||||
};
|
||||
|
||||
const addAccount = async (network: string): Promise<Account | undefined> => {
|
||||
try {
|
||||
const mnemonic = await getMnemonic();
|
||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||
const id = await getNextAccountId(network);
|
||||
const hdPath = getHDPath(network, id);
|
||||
|
||||
const node = hdNode.derivePath(hdPath);
|
||||
const pubKey = node.publicKey;
|
||||
const address = await getAddress(network, mnemonic, id);
|
||||
|
||||
await updateAccountIndices(network, id);
|
||||
const { accountCounter, counterId } = await updateGlobalCounter(network);
|
||||
|
||||
await Promise.all([
|
||||
resetInternetCredentials(`${network}:globalCounter`),
|
||||
setInternetCredentials(
|
||||
`${network}:globalCounter`,
|
||||
`${network}Global`,
|
||||
accountCounter,
|
||||
),
|
||||
setInternetCredentials(
|
||||
`${network}:keyServer:${counterId}`,
|
||||
`${network}:pathKey:${counterId}`,
|
||||
`0'/0/${id},${node.privateKey}`,
|
||||
),
|
||||
]);
|
||||
|
||||
return { counterId, pubKey, address, hdPath };
|
||||
} catch (error) {
|
||||
console.error('Error creating account:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const addAccountFromHDPath = async (
|
||||
hdPath: string,
|
||||
): Promise<Account | undefined> => {
|
||||
try {
|
||||
const account = await accountInfoFromHDPath(hdPath);
|
||||
if (!account) {
|
||||
throw new Error('Error while creating account');
|
||||
}
|
||||
|
||||
const parts = hdPath.split('/');
|
||||
const path = parts.slice(-3).join('/');
|
||||
|
||||
const { privKey, pubKey, address, network } = account;
|
||||
|
||||
const { accountCounter, counterId } = await updateGlobalCounter(network);
|
||||
const updatedAccountCounter = `${accountCounter},${counterId.toString()}`;
|
||||
|
||||
await Promise.all([
|
||||
resetInternetCredentials(`${network}:globalCounter`),
|
||||
setInternetCredentials(
|
||||
`${network}:globalCounter`,
|
||||
`${network}Global`,
|
||||
updatedAccountCounter,
|
||||
),
|
||||
setInternetCredentials(
|
||||
`${network}:keyServer:${counterId}`,
|
||||
`${network}:pathKey:${counterId}`,
|
||||
`${path},${privKey}`,
|
||||
),
|
||||
]);
|
||||
|
||||
return { counterId, pubKey, address, hdPath };
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
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'),
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error('Error resetting wallet:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export { createWallet, addAccount, addAccountFromHDPath, resetWallet };
|
80
utils/SignMessage.ts
Normal file
80
utils/SignMessage.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import 'react-native-get-random-values';
|
||||
import '@ethersproject/shims';
|
||||
|
||||
import { Wallet } from 'ethers';
|
||||
|
||||
import { SignMessageParams } from '../types';
|
||||
import { getCosmosAccounts, getMnemonic, getPathKey } from './utils';
|
||||
|
||||
const signMessage = async ({
|
||||
message,
|
||||
network,
|
||||
accountId,
|
||||
}: SignMessageParams): Promise<string | undefined> => {
|
||||
const hdPath = (await getPathKey(network, accountId)).hdPath;
|
||||
|
||||
switch (network) {
|
||||
case 'eth':
|
||||
return await signEthMessage(message, accountId);
|
||||
case 'cosmos':
|
||||
return await signCosmosMessage(message, hdPath);
|
||||
default:
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
};
|
||||
|
||||
const signEthMessage = async (
|
||||
message: string,
|
||||
accountId: number,
|
||||
): Promise<string | undefined> => {
|
||||
try {
|
||||
const privKey = (await getPathKey('eth', accountId)).privKey;
|
||||
const wallet = new Wallet(privKey);
|
||||
const signature = await wallet.signMessage(message);
|
||||
|
||||
return signature;
|
||||
} catch (error) {
|
||||
console.error('Error signing Ethereum message:', error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const signCosmosMessage = async (
|
||||
message: string,
|
||||
hdPath: string,
|
||||
): Promise<string | undefined> => {
|
||||
try {
|
||||
const mnemonic = await getMnemonic();
|
||||
const cosmosAccount = await getCosmosAccounts(mnemonic, hdPath);
|
||||
const address = cosmosAccount.data.address;
|
||||
const cosmosSignature = await cosmosAccount.cosmosWallet.signAmino(
|
||||
address,
|
||||
{
|
||||
chain_id: '',
|
||||
account_number: '0',
|
||||
sequence: '0',
|
||||
fee: {
|
||||
gas: '0',
|
||||
amount: [],
|
||||
},
|
||||
msgs: [
|
||||
{
|
||||
type: 'sign/MsgSignData',
|
||||
value: {
|
||||
signer: address,
|
||||
data: btoa(message),
|
||||
},
|
||||
},
|
||||
],
|
||||
memo: '',
|
||||
},
|
||||
);
|
||||
|
||||
return cosmosSignature.signature.signature;
|
||||
} catch (error) {
|
||||
console.error('Error signing Cosmos message:', error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export { signMessage, signEthMessage, signCosmosMessage };
|
211
utils/utils.ts
Normal file
211
utils/utils.ts
Normal file
@ -0,0 +1,211 @@
|
||||
import 'react-native-get-random-values';
|
||||
import '@ethersproject/shims';
|
||||
|
||||
import { HDNode } from 'ethers/lib/utils';
|
||||
import {
|
||||
getInternetCredentials,
|
||||
resetInternetCredentials,
|
||||
setInternetCredentials,
|
||||
} from 'react-native-keychain';
|
||||
|
||||
import { AccountData, Secp256k1HdWallet } from '@cosmjs/amino';
|
||||
import { stringToPath } from '@cosmjs/crypto';
|
||||
|
||||
const getMnemonic = async (): Promise<string> => {
|
||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||
if (!mnemonicStore) {
|
||||
throw new Error('Mnemonic not found!');
|
||||
}
|
||||
|
||||
const mnemonic = mnemonicStore.password;
|
||||
return mnemonic;
|
||||
};
|
||||
|
||||
const getHDPath = (network: string, id: number): string => {
|
||||
return network === 'eth' ? `m/44'/60'/0'/0/${id}` : `m/44'/118'/0'/0/${id}`;
|
||||
};
|
||||
|
||||
const getAddress = async (
|
||||
network: string,
|
||||
mnemonic: string,
|
||||
id: number,
|
||||
): Promise<string> => {
|
||||
switch (network) {
|
||||
case 'eth':
|
||||
return HDNode.fromMnemonic(mnemonic).derivePath(`m/44'/60'/0'/0/${id}`)
|
||||
.address;
|
||||
case 'cosmos':
|
||||
return (await getCosmosAccounts(mnemonic, `0'/0/${id}`)).data.address;
|
||||
default:
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
};
|
||||
|
||||
const getCosmosAccounts = async (
|
||||
mnemonic: string,
|
||||
hdPath: string,
|
||||
): Promise<{ cosmosWallet: Secp256k1HdWallet; data: AccountData }> => {
|
||||
const cosmosWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, {
|
||||
hdPaths: [stringToPath(`m/44'/118'/${hdPath}`)],
|
||||
});
|
||||
|
||||
const accountsData = await cosmosWallet.getAccounts();
|
||||
const data = accountsData[0];
|
||||
|
||||
return { cosmosWallet, data };
|
||||
};
|
||||
|
||||
const accountInfoFromHDPath = async (
|
||||
hdPath: string,
|
||||
): Promise<
|
||||
| { privKey: string; pubKey: string; address: string; network: string }
|
||||
| undefined
|
||||
> => {
|
||||
const mnemonicStore = await getInternetCredentials('mnemonicServer');
|
||||
if (!mnemonicStore) {
|
||||
throw new Error('Mnemonic not found!');
|
||||
}
|
||||
|
||||
const mnemonic = mnemonicStore.password;
|
||||
const hdNode = HDNode.fromMnemonic(mnemonic);
|
||||
const node = hdNode.derivePath(hdPath);
|
||||
|
||||
const privKey = node.privateKey;
|
||||
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;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid wallet type');
|
||||
}
|
||||
return { privKey, pubKey, address, network };
|
||||
};
|
||||
|
||||
const getPathKey = async (
|
||||
network: string,
|
||||
accountId: number,
|
||||
): Promise<{ hdPath: string; privKey: string }> => {
|
||||
const pathKeyStore = await getInternetCredentials(
|
||||
`${network}:keyServer:${accountId}`,
|
||||
);
|
||||
|
||||
if (!pathKeyStore) {
|
||||
throw new Error('Error while fetching counter');
|
||||
}
|
||||
|
||||
const pathKeyVal = pathKeyStore.password;
|
||||
const pathkey = pathKeyVal.split(',');
|
||||
const hdPath = pathkey[0];
|
||||
const privKey = pathkey[1];
|
||||
|
||||
return { hdPath, privKey };
|
||||
};
|
||||
|
||||
const getGlobalCounter = async (
|
||||
network: string,
|
||||
): Promise<{
|
||||
accountCounter: string;
|
||||
counterIds: number[];
|
||||
counterId: number;
|
||||
}> => {
|
||||
const counterStore = await getInternetCredentials(`${network}:globalCounter`);
|
||||
|
||||
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;
|
||||
|
||||
return { accountCounter, counterIds, counterId };
|
||||
};
|
||||
|
||||
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()}`;
|
||||
|
||||
await resetInternetCredentials(`${network}:globalCounter`);
|
||||
await setInternetCredentials(
|
||||
`${network}:globalCounter`,
|
||||
`${network}Global`,
|
||||
updatedAccountCounter,
|
||||
);
|
||||
|
||||
return { accountCounter: updatedAccountCounter, counterId };
|
||||
};
|
||||
|
||||
const getNextAccountId = async (network: string): Promise<number> => {
|
||||
const idStore = await getInternetCredentials(`${network}:accountIndices`);
|
||||
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 updateAccountIndices = async (
|
||||
network: string,
|
||||
id: number,
|
||||
): Promise<void> => {
|
||||
const idStore = await getInternetCredentials(`${network}:accountIndices`);
|
||||
if (!idStore) {
|
||||
throw new Error('Account id not found');
|
||||
}
|
||||
|
||||
const updatedIndices = `${idStore.password},${id.toString()}`;
|
||||
await resetInternetCredentials(`${network}:accountIndices`);
|
||||
await setInternetCredentials(
|
||||
`${network}:accountIndices`,
|
||||
`${network}Counter`,
|
||||
updatedIndices,
|
||||
);
|
||||
};
|
||||
|
||||
const resetKeyServers = async (prefix: string) => {
|
||||
const idStore = await getInternetCredentials(`${prefix}:accountIndices`);
|
||||
if (!idStore) {
|
||||
throw new Error('Account id not found.');
|
||||
}
|
||||
|
||||
const accountIds = idStore.password;
|
||||
const ids = accountIds.split(',').map(Number);
|
||||
const id = ids[ids.length - 1];
|
||||
|
||||
for (let i = 0; i <= id; i++) {
|
||||
await resetInternetCredentials(`${prefix}:keyServer:${i}`);
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
accountInfoFromHDPath,
|
||||
getCosmosAccounts,
|
||||
getMnemonic,
|
||||
getPathKey,
|
||||
getNextAccountId,
|
||||
updateGlobalCounter,
|
||||
updateAccountIndices,
|
||||
getHDPath,
|
||||
getAddress,
|
||||
resetKeyServers,
|
||||
};
|
Loading…
Reference in New Issue
Block a user