/* 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 => { 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 => { 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 => { 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 => { 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 => { 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 => { 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, };