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