From 3c2684f851c433e502f745e43eb764b8b655b3ac Mon Sep 17 00:00:00 2001 From: Ben Kremer Date: Mon, 21 Feb 2022 11:31:07 +0100 Subject: [PATCH] refactor(dapp): support signing from different addresses on same network --- dapps/react-dapp-v2/src/App.tsx | 28 ++-- .../src/components/Blockchain.tsx | 4 +- .../src/contexts/JsonRpcContext.tsx | 148 ++++++++---------- dapps/react-dapp-v2/src/helpers/types.ts | 2 +- 4 files changed, 81 insertions(+), 101 deletions(-) diff --git a/dapps/react-dapp-v2/src/App.tsx b/dapps/react-dapp-v2/src/App.tsx index 84082f6..0b094a7 100644 --- a/dapps/react-dapp-v2/src/App.tsx +++ b/dapps/react-dapp-v2/src/App.tsx @@ -77,25 +77,25 @@ export default function App() { }; const getEthereumActions = (): AccountAction[] => { - const onSendTransaction = async (chainId: string) => { + const onSendTransaction = async (chainId: string, address: string) => { openRequestModal(); - await ethereumRpc.testSendTransaction(chainId); + await ethereumRpc.testSendTransaction(chainId, address); }; - const onSignTransaction = async (chainId: string) => { + const onSignTransaction = async (chainId: string, address: string) => { openRequestModal(); - await ethereumRpc.testSignTransaction(chainId); + await ethereumRpc.testSignTransaction(chainId, address); }; - const onSignPersonalMessage = async (chainId: string) => { + const onSignPersonalMessage = async (chainId: string, address: string) => { openRequestModal(); - await ethereumRpc.testSignPersonalMessage(chainId); + await ethereumRpc.testSignPersonalMessage(chainId, address); }; - const onEthSign = async (chainId: string) => { + const onEthSign = async (chainId: string, address: string) => { openRequestModal(); - await ethereumRpc.testEthSign(chainId); + await ethereumRpc.testEthSign(chainId, address); }; - const onSignTypedData = async (chainId: string) => { + const onSignTypedData = async (chainId: string, address: string) => { openRequestModal(); - await ethereumRpc.testSignTypedData(chainId); + await ethereumRpc.testSignTypedData(chainId, address); }; return [ @@ -108,13 +108,13 @@ export default function App() { }; const getCosmosActions = (): AccountAction[] => { - const onSignDirect = async (chainId: string) => { + const onSignDirect = async (chainId: string, address: string) => { openRequestModal(); - await cosmosRpc.testSignDirect(chainId); + await cosmosRpc.testSignDirect(chainId, address); }; - const onSignAmino = async (chainId: string) => { + const onSignAmino = async (chainId: string, address: string) => { openRequestModal(); - await cosmosRpc.testSignAmino(chainId); + await cosmosRpc.testSignAmino(chainId, address); }; return [ { method: "cosmos_signDirect", callback: onSignDirect }, diff --git a/dapps/react-dapp-v2/src/components/Blockchain.tsx b/dapps/react-dapp-v2/src/components/Blockchain.tsx index eeea85b..8ed870f 100644 --- a/dapps/react-dapp-v2/src/components/Blockchain.tsx +++ b/dapps/react-dapp-v2/src/components/Blockchain.tsx @@ -155,7 +155,7 @@ const Blockchain: FC> = ( ) : null} - {!!actions && actions.length ? ( + {address && !!actions && actions.length ? (
Methods
{actions.map(action => ( @@ -163,7 +163,7 @@ const Blockchain: FC> = ( key={action.method} left rgb={chain.meta.rgb} - onClick={() => action.callback(chainId)} + onClick={() => action.callback(chainId, address)} > {action.method} diff --git a/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx b/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx index df5dac0..141a429 100644 --- a/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx +++ b/dapps/react-dapp-v2/src/contexts/JsonRpcContext.tsx @@ -28,18 +28,20 @@ interface IRpcResult { valid: boolean; } +type TRpcRequestCallback = (chainId: string, address: string) => Promise; + interface IContext { ping: () => Promise; ethereumRpc: { - testSendTransaction: (chainId: string) => Promise; - testSignTransaction: (chainId: string) => Promise; - testEthSign: (chainId: string) => Promise; - testSignPersonalMessage: (chainId: string) => Promise; - testSignTypedData: (chainId: string) => Promise; + testSendTransaction: TRpcRequestCallback; + testSignTransaction: TRpcRequestCallback; + testEthSign: TRpcRequestCallback; + testSignPersonalMessage: TRpcRequestCallback; + testSignTypedData: TRpcRequestCallback; }; cosmosRpc: { - testSignDirect: (chainId: string) => Promise; - testSignAmino: (chainId: string) => Promise; + testSignDirect: TRpcRequestCallback; + testSignAmino: TRpcRequestCallback; }; chainData: ChainNamespaces; rpcResult?: IRpcResult | null; @@ -84,18 +86,9 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea setChainData(chainData); }; - const getAddressByChainId = (chainId: string) => { - const account = accounts.find(account => account.startsWith(chainId)); - if (account === undefined) throw new Error(`Account for chainId ${chainId} not found.`); - const address = account.split(":").pop(); - if (address === undefined) throw new Error(`Address for account ${account} is invalid`); - - return address; - }; - const _createJsonRpcRequestHandler = - (rpcRequest: (...requestArgs: [any]) => Promise) => - async (chainId: string) => { + (rpcRequest: (chainId: string, address: string) => Promise) => + async (chainId: string, address: string) => { if (typeof client === "undefined") { throw new Error("WalletConnect is not initialized"); } @@ -105,7 +98,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea try { setPending(true); - const result = await rpcRequest(chainId); + const result = await rpcRequest(chainId, address); setResult(result); } catch (err) { console.error(err); @@ -151,12 +144,10 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea // -------- ETHEREUM/EIP155 RPC METHODS -------- const ethereumRpc = { - testSendTransaction: _createJsonRpcRequestHandler(async (chainId: string) => { - // get ethereum address - const account = accounts.find(account => account.startsWith(chainId)); - if (account === undefined) throw new Error("Account is not found"); - const address = account.split(":").pop(); - if (address === undefined) throw new Error("Address is invalid"); + testSendTransaction: _createJsonRpcRequestHandler(async (chainId: string, address: string) => { + const caipAccountAddress = `${chainId}:${address}`; + const account = accounts.find(account => account === caipAccountAddress); + if (account === undefined) throw new Error(`Account for ${caipAccountAddress} not found`); const tx = await formatTestTransaction(account); @@ -185,8 +176,6 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea console.error(error); } - console.log(result); - // format displayed result return { method: "eth_sendTransaction", @@ -195,12 +184,10 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea result, }; }), - testSignTransaction: _createJsonRpcRequestHandler(async (chainId: string) => { - // get ethereum address - const account = accounts.find(account => account.startsWith(chainId)); - if (account === undefined) throw new Error("Account is not found"); - const address = account.split(":").pop(); - if (address === undefined) throw new Error("Address is invalid"); + testSignTransaction: _createJsonRpcRequestHandler(async (chainId: string, address: string) => { + const caipAccountAddress = `${chainId}:${address}`; + const account = accounts.find(account => account === caipAccountAddress); + if (account === undefined) throw new Error(`Account for ${caipAccountAddress} not found`); const tx = await formatTestTransaction(account); @@ -220,54 +207,52 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea result, }; }), - testSignPersonalMessage: _createJsonRpcRequestHandler(async (chainId: string) => { - // test message - const message = `My email is john@doe.com - ${Date.now()}`; + testSignPersonalMessage: _createJsonRpcRequestHandler( + async (chainId: string, address: string) => { + // test message + const message = `My email is john@doe.com - ${Date.now()}`; - // encode message (hex) - const hexMsg = encoding.utf8ToHex(message, true); + // encode message (hex) + const hexMsg = encoding.utf8ToHex(message, true); - const address = getAddressByChainId(chainId); + // personal_sign params + const params = [hexMsg, address]; - // personal_sign params - const params = [hexMsg, address]; + // send message + const result: string = await client!.request({ + topic: session!.topic, + chainId, + request: { + method: "personal_sign", + params, + }, + }); - // send message - const result: string = await client!.request({ - topic: session!.topic, - chainId, - request: { + // split chainId + const [namespace, reference] = chainId.split(":"); + + const targetChainData = chainData[namespace][reference]; + + if (typeof targetChainData === "undefined") { + throw new Error(`Missing chain data for chainId: ${chainId}`); + } + + const rpcUrl = targetChainData.rpc[0]; + + // verify signature + const hash = hashPersonalMessage(message); + const valid = await verifySignature(address, result, hash, rpcUrl); + + // format displayed result + return { method: "personal_sign", - params, - }, - }); - - // split chainId - const [namespace, reference] = chainId.split(":"); - - const targetChainData = chainData[namespace][reference]; - - if (typeof targetChainData === "undefined") { - throw new Error(`Missing chain data for chainId: ${chainId}`); - } - - const rpcUrl = targetChainData.rpc[0]; - - // verify signature - const hash = hashPersonalMessage(message); - const valid = await verifySignature(address, result, hash, rpcUrl); - - // format displayed result - return { - method: "personal_sign", - address, - valid, - result, - }; - }), - testEthSign: _createJsonRpcRequestHandler(async (chainId: string) => { - const address = getAddressByChainId(chainId); - + address, + valid, + result, + }; + }, + ), + testEthSign: _createJsonRpcRequestHandler(async (chainId: string, address: string) => { // test message const message = `My email is john@doe.com - ${Date.now()}`; // encode message (hex) @@ -308,7 +293,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea result, }; }), - testSignTypedData: _createJsonRpcRequestHandler(async (chainId: string) => { + testSignTypedData: _createJsonRpcRequestHandler(async (chainId: string, address: string) => { const typedData = { types: { Person: [ @@ -341,7 +326,6 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea }, }; - const address = getAddressByChainId(chainId); const message = JSON.stringify(typedData); // eth_signTypedData params @@ -372,7 +356,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea // -------- COSMOS RPC METHODS -------- const cosmosRpc = { - testSignDirect: _createJsonRpcRequestHandler(async (chainId: string) => { + testSignDirect: _createJsonRpcRequestHandler(async (chainId: string, address: string) => { // test direct sign doc inputs const inputs = { fee: [{ amount: "2000", denom: "ucosm" }], @@ -400,8 +384,6 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea reference, ); - const address = getAddressByChainId(chainId); - // cosmos_signDirect params const params = { signerAddress: address, @@ -435,7 +417,7 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea result: result.signature.signature, }; }), - testSignAmino: _createJsonRpcRequestHandler(async (chainId: string) => { + testSignAmino: _createJsonRpcRequestHandler(async (chainId: string, address: string) => { // split chainId const [namespace, reference] = chainId.split(":"); @@ -449,8 +431,6 @@ export function JsonRpcContextProvider({ children }: { children: ReactNode | Rea sequence: "54", }; - const address = getAddressByChainId(chainId); - // cosmos_signAmino params const params = { signerAddress: address, signDoc }; diff --git a/dapps/react-dapp-v2/src/helpers/types.ts b/dapps/react-dapp-v2/src/helpers/types.ts index 01459a9..1f2bcf9 100644 --- a/dapps/react-dapp-v2/src/helpers/types.ts +++ b/dapps/react-dapp-v2/src/helpers/types.ts @@ -151,7 +151,7 @@ export interface ChainNamespaces { export interface AccountAction { method: string; - callback: (chainId: string) => Promise; + callback: (chainId: string, address: string) => Promise; } export interface AccountBalances {