diff --git a/src/components/Accounts.tsx b/src/components/Accounts.tsx index f1de0e6..26c61c5 100644 --- a/src/components/Accounts.tsx +++ b/src/components/Accounts.tsx @@ -16,7 +16,7 @@ import { useNetworks } from "../context/NetworksContext"; import ConfirmDialog from "./ConfirmDialog"; import { getNamespaces } from "../utils/wallet-connect/helpers"; import ShowPKDialog from "./ShowPKDialog"; -import { setInternetCredentials } from "../utils/key-store"; +import { updateNetworks } from "../utils/eyre-client"; import { Accordion, AccordionSummary, @@ -110,11 +110,7 @@ const Accounts = () => { (networkData) => selectedNetwork!.networkId !== networkData.networkId, ); - await setInternetCredentials( - "networks", - "_", - JSON.stringify(updatedNetworks), - ); + await updateNetworks(updatedNetworks); setSelectedNetwork(updatedNetworks[0]); setCurrentIndex(0); diff --git a/src/context/NetworksContext.tsx b/src/context/NetworksContext.tsx index ec311e6..7808651 100644 --- a/src/context/NetworksContext.tsx +++ b/src/context/NetworksContext.tsx @@ -1,9 +1,8 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; import { NetworksDataState } from '../types'; -import { retrieveNetworksData } from '../utils/accounts'; +import { getNetworks, updateNetworks } from '../utils/eyre-client'; import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants'; -import { setInternetCredentials } from '../utils/key-store'; const NetworksContext = createContext<{ networksData: NetworksDataState[]; @@ -42,15 +41,10 @@ const NetworksProvider = ({ children }: { children: React.ReactNode }) => { useEffect(() => { const fetchData = async () => { - let retrievedNetworks = await retrieveNetworksData(); + let retrievedNetworks = await getNetworks(); if (retrievedNetworks.length === 0) { - setInternetCredentials( - 'networks', - '_', - JSON.stringify(DEFAULT_NETWORKS_DATA), - ); - + await updateNetworks(DEFAULT_NETWORKS_DATA); retrievedNetworks = DEFAULT_NETWORKS_DATA; } diff --git a/src/screens/EditNetwork.tsx b/src/screens/EditNetwork.tsx index e82482a..538d109 100644 --- a/src/screens/EditNetwork.tsx +++ b/src/screens/EditNetwork.tsx @@ -10,9 +10,8 @@ import { } from "@react-navigation/native-stack"; import { useNavigation } from "@react-navigation/native"; -import { setInternetCredentials } from "../utils/key-store"; import { StackParamsList } from "../types"; -import { retrieveNetworksData } from "../utils/accounts"; +import { getNetworks, updateNetworks } from "../utils/eyre-client"; import { useNetworks } from "../context/NetworksContext"; import { COSMOS, @@ -74,7 +73,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => { const submit = useCallback( async (data: z.infer) => { - const retrievedNetworksData = await retrieveNetworksData(); + const retrievedNetworksData = await getNetworks(); const { type, ...dataWithoutType } = data; const newNetworkData = { ...networkData, ...dataWithoutType }; const index = retrievedNetworksData.findIndex( @@ -83,11 +82,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => { retrievedNetworksData.splice(index, 1, newNetworkData); - await setInternetCredentials( - "networks", - "_", - JSON.stringify(retrievedNetworksData), - ); + await updateNetworks(retrievedNetworksData); setNetworksData(retrievedNetworksData); diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index 380922f..369f5d8 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -7,106 +7,54 @@ import '@ethersproject/shims'; import { utils } from 'ethers'; import { HDNode } from 'ethers/lib/utils'; -import { - setInternetCredentials, - resetInternetCredentials, - getInternetCredentials, -} from './key-store'; import { Secp256k1HdWallet } from '@cosmjs/amino'; import { AccountData } from '@cosmjs/proto-signing'; import { stringToPath } from '@cosmjs/crypto'; import { Account, NetworksDataState, NetworksFormData } from '../types'; -import { - getHDPath, - getPathKey, - resetKeyServers, - updateAccountIndices, -} from './misc'; import { COSMOS, EIP155 } from './constants'; +import * as eyre from './eyre-client'; + +function splitNsChain(nsChain: string): [string, string] { + const i = nsChain.indexOf(':'); + return [nsChain.slice(0, i), nsChain.slice(i + 1)]; +} const createWallet = async ( networksData: NetworksDataState[], recoveryPhrase?: string, ): Promise => { const mnemonic = recoveryPhrase ? recoveryPhrase : utils.entropyToMnemonic(utils.randomBytes(16)); - - const hdNode = HDNode.fromMnemonic(mnemonic); - await setInternetCredentials('mnemonicServer', 'mnemonic', mnemonic); - - await createWalletFromMnemonic(networksData, hdNode, mnemonic); - + await eyre.createWallet(networksData, mnemonic); return mnemonic; }; const createWalletFromMnemonic = async ( networksData: NetworksDataState[], - hdNode: HDNode, + _hdNode: HDNode, mnemonic: string ): Promise => { - for (const network of networksData) { - const hdPath = `m/44'/${network.coinType}'/0'/0/0`; - const node = hdNode.derivePath(hdPath); - let address; - - switch (network.namespace) { - case EIP155: - address = node.address; - break; - - case COSMOS: - address = ( - await getCosmosAccountByHDPath(mnemonic, hdPath, network.addressPrefix) - ).data.address; - break; - - default: - throw new Error('Unsupported namespace'); - } - - const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`; - - await Promise.all([ - setInternetCredentials( - `accounts/${network.namespace}:${network.chainId}/0`, - '_', - accountInfo, - ), - setInternetCredentials( - `addAccountCounter/${network.namespace}:${network.chainId}`, - '_', - '1', - ), - setInternetCredentials( - `accountIndices/${network.namespace}:${network.chainId}`, - '_', - '0', - ), - ]); - } + // HD derivation delegated to agent — hdNode param unused + await eyre.createWallet(networksData, mnemonic); }; const addAccount = async ( chainId: string, ): Promise => { try { - let selectedNetworkAccount - const networksData = await retrieveNetworksData(); + const networksData = await eyre.getNetworks(); - // Add account to all networks and return account for selected network + // Add account to all networks (agent handles derivation + counter) for (const network of networksData) { - const namespaceChainId = `${network.namespace}:${network.chainId}`; - const id = await getNextAccountId(namespaceChainId); - const hdPath = getHDPath(namespaceChainId, `0'/0/${id}`); - const account = await addAccountFromHDPath(hdPath, network); - await updateAccountCounter(namespaceChainId, id); - - if (network.chainId === chainId) { - selectedNetworkAccount = account; - } + await eyre.addAccount(network.namespace, network.chainId); } - return selectedNetworkAccount; + // Return the new account for the selected network + const selectedNetwork = networksData.find(n => n.chainId === chainId); + if (!selectedNetwork) return; + + const accounts = await eyre.getAccounts(selectedNetwork.namespace, chainId); + return accounts[accounts.length - 1]; } catch (error) { console.error('Error creating account:', error); } @@ -117,13 +65,8 @@ const addAccountsForNetwork = async ( numberOfAccounts: number, ): Promise => { try { - const namespaceChainId = `${network.namespace}:${network.chainId}`; - for (let i = 0; i < numberOfAccounts; i++) { - const id = await getNextAccountId(namespaceChainId); - const hdPath = getHDPath(namespaceChainId, `0'/0/${id}`); - await addAccountFromHDPath(hdPath, network); - await updateAccountCounter(namespaceChainId, id); + await eyre.addAccount(network.namespace, network.chainId); } } catch (error) { console.error('Error creating account:', error); @@ -135,26 +78,11 @@ const addAccountFromHDPath = async ( networkData: NetworksDataState, ): Promise => { try { - const account = await accountInfoFromHDPath(hdPath, networkData); - if (!account) { - throw new Error('Error while creating account'); - } + // Agent derives from stored mnemonic using the given HD path + await eyre.addAccountFromPath(networkData.namespace, networkData.chainId, hdPath); - const { privKey, pubKey, address } = account; - - const namespaceChainId = `${networkData.namespace}:${networkData.chainId}`; - - const index = (await updateAccountIndices(namespaceChainId)).index; - - await Promise.all([ - setInternetCredentials( - `accounts/${namespaceChainId}/${index}`, - '_', - `${hdPath},${privKey},${pubKey},${address}`, - ), - ]); - - return { index, pubKey, address, hdPath }; + const accounts = await eyre.getAccounts(networkData.namespace, networkData.chainId); + return accounts[accounts.length - 1]; } catch (error) { console.error(error); } @@ -163,160 +91,37 @@ const addAccountFromHDPath = async ( const addNewNetwork = async ( newNetworkData: NetworksFormData ): Promise => { - const mnemonicServer = await getInternetCredentials("mnemonicServer"); - const mnemonic = mnemonicServer; - - if (!mnemonic) { - throw new Error("Mnemonic not found"); - } - - const hdNode = HDNode.fromMnemonic(mnemonic); - - const hdPath = `m/44'/${newNetworkData.coinType}'/0'/0/0`; - const node = hdNode.derivePath(hdPath); - let address; - - switch (newNetworkData.namespace) { - case EIP155: - address = node.address; - break; - - case COSMOS: - address = ( - await getCosmosAccountByHDPath( - mnemonic, - hdPath, - newNetworkData.addressPrefix, - ) - ).data.address; - break; - - default: - throw new Error("Unsupported namespace"); - } - - const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`; - - await Promise.all([ - setInternetCredentials( - `accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`, - "_", - accountInfo, - ), - setInternetCredentials( - `addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`, - "_", - "1", - ), - setInternetCredentials( - `accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`, - "_", - "0", - ), - ]); - - const retrievedNetworksData = await storeNetworkData(newNetworkData); - - // Get number of accounts in first network - const nextAccountId = await getNextAccountId( - `${retrievedNetworksData[0].namespace}:${retrievedNetworksData[0].chainId}`, - ); - - const selectedNetwork = retrievedNetworksData.find( - (network) => network.chainId === newNetworkData.chainId, - ); - - await addAccountsForNetwork(selectedNetwork!, nextAccountId - 1); - - return retrievedNetworksData; -} + await eyre.addNetwork(newNetworkData); + return eyre.getNetworks(); +}; const storeNetworkData = async ( networkData: NetworksFormData, ): Promise => { - const networks = await getInternetCredentials('networks'); - let retrievedNetworks = []; - if (networks) { - retrievedNetworks = JSON.parse(networks!); - } - let networkId = 0; - if (retrievedNetworks.length > 0) { - networkId = retrievedNetworks[retrievedNetworks.length - 1].networkId + 1; - } - - const updatedNetworks: NetworksDataState[] = [ - ...retrievedNetworks, - { - ...networkData, - networkId: String(networkId), - }, - ]; - - await setInternetCredentials( - 'networks', - '_', - JSON.stringify(updatedNetworks), - ); - - return updatedNetworks; + await eyre.addNetwork(networkData); + return eyre.getNetworks(); }; const retrieveNetworksData = async (): Promise => { - const networks = await getInternetCredentials('networks'); - - if (!networks) { - return []; - } - - const parsedNetworks: NetworksDataState[] = JSON.parse(networks); - - return parsedNetworks; + return eyre.getNetworks(); }; export const retrieveAccountsForNetwork = async ( namespaceChainId: string, - accountsIndices: string, + _accountsIndices?: string, ): Promise => { - const accountsIndexArray = accountsIndices.split(','); - - const loadedAccounts = await Promise.all( - accountsIndexArray.map(async i => { - const { address, path, pubKey } = await getPathKey( - namespaceChainId, - Number(i), - ); - - const account: Account = { - index: Number(i), - pubKey, - address, - hdPath: path, - }; - - return account; - }), - ); - - return loadedAccounts; + const [ns, chain] = splitNsChain(namespaceChainId); + return eyre.getAccounts(ns, chain); }; const retrieveAccounts = async ( currentNetworkData: NetworksDataState, ): Promise => { - const accountIndicesServer = await getInternetCredentials( - `accountIndices/${currentNetworkData.namespace}:${currentNetworkData.chainId}`, + const accounts = await eyre.getAccounts( + currentNetworkData.namespace, + currentNetworkData.chainId, ); - const accountIndices = accountIndicesServer; - if (!accountIndices) { - return; - } - - const loadedAccounts = await retrieveAccountsForNetwork( - `${currentNetworkData.namespace}:${currentNetworkData.chainId}`, - accountIndices, - ) - - return loadedAccounts; + return accounts.length > 0 ? accounts : undefined; }; const retrieveSingleAccount = async ( @@ -324,37 +129,13 @@ const retrieveSingleAccount = async ( chainId: string, address: string, ) => { - let loadedAccounts; - - const accountIndicesServer = await getInternetCredentials( - `accountIndices/${namespace}:${chainId}`, - ); - const accountIndices = accountIndicesServer; - - if (!accountIndices) { - throw new Error('Indices for given chain not found'); - } - - loadedAccounts = await retrieveAccountsForNetwork( - `${namespace}:${chainId}`, - accountIndices, - ); - - if (!loadedAccounts) { - throw new Error('Accounts for given chain not found'); - } - - return loadedAccounts.find(account => account.address === address); + const accounts = await eyre.getAccounts(namespace, chainId); + return accounts.find(account => account.address === address); }; const resetWallet = async () => { try { - await Promise.all([ - resetInternetCredentials('mnemonicServer'), - resetKeyServers(EIP155), - resetKeyServers(COSMOS), - setInternetCredentials('networks', '_', JSON.stringify([])), - ]); + await eyre.resetWallet(); } catch (error) { console.error('Error resetting wallet:', error); throw error; @@ -367,12 +148,13 @@ const accountInfoFromHDPath = async ( ): Promise< { privKey: string; pubKey: string; address: string } | undefined > => { - const mnemonicStore = await getInternetCredentials('mnemonicServer'); - if (!mnemonicStore) { + // Phase 1: still derives in browser for API compat. + // Phase 2: this moves to the agent entirely. + const mnemonic = await eyre.getMnemonic(); + if (!mnemonic) { throw new Error('Mnemonic not found!'); } - const mnemonic = mnemonicStore; const hdNode = HDNode.fromMnemonic(mnemonic); const node = hdNode.derivePath(hdPath); @@ -397,36 +179,15 @@ const accountInfoFromHDPath = async ( }; const getNextAccountId = async (namespaceChainId: string): Promise => { - const idStore = await getInternetCredentials( - `addAccountCounter/${namespaceChainId}`, - ); - if (!idStore) { - throw new Error('Account id not found'); - } - - const accountCounter = idStore; - const nextCounter = Number(accountCounter); - return nextCounter; + const [ns, chain] = splitNsChain(namespaceChainId); + return eyre.getNextAccountId(ns, chain); }; const updateAccountCounter = async ( - namespaceChainId: string, - id: number, + _namespaceChainId: string, + _id: number, ): Promise => { - const idStore = await getInternetCredentials( - `addAccountCounter/${namespaceChainId}`, - ); - if (!idStore) { - throw new Error('Account id not found'); - } - - const updatedCounter = String(id + 1); - await resetInternetCredentials(`addAccountCounter/${namespaceChainId}`); - await setInternetCredentials( - `addAccountCounter/${namespaceChainId}`, - '_', - updatedCounter, - ); + // Agent manages account counter atomically via addAccount poke. }; const getCosmosAccountByHDPath = async ( @@ -448,23 +209,13 @@ const getCosmosAccountByHDPath = async ( const checkNetworkForChainID = async ( chainId: string, ): Promise => { - const networks = await getInternetCredentials('networks'); - - if (!networks) { - return false; - } - - const networksData: NetworksFormData[] = JSON.parse(networks); - - return networksData.some((network) => network.chainId === chainId); + const networks = await eyre.getNetworks(); + return networks.some((network) => network.chainId === chainId); } const isWalletCreated = async ( ): Promise => { - const mnemonicServer = await getInternetCredentials("mnemonicServer"); - const mnemonic = mnemonicServer; - - return mnemonic !== null; + return eyre.walletExists(); }; export { diff --git a/src/utils/key-store.ts b/src/utils/key-store.ts index f71fdff..40688c7 100644 --- a/src/utils/key-store.ts +++ b/src/utils/key-store.ts @@ -1,3 +1,8 @@ +/** + * @deprecated Use eyre-client.ts instead. This module will be removed in Phase 2. + * All consumers have been migrated to eyre-client.ts typed API. + */ + const setInternetCredentials = (name:string, username:string, password:string) => { localStorage.setItem(name, password); }; diff --git a/src/utils/misc.ts b/src/utils/misc.ts index d7846de..c19d809 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -9,20 +9,23 @@ import { stringToPath } from '@cosmjs/crypto'; import '@ethersproject/shims'; import { - getInternetCredentials, - resetInternetCredentials, - setInternetCredentials, -} from './key-store'; + getMnemonic as eyreGetMnemonic, + getAccounts, + getAccount, +} from './eyre-client'; import { EIP155 } from './constants'; -import { NetworksDataState } from '../types'; + +function splitNsChain(namespaceChainId: string): [string, string] { + const i = namespaceChainId.indexOf(':'); + return [namespaceChainId.slice(0, i), namespaceChainId.slice(i + 1)]; +} const getMnemonic = async (): Promise => { - const mnemonicStore = await getInternetCredentials('mnemonicServer'); - if (!mnemonicStore) { + const mnemonic = await eyreGetMnemonic(); + if (!mnemonic) { throw new Error('Mnemonic not found!'); } - const mnemonic = mnemonicStore; return mnemonic; }; @@ -53,22 +56,19 @@ const getPathKey = async ( pubKey: string; address: string; }> => { - const pathKeyStore = await getInternetCredentials( - `accounts/${namespaceChainId}/${accountId}`, - ); + const [ns, chain] = splitNsChain(namespaceChainId); + const account = await getAccount(ns, chain, accountId); - if (!pathKeyStore) { - throw new Error('Error while fetching counter'); + if (!account) { + throw new Error('Error while fetching account'); } - const pathKeyVal = pathKeyStore; - const pathkey = pathKeyVal.split(','); - const path = pathkey[0]; - const privKey = pathkey[1]; - const pubKey = pathkey[2]; - const address = pathkey[3]; - - return { path, privKey, pubKey, address }; + return { + path: account.hdPath, + privKey: account.privKey, + pubKey: account.pubKey, + address: account.address, + }; }; const getAccountIndices = async ( @@ -78,75 +78,35 @@ const getAccountIndices = async ( indices: number[]; index: number; }> => { - const counterStore = await getInternetCredentials( - `accountIndices/${namespaceChainId}`, - ); + const [ns, chain] = splitNsChain(namespaceChainId); + const accounts = await getAccounts(ns, chain); - if (!counterStore) { - throw new Error('Error while fetching counter'); + if (accounts.length === 0) { + throw new Error('Error while fetching accounts'); } - let accountIndices = counterStore; - const indices = accountIndices.split(',').map(Number); - const index = indices[indices.length - 1] + 1; + const indices = accounts.map(a => a.index); + const maxIndex = Math.max(...indices); - return { accountIndices, indices, index }; + return { + accountIndices: indices.join(','), + indices, + index: maxIndex + 1, + }; }; const updateAccountIndices = async ( namespaceChainId: string, ): Promise<{ accountIndices: string; index: number }> => { - const accountIndicesData = await getAccountIndices(namespaceChainId); - const accountIndices = accountIndicesData.accountIndices; - const index = accountIndicesData.index; - const updatedAccountIndices = `${accountIndices},${index.toString()}`; - - await resetInternetCredentials(`accountIndices/${namespaceChainId}`); - await setInternetCredentials( - `accountIndices/${namespaceChainId}`, - '_', - updatedAccountIndices, - ); - - return { accountIndices: updatedAccountIndices, index }; + // Agent manages account indices atomically via addAccount poke. + // This reads current state for callers that still expect the old return shape. + const data = await getAccountIndices(namespaceChainId); + return { accountIndices: data.accountIndices, index: data.index }; }; -const resetKeyServers = async (namespace: string) => { - const networksServer = await getInternetCredentials('networks'); - if (!networksServer) { - throw new Error('Networks not found.'); - } - - const networksData: NetworksDataState[] = JSON.parse(networksServer); - const filteredNetworks = networksData.filter( - network => network.namespace === namespace, - ); - - if (filteredNetworks.length === 0) { - throw new Error(`No networks found for namespace ${namespace}.`); - } - - filteredNetworks.forEach(async network => { - const { chainId } = network; - const namespaceChainId = `${namespace}:${chainId}`; - - const idStore = await getInternetCredentials( - `accountIndices/${namespaceChainId}`, - ); - if (!idStore) { - throw new Error(`Account indices not found for ${namespaceChainId}.`); - } - - const accountIds = idStore; - const ids = accountIds.split(',').map(Number); - const latestId = Math.max(...ids); - - for (let i = 0; i <= latestId; i++) { - await resetInternetCredentials(`accounts/${namespaceChainId}/${i}`); - } - await resetInternetCredentials(`addAccountCounter/${namespaceChainId}`); - await resetInternetCredentials(`accountIndices/${namespaceChainId}`); - }); +const resetKeyServers = async (_namespace: string) => { + // Agent manages account lifecycle atomically via resetWallet poke. + // Individual namespace cleanup is a no-op — resetWallet handles it all. }; const sendMessage = (