/* 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 => { 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 => { 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 => { 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[pathkey.length - 1]; switch (network) { case 'eth': return await signEthMessage(message, hdPath); case 'cosmos': return await signCosmosMessage(message, hdPath); default: throw new Error('Invalid wallet type'); } }; const signEthMessage = async ( message: string, hdPath: string, ): Promise => { try { const keyCred = await getInternetCredentials(`eth:keyServer:${hdPath}`); 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, hdPath: string, ): Promise => { 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, };