forked from cerc-io/laconic-wallet
* Display mnemonic on wallet creation * Change srp to mnemonic * Display mnemonic in a grid * Remove line * Make review changes --------- Co-authored-by: Adw8 <adwait@deepstacksoft.com>
255 lines
6.8 KiB
TypeScript
255 lines
6.8 KiB
TypeScript
/* 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)).data.address;
|
|
|
|
await setInternetCredentials(
|
|
'eth:keyServer:0',
|
|
'eth:key:0',
|
|
ethNode.privateKey,
|
|
);
|
|
await setInternetCredentials(
|
|
'cosmos:keyServer:0',
|
|
'cosmos:key:0',
|
|
cosmosNode.privateKey,
|
|
);
|
|
|
|
await setInternetCredentials('eth:accountIndices', 'ethAccount', '0');
|
|
await setInternetCredentials('cosmos:accountIndices', 'cosmosAccount', '0');
|
|
|
|
const ethAccounts = {
|
|
id: 0,
|
|
pubKey: ethNode.publicKey,
|
|
address: ethAddress,
|
|
};
|
|
|
|
const cosmosAccounts = {
|
|
id: 0,
|
|
pubKey: cosmosNode.publicKey,
|
|
address: cosmosAddress,
|
|
};
|
|
|
|
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 derivationPath =
|
|
network === 'eth' ? `m/44'/60'/0'/0/${id}` : `m/44'/118'/0'/0/${id}`;
|
|
|
|
const node = hdNode.derivePath(derivationPath);
|
|
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, id)).data.address;
|
|
break;
|
|
default:
|
|
throw new Error('Invalid wallet type');
|
|
}
|
|
|
|
await setInternetCredentials(
|
|
`${network}:keyServer:${id}`,
|
|
`${network}:key:${id}`,
|
|
privKey,
|
|
);
|
|
|
|
const accountIndicesKey = `${network}:accountIndices`;
|
|
const accountIndices = await getInternetCredentials(accountIndicesKey);
|
|
|
|
if (!accountIndices) {
|
|
throw new Error('Account not found!');
|
|
}
|
|
let indices = accountIndices.password;
|
|
indices += `,${id.toString()}`;
|
|
|
|
await resetInternetCredentials(accountIndicesKey);
|
|
await setInternetCredentials(
|
|
accountIndicesKey,
|
|
`${network}Account`,
|
|
indices,
|
|
);
|
|
|
|
return { pubKey, address, id };
|
|
} catch (error) {
|
|
console.error('Error creating account:', error);
|
|
}
|
|
};
|
|
|
|
const signMessage = async ({
|
|
message,
|
|
network,
|
|
accountId,
|
|
}: SignMessageParams): Promise<string | undefined> => {
|
|
switch (network) {
|
|
case 'eth':
|
|
return await signEthMessage(message, accountId);
|
|
case 'cosmos':
|
|
return await signCosmosMessage(message, accountId);
|
|
default:
|
|
throw new Error('Invalid wallet type');
|
|
}
|
|
};
|
|
|
|
const signEthMessage = async (
|
|
message: string,
|
|
id: number,
|
|
): Promise<string | undefined> => {
|
|
try {
|
|
const keyCred = await getInternetCredentials(`eth:keyServer:${id}`);
|
|
|
|
if (!keyCred) {
|
|
throw new Error('Failed to retrieve internet credentials');
|
|
}
|
|
|
|
const wallet = new Wallet(keyCred.password);
|
|
const signature = await wallet.signMessage(message);
|
|
|
|
return signature;
|
|
} catch (error) {
|
|
console.error('Error signing Ethereum message:', error);
|
|
return undefined;
|
|
}
|
|
};
|
|
|
|
const signCosmosMessage = async (
|
|
message: string,
|
|
id: number,
|
|
): 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, id);
|
|
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,
|
|
id: number,
|
|
): Promise<{ cosmosWallet: Secp256k1HdWallet; data: AccountData }> => {
|
|
const cosmosWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, {
|
|
hdPaths: [stringToPath(`m/44'/118'/0'/0/${id}`)],
|
|
});
|
|
|
|
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');
|
|
} catch (error) {
|
|
console.error('Error resetting wallet:', error);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
export { createWallet, addAccount, signMessage, resetWallet };
|