on master help sign in
tx: metamask flow uses EIP1559

This change simplifies the Metamask / Brave Wallet flow by refactoring the TX payload and the corresponding Typescript types. Successful mainnet TX using Brave Wallet: https://etherscan.io/tx/0xdc2ce16dec4e6f862419e6aa8ec85f5287f7123ac88397c9778814e941602e8a

commitb6399a0f28505a38e91659418907935c921631f1 authortomholford <tomholford@users.noreply.github.com> dateTue 18 Oct 2022, 20:01 (4 years ago) parent73d2203
2 files changed+29−47
src/lib/metamask.ts+16−16 src/lib/txn.ts+13−31
src/lib/metamask.ts+16−16
@@ -1,3 +1,4 @@
1+import { FeeMarketEIP1559Transaction as EIP1559Transaction, JsonTx } from '@ethereumjs/tx';
12import Web3 from 'web3';
23import { toHex } from 'web3-utils';
34import { FakeSignableTx, FakeSignResult } from './txn';
@@ -12,24 +13,30 @@ export class MetamaskWallet {
1213
1314// Cheat a bit and pretend to sign transaction
1415// Sign it actually when sending
15−export const metamaskSignTransaction = async (txn: FakeSignableTx) => {
16+export const metamaskSignTransaction = async (txn: EIP1559Transaction) => {
1617 return FakeSignResult(txn, metamaskSendTransaction);
1718};
1819
1920const metamaskSendTransaction = async (txn: FakeSignableTx, web3: Web3) => {
20− const { data, gasLimit, gasPrice, nonce, to, from } = txn; // TODO
21− if (!(data && gasLimit && gasPrice && nonce && to && from)) { // TODO
21+ const accounts = await web3.eth.getAccounts();
22+ if(accounts.length === 0) {
23+ throw new Error('No accounts connected');
24+ }
25+ const from = accounts[0];
26+ const { data, to, value, maxPriorityFeePerGas, maxFeePerGas } = txn.toJSON();
27+ if (!(data && to && value && maxPriorityFeePerGas && maxFeePerGas && from)) { // TODO
2228 throw new Error('Unable to send Metamask TX, something is missing');
2329 }
2430
2531 // https://docs.metamask.io/guide/sending-transactions.html#transaction-parameters
32+ // https://github.com/brave/brave-wallet-docs/blob/b19d8884035b00fb21b7ea7628199fea17d5c6ee/docs/ethereum/use-cases/sending-transactions.md
2633 const metamaskFormattedTxn = {
27− data: data.toString(),
28− gasLimit,
29− gasPrice, // TODO
30− nonce: Number(nonce).toString(),
31− to: to.toString(),
32− from: toHex(from),
34+ from,
35+ to,
36+ value,
37+ data,
38+ maxPriorityFeePerGas,
39+ maxFeePerGas
3340 };
3441
3542 let txHash;
@@ -41,19 +48,12 @@ const metamaskSendTransaction = async (txn: FakeSignableTx, web3: Web3) => {
4148 txHash = await window.ethereum.request({
4249 method: 'eth_sendTransaction',
4350 params: [metamaskFormattedTxn],
44− from: txn.from,
4551 });
4652 } else {
4753 //NOTE since this gives us a receipt instead of just the hash of the
4854 // signed tx, it makes us wait for confirmation outside of our own
4955 // waitForTransactionConfirm. because of this the progress bar loses
5056 // its progressiveness for metamask users.
51−
52− // Per the web3 lib's Typescript declarations, sendTransaction
53− // expects a Transaction nonce of type number to be passed in.
54− // However, at runtime, Metamask throws an error indicating that
55− // nonce should be a string.
56− //@ts-ignore
5757 let receipt = await web3.eth.sendTransaction(metamaskFormattedTxn);
5858 txHash = receipt.transactionHash;
5959 }
src/lib/txn.ts+13−31
@@ -1,5 +1,5 @@
11import { Common, Chain, Hardfork } from '@ethereumjs/common';
2−import { JsonTx, Transaction, FeeMarketEIP1559Transaction as EIP1559Transaction, TxOptions, TxData, FeeMarketEIP1559TxData as EIP1559TxData } from '@ethereumjs/tx';
2+import { Transaction, FeeMarketEIP1559Transaction as EIP1559Transaction, TxOptions, TxData, FeeMarketEIP1559TxData as EIP1559TxData } from '@ethereumjs/tx';
33import { ITxData } from '@walletconnect/types';
44import { toHex, toWei } from 'web3-utils';
55import retry from 'async-retry';
@@ -20,21 +20,9 @@ const RETRY_OPTIONS = {
2020 randomize: false,
2121};
2222
23−type SignableTx = (Transaction | EIP1559Transaction) & {
24− nonce: string;
25− chainId: string;
26− gasPrice?: string; // TODO: is this still needed?
27− gasLimit: string; // TODO: is this still needed?
28− from?: string;
29−};
30−
31−// catch-all type: Metamask passes an obj shaped like ITxData, WC passes JsonTx + from
32−export type FakeSignableTx =
33− | ITxData
34− | (JsonTx & { from: string; to: string; gas: string })
35− | SignableTx;
23+export type FakeSignableTx = EIP1559Transaction;
3624
37−type TxSender = (txn: FakeSignableTx, web3: Web3) => Promise<string>; // Metamask requires Web3 (txhash)
25+type TxSender = (txn: FakeSignableTx, web3: Web3) => Promise<string>;
3826
3927export type FakeSignedTx = {
4028 txn: FakeSignableTx;
@@ -57,7 +45,7 @@ interface signTransactionProps {
5745 walletType: symbol;
5846 walletHdPath: string; // TODO: is this still needed?
5947 networkType: symbol;
60− txn: Transaction | EIP1559Transaction;
48+ txn: { data: string, to: string, value: number }
6149 nonce: number | string;
6250 chainId: number | string;
6351 gasPriceData: GasPriceData;
@@ -74,7 +62,6 @@ const _web3 = () => {
7462const estimateGasLimit = async (utx: TransactionConfig) => {
7563 const web3 = _web3();
7664 const estimate = await web3.eth.estimateGas(utx);
77− console.log(estimate);
7865 return web3.utils.toBN(estimate).muln(120).divn(100); // 20% cushion
7966}
8067
@@ -89,7 +76,7 @@ const signTransaction = async ({
8976 walletType,
9077 walletHdPath, // TODO
9178 networkType,
92− txn,
79+ txn, // output of transactionBuilder in useEthereumTransaction
9380 nonce, // number
9481 chainId, // number
9582 gasPriceData, // GasPriceData
@@ -97,7 +84,6 @@ const signTransaction = async ({
9784 txnSigner, // optionally inject a transaction signing function,
9885 txnSender, // and a sending function, for wallets that need these passed in.
9986}: signTransactionProps) => {
100− console.log('txn', txn);
10187 const from = wallet.address;
10288 const estimate = await estimateGasLimit({...txn, from });
10389 const maxFeePerGas = await getMaxFeePerGas();
@@ -112,8 +98,6 @@ const signTransaction = async ({
11298 chainId: toHex(chainId),
11399 type: toHex(EIP1559_TRANSACTION_TYPE),
114100 }
115−
116− const utx: SignableTx = Object.assign(txn, txParams);
117101
118102 // TODO: Ropsten will sunset Q4 2022, need to replace w/ support for Goerli
119103 const chain =
@@ -130,7 +114,7 @@ const signTransaction = async ({
130114 let stx = EIP1559Transaction.fromTxData(txParams, txConfig)
131115
132116 if (walletType === WALLET_TYPES.METAMASK) {
133− return metamaskSignTransaction(utx);
117+ return metamaskSignTransaction(stx);
134118 } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
135119 if (!(txnSender && txnSigner)) {
136120 throw Error('WalletConnect TX signer unavailable');
@@ -152,12 +136,11 @@ const signTransaction = async ({
152136
153137const sendSignedTransaction = (
154138 web3: Web3,
155− // TODO: do we still need legacy TX type? elsewhere too
156− stx: Transaction | EIP1559Transaction | FakeSignedTx,
139+ stx: EIP1559Transaction | FakeSignedTx,
157140 doubtNonceError: boolean
158141): Promise<string> => {
159142 // if we couldn't sign it, we depend on the given sender function
160− if (!(stx instanceof Transaction || stx instanceof EIP1559Transaction)) { // TODO
143+ if (!(stx instanceof EIP1559Transaction)) { // TODO
161144 if (doubtNonceError) {
162145 console.log('why doubting nonce error? tank unavailable without rawtx.');
163146 }
@@ -221,7 +204,7 @@ const waitForTransactionConfirm = (web3: Web3, txHash: string) => {
221204// returns a Promise that resolves when all stxs have been sent & confirmed
222205const sendAndAwaitAll = (
223206 web3: Web3,
224− stxs: Transaction[] | EIP1559Transaction[] | FakeSignedTx[],
207+ stxs: EIP1559Transaction[] | FakeSignedTx[],
225208 doubtNonceError: boolean
226209) => {
227210 return Promise.all(
@@ -234,13 +217,12 @@ const sendAndAwaitAll = (
234217
235218const sendAndAwaitAllSerial = (
236219 web3: Web3,
237− stxs: Array<Transaction> | Array<EIP1559Transaction> | Array<FakeSignedTx>,
220+ stxs: EIP1559Transaction[] | FakeSignedTx[],
238221 doubtNonceError: boolean
239222) => {
240− // tsc complains that stxs.reduce() is not callable
241− //@ts-ignore
223+ //@ts-expect-error tsc complains that stxs.reduce() is not callable
242224 return stxs.reduce(
243− (promise: Promise<TransactionReceipt[]>, stx: Transaction | FakeSignedTx) =>
225+ (promise: Promise<TransactionReceipt[]>, stx: EIP1559Transaction | FakeSignedTx) =>
244226 promise.then(async (receipts = []) => {
245227 const txHash = await sendSignedTransaction(web3, stx, doubtNonceError);
246228 return [...receipts, await waitForTransactionConfirm(web3, txHash)];
@@ -251,7 +233,7 @@ const sendAndAwaitAllSerial = (
251233
252234const sendTransactionsAndAwaitConfirm = async (
253235 web3: Web3,
254− signedTxs: Array<Transaction> | Array<EIP1559Transaction> | Array<FakeSignedTx>,
236+ signedTxs: EIP1559Transaction[] | FakeSignedTx[],
255237 usedTank: boolean
256238) =>
257239 Promise.all(signedTxs.map(tx => sendSignedTransaction(web3, tx, usedTank)));