on master help sign in
tx: metamask flow uses EIP1559
b6399a0tomholford
src/lib/metamask.ts+16−16
@@ -1,3 +1,4 @@
1+import { FeeMarketEIP1559Transaction as EIP1559Transaction, JsonTx } from '@ethereumjs/tx';
1import Web3 from 'web3';
2import Web3 from 'web3';
2import { toHex } from 'web3-utils';
3import { toHex } from 'web3-utils';
3import { FakeSignableTx, FakeSignResult } from './txn';
4import { FakeSignableTx, FakeSignResult } from './txn';
@@ -12,24 +13,30 @@ export class MetamaskWallet {
12
13
13// Cheat a bit and pretend to sign transaction
14// Cheat a bit and pretend to sign transaction
14// Sign it actually when sending
15// Sign it actually when sending
15−export const metamaskSignTransaction = async (txn: FakeSignableTx) => {
16+export const metamaskSignTransaction = async (txn: EIP1559Transaction) => {
16 return FakeSignResult(txn, metamaskSendTransaction);
17 return FakeSignResult(txn, metamaskSendTransaction);
17};
18};
18
19
19const metamaskSendTransaction = async (txn: FakeSignableTx, web3: Web3) => {
20const metamaskSendTransaction = async (txn: FakeSignableTx, web3: Web3) => {
20− const { data, gasLimit, gasPrice, nonce, to, from } = txn; // TODO
21+ const accounts = await web3.eth.getAccounts();
21− if (!(data && gasLimit && gasPrice && nonce && to && from)) { // TODO
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
22 throw new Error('Unable to send Metamask TX, something is missing');
28 throw new Error('Unable to send Metamask TX, something is missing');
23 }
29 }
24
30
25 // https://docs.metamask.io/guide/sending-transactions.html#transaction-parameters
31 // 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
26 const metamaskFormattedTxn = {
33 const metamaskFormattedTxn = {
27− data: data.toString(),
34+ from,
28− gasLimit,
35+ to,
29− gasPrice, // TODO
36+ value,
30− nonce: Number(nonce).toString(),
37+ data,
31− to: to.toString(),
38+ maxPriorityFeePerGas,
32− from: toHex(from),
39+ maxFeePerGas
33 };
40 };
34
41
35 let txHash;
42 let txHash;
@@ -41,19 +48,12 @@ const metamaskSendTransaction = async (txn: FakeSignableTx, web3: Web3) => {
41 txHash = await window.ethereum.request({
48 txHash = await window.ethereum.request({
42 method: 'eth_sendTransaction',
49 method: 'eth_sendTransaction',
43 params: [metamaskFormattedTxn],
50 params: [metamaskFormattedTxn],
44− from: txn.from,
45 });
51 });
46 } else {
52 } else {
47 //NOTE since this gives us a receipt instead of just the hash of the
53 //NOTE since this gives us a receipt instead of just the hash of the
48 // signed tx, it makes us wait for confirmation outside of our own
54 // signed tx, it makes us wait for confirmation outside of our own
49 // waitForTransactionConfirm. because of this the progress bar loses
55 // waitForTransactionConfirm. because of this the progress bar loses
50 // its progressiveness for metamask users.
56 // 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
57 let receipt = await web3.eth.sendTransaction(metamaskFormattedTxn);
57 let receipt = await web3.eth.sendTransaction(metamaskFormattedTxn);
58 txHash = receipt.transactionHash;
58 txHash = receipt.transactionHash;
59 }
59 }
src/lib/txn.ts+13−31
@@ -1,5 +1,5 @@
1import { Common, Chain, Hardfork } from '@ethereumjs/common';
1import { 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';
3import { ITxData } from '@walletconnect/types';
3import { ITxData } from '@walletconnect/types';
4import { toHex, toWei } from 'web3-utils';
4import { toHex, toWei } from 'web3-utils';
5import retry from 'async-retry';
5import retry from 'async-retry';
@@ -20,21 +20,9 @@ const RETRY_OPTIONS = {
20 randomize: false,
20 randomize: false,
21};
21};
22
22
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 =
23+export type FakeSignableTx = EIP1559Transaction;
33− | ITxData
34− | (JsonTx & { from: string; to: string; gas: string })
35− | SignableTx;
36
24
37−type TxSender = (txn: FakeSignableTx, web3: Web3) => Promise<string>; // Metamask requires Web3 (txhash)
25+type TxSender = (txn: FakeSignableTx, web3: Web3) => Promise<string>;
38
26
39export type FakeSignedTx = {
27export type FakeSignedTx = {
40 txn: FakeSignableTx;
28 txn: FakeSignableTx;
@@ -57,7 +45,7 @@ interface signTransactionProps {
57 walletType: symbol;
45 walletType: symbol;
58 walletHdPath: string; // TODO: is this still needed?
46 walletHdPath: string; // TODO: is this still needed?
59 networkType: symbol;
47 networkType: symbol;
60− txn: Transaction | EIP1559Transaction;
48+ txn: { data: string, to: string, value: number }
61 nonce: number | string;
49 nonce: number | string;
62 chainId: number | string;
50 chainId: number | string;
63 gasPriceData: GasPriceData;
51 gasPriceData: GasPriceData;
@@ -74,7 +62,6 @@ const _web3 = () => {
74const estimateGasLimit = async (utx: TransactionConfig) => {
62const estimateGasLimit = async (utx: TransactionConfig) => {
75 const web3 = _web3();
63 const web3 = _web3();
76 const estimate = await web3.eth.estimateGas(utx);
64 const estimate = await web3.eth.estimateGas(utx);
77− console.log(estimate);
78 return web3.utils.toBN(estimate).muln(120).divn(100); // 20% cushion
65 return web3.utils.toBN(estimate).muln(120).divn(100); // 20% cushion
79}
66}
80
67
@@ -89,7 +76,7 @@ const signTransaction = async ({
89 walletType,
76 walletType,
90 walletHdPath, // TODO
77 walletHdPath, // TODO
91 networkType,
78 networkType,
92− txn,
79+ txn, // output of transactionBuilder in useEthereumTransaction
93 nonce, // number
80 nonce, // number
94 chainId, // number
81 chainId, // number
95 gasPriceData, // GasPriceData
82 gasPriceData, // GasPriceData
@@ -97,7 +84,6 @@ const signTransaction = async ({
97 txnSigner, // optionally inject a transaction signing function,
84 txnSigner, // optionally inject a transaction signing function,
98 txnSender, // and a sending function, for wallets that need these passed in.
85 txnSender, // and a sending function, for wallets that need these passed in.
99}: signTransactionProps) => {
86}: signTransactionProps) => {
100− console.log('txn', txn);
101 const from = wallet.address;
87 const from = wallet.address;
102 const estimate = await estimateGasLimit({...txn, from });
88 const estimate = await estimateGasLimit({...txn, from });
103 const maxFeePerGas = await getMaxFeePerGas();
89 const maxFeePerGas = await getMaxFeePerGas();
@@ -112,8 +98,6 @@ const signTransaction = async ({
112 chainId: toHex(chainId),
98 chainId: toHex(chainId),
113 type: toHex(EIP1559_TRANSACTION_TYPE),
99 type: toHex(EIP1559_TRANSACTION_TYPE),
114 }
100 }
115−
116− const utx: SignableTx = Object.assign(txn, txParams);
117
101
118 // TODO: Ropsten will sunset Q4 2022, need to replace w/ support for Goerli
102 // TODO: Ropsten will sunset Q4 2022, need to replace w/ support for Goerli
119 const chain =
103 const chain =
@@ -130,7 +114,7 @@ const signTransaction = async ({
130 let stx = EIP1559Transaction.fromTxData(txParams, txConfig)
114 let stx = EIP1559Transaction.fromTxData(txParams, txConfig)
131
115
132 if (walletType === WALLET_TYPES.METAMASK) {
116 if (walletType === WALLET_TYPES.METAMASK) {
133− return metamaskSignTransaction(utx);
117+ return metamaskSignTransaction(stx);
134 } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
118 } else if (walletType === WALLET_TYPES.WALLET_CONNECT) {
135 if (!(txnSender && txnSigner)) {
119 if (!(txnSender && txnSigner)) {
136 throw Error('WalletConnect TX signer unavailable');
120 throw Error('WalletConnect TX signer unavailable');
@@ -152,12 +136,11 @@ const signTransaction = async ({
152
136
153const sendSignedTransaction = (
137const sendSignedTransaction = (
154 web3: Web3,
138 web3: Web3,
155− // TODO: do we still need legacy TX type? elsewhere too
156− stx: Transaction | EIP1559Transaction | FakeSignedTx,
139+ stx: EIP1559Transaction | FakeSignedTx,
157 doubtNonceError: boolean
140 doubtNonceError: boolean
158): Promise<string> => {
141): Promise<string> => {
159 // if we couldn't sign it, we depend on the given sender function
142 // 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
161 if (doubtNonceError) {
144 if (doubtNonceError) {
162 console.log('why doubting nonce error? tank unavailable without rawtx.');
145 console.log('why doubting nonce error? tank unavailable without rawtx.');
163 }
146 }
@@ -221,7 +204,7 @@ const waitForTransactionConfirm = (web3: Web3, txHash: string) => {
221// returns a Promise that resolves when all stxs have been sent & confirmed
204// returns a Promise that resolves when all stxs have been sent & confirmed
222const sendAndAwaitAll = (
205const sendAndAwaitAll = (
223 web3: Web3,
206 web3: Web3,
224− stxs: Transaction[] | EIP1559Transaction[] | FakeSignedTx[],
207+ stxs: EIP1559Transaction[] | FakeSignedTx[],
225 doubtNonceError: boolean
208 doubtNonceError: boolean
226) => {
209) => {
227 return Promise.all(
210 return Promise.all(
@@ -234,13 +217,12 @@ const sendAndAwaitAll = (
234
217
235const sendAndAwaitAllSerial = (
218const sendAndAwaitAllSerial = (
236 web3: Web3,
219 web3: Web3,
237− stxs: Array<Transaction> | Array<EIP1559Transaction> | Array<FakeSignedTx>,
220+ stxs: EIP1559Transaction[] | FakeSignedTx[],
238 doubtNonceError: boolean
221 doubtNonceError: boolean
239) => {
222) => {
240− // tsc complains that stxs.reduce() is not callable
223+ //@ts-expect-error tsc complains that stxs.reduce() is not callable
241− //@ts-ignore
242 return stxs.reduce(
224 return stxs.reduce(
243− (promise: Promise<TransactionReceipt[]>, stx: Transaction | FakeSignedTx) =>
225+ (promise: Promise<TransactionReceipt[]>, stx: EIP1559Transaction | FakeSignedTx) =>
244 promise.then(async (receipts = []) => {
226 promise.then(async (receipts = []) => {
245 const txHash = await sendSignedTransaction(web3, stx, doubtNonceError);
227 const txHash = await sendSignedTransaction(web3, stx, doubtNonceError);
246 return [...receipts, await waitForTransactionConfirm(web3, txHash)];
228 return [...receipts, await waitForTransactionConfirm(web3, txHash)];
@@ -251,7 +233,7 @@ const sendAndAwaitAllSerial = (
251
233
252const sendTransactionsAndAwaitConfirm = async (
234const sendTransactionsAndAwaitConfirm = async (
253 web3: Web3,
235 web3: Web3,
254− signedTxs: Array<Transaction> | Array<EIP1559Transaction> | Array<FakeSignedTx>,
236+ signedTxs: EIP1559Transaction[] | FakeSignedTx[],
255 usedTank: boolean
237 usedTank: boolean
256) =>
238) =>
257 Promise.all(signedTxs.map(tx => sendSignedTransaction(web3, tx, usedTank)));
239 Promise.all(signedTxs.map(tx => sendSignedTransaction(web3, tx, usedTank)));