laconic-wallet/src/utils/accounts.ts
2024-03-28 14:21:49 +05:30

320 lines
8.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 { utils } from 'ethers';
import { HDNode } from 'ethers/lib/utils';
import {
setInternetCredentials,
resetInternetCredentials,
getInternetCredentials,
} from 'react-native-keychain';
import { Secp256k1HdWallet } from '@cosmjs/amino';
import { AccountData } from '@cosmjs/proto-signing';
import { stringToPath } from '@cosmjs/crypto';
import { Account, WalletDetails } from '../types';
import {
getHDPath,
getPathKey,
resetKeyServers,
updateGlobalCounter,
} from './misc';
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},${
ethNode.publicKey
},${ethAddress}`;
const cosmosAccountInfo = `${"0'/0/0"},${cosmosNode.privateKey},${
cosmosNode.publicKey
},${cosmosAddress}`;
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 id = await getNextAccountId(network);
const hdPath = getHDPath(network, `0'/0/${id}`);
const accounts = await addAccountFromHDPath(hdPath);
await updateAccountIndices(network, id);
return accounts;
} 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 counterId = (await updateGlobalCounter(network)).counterId;
await Promise.all([
setInternetCredentials(
`${network}:keyServer:${counterId}`,
`${network}:pathKey:${counterId}`,
`${path},${privKey},${pubKey},${address}`,
),
]);
return { counterId, pubKey, address, hdPath };
} catch (error) {
console.error(error);
}
};
const retrieveAccountsForNetwork = async (
network: string,
count: string,
): Promise<Account[]> => {
const elementsArray = count.split(',');
const loadedAccounts = await Promise.all(
elementsArray.map(async i => {
const pubKey = (await getPathKey(network, Number(i))).pubKey;
const address = (await getPathKey(network, Number(i))).address;
const path = (await getPathKey(network, Number(i))).path;
const hdPath = getHDPath(network, path);
const account: Account = {
counterId: Number(i),
pubKey: pubKey,
address: address,
hdPath: hdPath,
};
return account;
}),
);
return loadedAccounts;
};
const retrieveAccounts = async (): 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;
const ethLoadedAccounts = ethCounter
? await retrieveAccountsForNetwork('eth', ethCounter)
: undefined;
const cosmosLoadedAccounts = cosmosCounter
? await retrieveAccountsForNetwork('cosmos', cosmosCounter)
: undefined;
return { ethLoadedAccounts, cosmosLoadedAccounts };
};
const retrieveSingleAccount = async (network: string, address: string) => {
let loadedAccounts;
switch (network) {
case 'eth':
const ethServer = await getInternetCredentials('eth:globalCounter');
const ethCounter = ethServer && ethServer.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,
);
}
break;
default:
break;
}
if (loadedAccounts) {
return loadedAccounts.find(account => account.address === address);
}
return undefined;
};
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;
}
};
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 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 getCosmosAccounts = async (
mnemonic: string,
path: string,
): Promise<{ cosmosWallet: Secp256k1HdWallet; data: AccountData }> => {
const cosmosWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, {
hdPaths: [stringToPath(`m/44'/118'/${path}`)],
});
const accountsData = await cosmosWallet.getAccounts();
const data = accountsData[0];
return { cosmosWallet, data };
};
export {
createWallet,
addAccount,
addAccountFromHDPath,
retrieveAccounts,
retrieveSingleAccount,
resetWallet,
accountInfoFromHDPath,
getNextAccountId,
updateAccountIndices,
getCosmosAccounts,
};