From e22859a93f986f99d58215b6b07cbe37901149b1 Mon Sep 17 00:00:00 2001 From: Harpal Jadeja Date: Tue, 23 May 2023 15:21:17 +0530 Subject: [PATCH 1/2] refactor: `network.ts` --- packages/snap/src/utils/network.ts | 54 ++++++++++++++++-------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/packages/snap/src/utils/network.ts b/packages/snap/src/utils/network.ts index 6747275..f165cd8 100644 --- a/packages/snap/src/utils/network.ts +++ b/packages/snap/src/utils/network.ts @@ -1,32 +1,36 @@ - - -const networks = [{ - "name": "Celo Alfajores", - "chainIdHex": "0xaef3", - "chainIdDecimal": 44787, - "url": "https://alfajores-forno.celo-testnet.org", - "explorer": "https://explorer.celo.org/alfajores" -}, { - "name": "Celo Mainnet", - "chainIdHex": "0xa4ec", - "chainIdDecimal": 42220, - "url": "https://forno.celo.org", - "explorer": "https://explorer.celo.org/mainnet" +interface Networks { + [key: string]: Network; } -] export interface Network { - name: string; - chainIdHex: string, - chainIdDecimal: number, - url: string, - explorer: string + name: string; + chainIdHex: string; + chainIdDecimal: number; + url: string; + explorer: string; } +const networks: Networks = { + '0xaef3': { + name: 'Celo Alfajores', + chainIdHex: '0xaef3', + chainIdDecimal: 44787, + url: 'https://alfajores-forno.celo-testnet.org', + explorer: 'https://explorer.celo.org/alfajores', + }, + '0xa4ec': { + name: 'Celo Mainnet', + chainIdHex: '0xa4ec', + chainIdDecimal: 42220, + url: 'https://forno.celo.org', + explorer: 'https://explorer.celo.org/mainnet', + }, +}; + export const getNetwork = (chainId: string): Network => { - const network = networks.filter((n: Network) => n.chainIdHex == chainId) - if (network.length == 0) { - throw new Error("Unsupported Network") - } - return network[0]; + const network = networks[chainId]; + if (network == undefined) { + throw new Error('Unsupported Network'); + } + return network; }; From ff1dd3f533192ab8b9e3ad5cb526152d91adac41 Mon Sep 17 00:00:00 2001 From: Harpal Jadeja Date: Wed, 24 May 2023 16:07:40 +0530 Subject: [PATCH 2/2] refactor: refactor code for `supportedCurrencies` --- packages/snap/src/index.ts | 216 +++++++++++++++++++------------------ 1 file changed, 111 insertions(+), 105 deletions(-) diff --git a/packages/snap/src/index.ts b/packages/snap/src/index.ts index 05065b4..5dedf32 100644 --- a/packages/snap/src/index.ts +++ b/packages/snap/src/index.ts @@ -1,19 +1,26 @@ -import { OnRpcRequestHandler } from '@metamask/snaps-types' -import { panel, text, copyable } from '@metamask/snaps-ui' -import { CeloProvider, CeloWallet } from '@celo-tools/celo-ethers-wrapper' -import { ethers } from 'ethers' -import { getBIP44AddressKeyDeriver, BIP44Node } from '@metamask/key-tree' -import { Network, getNetwork } from './utils/network' +import { OnRpcRequestHandler } from '@metamask/snaps-types'; +import { panel, text, copyable } from '@metamask/snaps-ui'; +import { CeloProvider, CeloWallet } from '@celo-tools/celo-ethers-wrapper'; +import { ethers } from 'ethers'; +import { getBIP44AddressKeyDeriver, BIP44Node } from '@metamask/key-tree'; +import { Network, getNetwork } from './utils/network'; type SimpleTransaction = { - to: string - value: string - feeCurrency?: string -} + to: string; + value: string; + feeCurrency?: string; +}; + +const supportedCurrencies: { [feeCurrencyName: string]: string | undefined } = { + cusd: '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1', + ceur: '0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F', + creal: '0xE4D517785D091D3c54818832dB6094bcc2744545', + celo: undefined, +}; export type RequestParams = { - tx: SimpleTransaction // TODO replace with better type -} + tx: SimpleTransaction; // TODO replace with better type +}; /** * Handle incoming JSON-RPC requests, sent through `wallet_invokeSnap`. @@ -23,13 +30,15 @@ export type RequestParams = { * invoked the snap. * @param args.request - A validated JSON-RPC request object. * @returns The result of `snap_dialog`. - * @throws If the request method is not valid for this snap, or if the request params are invalid. + * @throws If the request method is not valid for this snap, or if the request params are invalid. */ -export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request }) => { - const params = request.params as unknown as RequestParams // TODO improve type safety +export const onRpcRequest: OnRpcRequestHandler = async ({ + origin, + request, +}) => { + const params = request.params as unknown as RequestParams; // TODO improve type safety switch (request.method) { - case 'celo_sendTransaction': const result = await snap.request({ method: 'snap_dialog', @@ -38,52 +47,57 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request }) => content: panel([ text('Please approve the following transaction'), text(`to: ${params.tx.to}`), - text(`value: ${params.tx.value} wei`) - ]) - } - }) + text(`value: ${params.tx.value} wei`), + ]), + }, + }); - const network = await getNetworkConfig() + const network = await getNetworkConfig(); if (result === true) { - - params.tx.feeCurrency ??= await getOptimalFeeCurrency(params.tx) - const suggestedFeeCurrency = getFeeCurrencyNameFromAddress(params.tx.feeCurrency) + params.tx.feeCurrency ??= await getOptimalFeeCurrency(params.tx); + const suggestedFeeCurrency = getFeeCurrencyNameFromAddress( + params.tx.feeCurrency, + ); const overrideFeeCurrency = await snap.request({ method: 'snap_dialog', params: { type: 'prompt', content: panel([ - text(`The suggested gas currency for your tx is ${suggestedFeeCurrency}`), - text(`If you would like to use a different gas currency, please enter it below`), - text(`Otherwise, press submit`) + text( + `The suggested gas currency for your tx is ${suggestedFeeCurrency}`, + ), + text( + `If you would like to use a different gas currency, please enter it below`, + ), + text(`Otherwise, press submit`), ]), - placeholder: `'cusd', 'ceur', 'creal', 'celo'` - } - }) - - if ( // TODO find a cleaner way to do this, probably use an enum - overrideFeeCurrency === 'cusd' || - overrideFeeCurrency === 'ceur' || - overrideFeeCurrency === 'creal' || - overrideFeeCurrency === 'celo' + placeholder: `'cusd', 'ceur', 'creal', 'celo'`, + }, + }); + + if ( + // TODO find a cleaner way to do this, probably use an enum + typeof overrideFeeCurrency == 'string' && + Object.keys(supportedCurrencies).includes(overrideFeeCurrency) ) { - params.tx.feeCurrency = getFeeCurrencyAddressFromName(overrideFeeCurrency) + params.tx.feeCurrency = + getFeeCurrencyAddressFromName(overrideFeeCurrency); } try { - const txReceipt = await sendTransaction(params) + const txReceipt = await sendTransaction(params); await snap.request({ method: 'snap_dialog', params: { type: 'alert', content: panel([ text(`Your transaction succeeded!`), - copyable(`${network.explorer}/tx/${txReceipt.transactionHash}`) - ]) - } - }) + copyable(`${network.explorer}/tx/${txReceipt.transactionHash}`), + ]), + }, + }); } catch (error) { await snap.request({ method: 'snap_dialog', @@ -91,38 +105,37 @@ export const onRpcRequest: OnRpcRequestHandler = async ({ origin, request }) => type: 'alert', content: panel([ text(`Your transaction failed!`), - text(`error: ${JSON.stringify(error)}`) - ]) - } - }) + text(`error: ${JSON.stringify(error)}`), + ]), + }, + }); } } default: - throw new Error('Method not found.') + throw new Error('Method not found.'); } -} +}; async function getNetworkConfig(): Promise { - const chainId = await ethereum.request({ method: 'eth_chainId' }) as string - return getNetwork(chainId) // TODO + const chainId = (await ethereum.request({ method: 'eth_chainId' })) as string; + return getNetwork(chainId); } async function sendTransaction(params: RequestParams) { - const network = await getNetworkConfig() - const provider = new CeloProvider(network.url) - const bip44Node = await getBIP44Node() - const privateKey = await getPrivateKey(bip44Node) - const wallet = new CeloWallet(privateKey).connect(provider) - + const network = await getNetworkConfig(); + const provider = new CeloProvider(network.url); + const privateKey = await getPrivateKey(); + const wallet = new CeloWallet(privateKey).connect(provider); + const txResponse = await wallet.sendTransaction({ ...params.tx, value: ethers.utils.parseUnits(params.tx.value, 'wei').toHexString(), gasLimit: (await wallet.estimateGas(params.tx)).mul(5), - gasPrice: await wallet.getGasPrice(params.tx.feeCurrency) - }) + gasPrice: await wallet.getGasPrice(params.tx.feeCurrency), + }); - return txResponse.wait() + return txResponse.wait(); } async function getBIP44Node(index: number = 0): Promise { @@ -132,71 +145,64 @@ async function getBIP44Node(index: number = 0): Promise { params: { coinType: 52752, // https://github.com/satoshilabs/slips/blob/master/slip-0044.md }, - }) - const deriveAddress = await getBIP44AddressKeyDeriver(bip44Node) - // Derive account w index - return deriveAddress(index) + }); + const deriveAddress = await getBIP44AddressKeyDeriver(bip44Node); + // Derive account w index + return deriveAddress(index); } -async function getPrivateKey(bip44Node?: BIP44Node, index: number = 0): Promise { +async function getPrivateKey( + bip44Node?: BIP44Node, + index: number = 0, +): Promise { if (!bip44Node) { - bip44Node = await getBIP44Node(index) + bip44Node = await getBIP44Node(index); } if (!bip44Node.privateKey) { - throw new Error('Private key is undefined. BIP-44 node is public.') + throw new Error('Private key is undefined. BIP-44 node is public.'); } - return bip44Node.privateKey + return bip44Node.privateKey; } /** - * Finds the optimal gas currency to send a transaction with based on user balances. - * This may differ from the feeCurrency specified in the transaction body. - * - * The returned feeCurrency will be Celo if the user has enough balance - * to pay for the transaction in Celo, as native transactions are cheaper. - * Otherwise, the returned feeCurrency will be whichever one the user would + * Finds the optimal gas currency to send a transaction with based on user balances. + * This may differ from the feeCurrency specified in the transaction body. + * + * The returned feeCurrency will be Celo if the user has enough balance + * to pay for the transaction in Celo, as native transactions are cheaper. + * Otherwise, the returned feeCurrency will be whichever one the user would * have the greatest balance in after sending the transaction. * * @param tx - The transaction to select the optimal gas currency for - * @returns - The address of the optimal feeCurrency, or undefined if the optimal - * feeCurrency is Celo. + * @returns - The address of the optimal feeCurrency, or undefined if the optimal + * feeCurrency is Celo. */ -async function getOptimalFeeCurrency(tx: SimpleTransaction): Promise { +async function getOptimalFeeCurrency( + tx: SimpleTransaction, +): Promise { // TODO - return undefined + return undefined; } // TODO find a better way to do this -function getFeeCurrencyNameFromAddress(feeCurrencyAddress: string | undefined): string { - switch (feeCurrencyAddress) { - case undefined: - return 'celo' - case '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1': - return 'cusd' - case '0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F': - return 'ceur' - case '0xE4D517785D091D3c54818832dB6094bcc2744545': - return 'creal' - default: - throw new Error( - `Fee currency address ${feeCurrencyAddress} not recognized.` - ) +function getFeeCurrencyNameFromAddress( + feeCurrencyAddress: string | undefined, +): string { + if (Object.values(supportedCurrencies).includes(feeCurrencyAddress)) { + for (var name in supportedCurrencies) { + if (supportedCurrencies[name] == feeCurrencyAddress) return name; + } } + throw new Error(`Fee currency address ${feeCurrencyAddress} not recognized.`); } -function getFeeCurrencyAddressFromName(feeCurrencyName: string): string | undefined { - switch (feeCurrencyName) { - case 'celo': - return undefined - case 'cusd': - return '0x874069Fa1Eb16D44d622F2e0Ca25eeA172369bC1' // TODO make this dynamic by network, currently just for alfajores - case 'ceur': - return '0x10c892A6EC43a53E45D0B916B4b7D383B1b78C0F' - case 'creal': - return '0xE4D517785D091D3c54818832dB6094bcc2744545' - default: - throw new Error( - `Fee currency string ${feeCurrencyName} not recognized. Must be either 'celo', 'cusd', 'ceur' or 'creal'.` - ) +function getFeeCurrencyAddressFromName( + feeCurrencyName: string, +): string | undefined { + if (Object.keys(supportedCurrencies).includes(feeCurrencyName)) { + return supportedCurrencies[feeCurrencyName]; } + throw new Error( + `Fee currency string ${feeCurrencyName} not recognized. Must be either 'celo', 'cusd', 'ceur' or 'creal'.`, + ); }