diff --git a/src/screens/SendTxEmbed.tsx b/src/screens/SendTxEmbed.tsx index b17010d..71cae25 100644 --- a/src/screens/SendTxEmbed.tsx +++ b/src/screens/SendTxEmbed.tsx @@ -11,7 +11,7 @@ import { DirectSecp256k1Wallet, Algo, TxBodyEncodeObject, decodeOptionalPubkey } import { SigningStargateClient } from '@cosmjs/stargate'; import { toHex } from '@cosmjs/encoding'; -import { retrieveAccounts, retrieveSingleAccount } from '../utils/accounts'; // Use retrieveAccounts +import { getCosmosAccount, retrieveAccounts, retrieveSingleAccount } from '../utils/accounts'; import AccountDetails from '../components/AccountDetails'; import styles from '../styles/stylesheet'; import DataBox from '../components/DataBox'; @@ -25,14 +25,16 @@ const SIGN_ONBOARD_TX_RESPONSE = "SIGN_ONBOARD_TX_RESPONSE"; // Type Definitions +interface GetAccountsRequestData { + chainId: string, +} + interface SignOnboardTxRequestData { address: string; signDoc: SignDoc; txBody: TxBodyEncodeObject; } -interface GetAccountsRequestData {} // Currently no specific data needed - type IncomingMessageData = SignOnboardTxRequestData | GetAccountsRequestData; interface IncomingMessageEventData { @@ -47,19 +49,18 @@ type TransactionDetails = { origin: string; signerAddress: string; chainId: string; - account: Account; // Wallet's internal Account type + account: Account; requestedNetwork: NetworksDataState; balance: string; - signDoc: SignDoc; // Deserialized SignDoc + signDoc: SignDoc; txBody: TxBodyEncodeObject; - // AuthInfo: SignerInfo[]; }; interface GetAccountsResponse { accounts: Array<{ algo: Algo; address: string; - pubkey: string; // hex encoded pubkey + pubkey: string; }>; } @@ -74,29 +75,34 @@ export const SendTxEmbed = () => { // Message Handlers const handleGetAccountsRequest = useCallback(async (event: MessageEvent) => { - const { id } = event.data; + const { id, data } = event.data; const source = event.source as Window; const origin = event.origin; + const requestData = data as GetAccountsRequestData; console.log("Received GET_ACCOUNTS_REQUEST", id); try { - const zenithNetworkData = networksData.find(networkData => networkData.chainId === "zenith-testnet") + const requestedNetworkData = networksData.find(networkData => networkData.chainId === requestData.chainId) - if(!zenithNetworkData) { + if(!requestedNetworkData) { throw new Error("Zenith network data not found") } // Ensure retrieveAccounts exists and returns Account[] - const allAccounts = await retrieveAccounts(zenithNetworkData); // Use retrieveAccounts + const allAccounts = await retrieveAccounts(requestedNetworkData); // Use retrieveAccounts if (!allAccounts || allAccounts.length === 0) { throw new Error("Accounts not found for zenithNetwork") } - const responseAccounts = allAccounts.map((acc) => ({ - algo: 'secp256k1' as Algo, // Assuming secp256k1 - address: acc.address, - pubkey: acc.pubKey.startsWith('0x') ? acc.pubKey : `0x${acc.pubKey}`, // Ensure hex format - })); + const responseAccounts = await Promise.all( + allAccounts.map(async (acc) => { + const cosmosAccount = await getCosmosAccount(acc.hdPath, requestedNetworkData.addressPrefix!); + return { + ...cosmosAccount, + pubkey: toHex(cosmosAccount.pubkey), + }; + }) + ); const response: GetAccountsResponse = { accounts: responseAccounts }; sendMessage(source, GET_ACCOUNTS_RESPONSE, {id, data: response}, origin); @@ -110,7 +116,7 @@ export const SendTxEmbed = () => { console.error("Cannot send error message: source is not a Window"); } } - }, [networksData]); // Add dependencies like retrieveAccounts if needed + }, [networksData]); const handleSignOnboardTxRequest = useCallback(async (event: MessageEvent) => { const { id, data } = event.data; @@ -119,7 +125,7 @@ export const SendTxEmbed = () => { const requestData = data as SignOnboardTxRequestData; console.log("Received SIGN_ONBOARD_TX_REQUEST", id); - setIsTxApprovalVisible(false); // Hide previous request first + setIsTxApprovalVisible(false); setTransactionDetails(null); setTxError(null); @@ -135,9 +141,9 @@ export const SendTxEmbed = () => { // Balance Check let balanceAmount = 'N/A'; try { - // Use a temporary read-only client for balance check if possible, or the signing client + // Use a temporary read-only client for balance const tempWallet = await DirectSecp256k1Wallet.fromKey( - new Uint8Array(Buffer.from((await getPathKey(`${network.namespace}:${network.chainId}`, account.index)).privKey.replace(/^0x/, ''), 'hex')), // Wrap in Uint8Array + new Uint8Array(Buffer.from((await getPathKey(`${network.namespace}:${network.chainId}`, account.index)).privKey.replace(/^0x/, ''), 'hex')), network.addressPrefix ); @@ -167,15 +173,11 @@ export const SendTxEmbed = () => { } catch (error: unknown) { console.error("Error handling SIGN_ONBOARD_TX_REQUEST:", error); const errorMsg = error instanceof Error ? error.message : String(error); - // Check if source is a Window before sending message - if (source instanceof Window) { - sendMessage(source, id, { error: `Failed to prepare transaction: ${errorMsg}` }, origin); - } else { - console.error("Cannot send error message: source is not a Window"); - } + + sendMessage(source, id, { error: `Failed to prepare transaction: ${errorMsg}` }, origin); setTxError(errorMsg); } - }, [networksData]); // Dependencies: networksData, gasLimit + }, [networksData]); const handleIncomingMessage = useCallback((event: MessageEvent) => { if (!event.data || typeof event.data !== 'object' || !event.data.type || !event.data.id || !event.source || event.source === window) { @@ -235,13 +237,9 @@ export const SendTxEmbed = () => { } catch (error: unknown) { console.error("Error during signDirect:", error); const errorMsg = error instanceof Error ? error.message : String(error); + setTxError(errorMsg); - // Check if source is a Window before sending message - if (source instanceof Window) { - sendMessage(source, SIGN_ONBOARD_TX_RESPONSE, {id: requestId, error: `Failed to sign transaction: ${errorMsg}` }, origin); - } else { - console.error("Cannot send error message: source is not a Window"); - } + sendMessage(source as Window, SIGN_ONBOARD_TX_RESPONSE, {id: requestId, error: `Failed to sign transaction: ${errorMsg}` }, origin); } finally { setIsTxLoading(false); } @@ -251,13 +249,64 @@ export const SendTxEmbed = () => { if (!transactionDetails) return; const { requestId, source, origin } = transactionDetails; console.log("Rejecting request:", requestId); - // Check if source is a Window before sending message + sendMessage(source as Window, SIGN_ONBOARD_TX_RESPONSE, {id: requestId, error: "User rejected the signature request." }, origin); setIsTxApprovalVisible(false); setTransactionDetails(null); setTxError(null); }; + const decodedAuth = React.useMemo(() => { + if (!transactionDetails) { + return + } + + const info = AuthInfo.decode(transactionDetails.signDoc.authInfoBytes); + return { + ...info, + signerInfos: info.signerInfos.map((signerInfo) => ({ + ...signerInfo, + publicKey: decodeOptionalPubkey(signerInfo.publicKey), + })), + }; + }, [transactionDetails]); + + const formattedTxBody = React.useMemo( + () => { + if (!transactionDetails) { + return + } + + return JSONbig.stringify(transactionDetails.txBody, null, 2) + }, + [transactionDetails] + ); + + const formattedAuthInfo = React.useMemo( + () => JSONbig.stringify(decodedAuth, null, 2), + [decodedAuth] + ); + + const formattedSignDoc = React.useMemo( + () => + { + if (!transactionDetails) { + return + } + + return JSONbig.stringify( + { + ...transactionDetails.signDoc, + bodyBytes: toHex(transactionDetails.signDoc.bodyBytes), + authInfoBytes: toHex(transactionDetails.signDoc.authInfoBytes), + }, + null, + 2 + ) + }, + [transactionDetails] + ); + return ( <> {isTxApprovalVisible && transactionDetails ? ( @@ -282,7 +331,7 @@ export const SendTxEmbed = () => { contentContainerStyle={{ padding: 10 }} > - {JSONbig.stringify(transactionDetails.txBody, null, 2)} + {formattedTxBody} @@ -291,17 +340,7 @@ export const SendTxEmbed = () => { Auth Info - {JSONbig.stringify( - { - ...AuthInfo.decode(transactionDetails.signDoc.authInfoBytes), - signerInfos: AuthInfo.decode(transactionDetails.signDoc.authInfoBytes).signerInfos.map((info) => ({ - ...info, - publicKey: decodeOptionalPubkey(info.publicKey) - })), - }, - null, - 2 - )} + {formattedAuthInfo} @@ -310,15 +349,7 @@ export const SendTxEmbed = () => { Transaction Data To Be Signed - {JSONbig.stringify( - { - ...transactionDetails.signDoc, - bodyBytes: toHex(transactionDetails.signDoc.bodyBytes), - authInfoBytes: toHex(transactionDetails.signDoc.authInfoBytes), - }, - null, - 2 - )} + {formattedSignDoc} diff --git a/src/utils/accounts.ts b/src/utils/accounts.ts index d7921a6..cc891dd 100644 --- a/src/utils/accounts.ts +++ b/src/utils/accounts.ts @@ -290,6 +290,17 @@ const accountInfoFromHDPath = async ( return { privKey, pubKey, address }; }; +const getCosmosAccount = async (hdPath: string, addressPrefix: string) => { + const mnemonicStore = getInternetCredentials('mnemonicServer'); + if (!mnemonicStore) { + throw new Error('Mnemonic not found!'); + } + + const mnemonic = mnemonicStore; + + return (await getCosmosAccounts(mnemonic, hdPath, addressPrefix)).data +} + const getNextAccountId = async (namespaceChainId: string): Promise => { const idStore = await getInternetCredentials( `addAccountCounter/${namespaceChainId}`, @@ -352,4 +363,5 @@ export { getNextAccountId, updateAccountCounter, getCosmosAccounts, + getCosmosAccount };