Skip to content

Commit e925ae5

Browse files
feat: support atomic batch transactions (#30271)
## **Description** Support atomic batch transactions via EIP-5792 and EIP-7702. Specifically: - Upgrade `@metamask/transaction-controller` to support adding batch transactions. - Upgrade `@metamask/eth-json-rpc-middleware` to validate and process EIP-5792 RPC requests. - Patch `@etheremjs/tx` to fix nonce type in EIP-7702 authorizations. - Add EIP-5792 utils to provide hooks for middleware. - Add minimal support for `TransactionType.batch` in confirmations. [![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/MetaMask/metamask-extension/pull/30271?quickstart=1) ## **Related issues** Fixes [#4209](MetaMask/MetaMask-planning#4209) ## **Manual testing steps** ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [x] I've followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots.
1 parent cc1408f commit e925ae5

File tree

19 files changed

+761
-226
lines changed

19 files changed

+761
-226
lines changed
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
diff --git a/dist/cjs/util.js b/dist/cjs/util.js
2+
index d60cf924ee8144ee49dd07e8e910d87a2c4bfb6a..ea5494530c05f4c831e301b88fef7855c89f2885 100644
3+
--- a/dist/cjs/util.js
4+
+++ b/dist/cjs/util.js
5+
@@ -119,14 +119,11 @@ class AuthorizationLists {
6+
}
7+
const chainId = (0, util_1.hexToBytes)(item.chainId);
8+
const addressBytes = (0, util_1.hexToBytes)(item.address);
9+
- const nonceList = [];
10+
- for (let j = 0; j < item.nonce.length; j++) {
11+
- nonceList.push((0, util_1.hexToBytes)(item.nonce[j]));
12+
- }
13+
+ const nonce = (0, util_1.hexToBytes)(item.nonce);
14+
const yParity = (0, util_1.hexToBytes)(item.yParity);
15+
const r = (0, util_1.hexToBytes)(item.r);
16+
const s = (0, util_1.hexToBytes)(item.s);
17+
- newAuthorizationList.push([chainId, addressBytes, nonceList, yParity, r, s]);
18+
+ newAuthorizationList.push([chainId, addressBytes, nonce, yParity, r, s]);
19+
}
20+
bufferAuthorizationList = newAuthorizationList;
21+
}
22+
@@ -138,18 +135,14 @@ class AuthorizationLists {
23+
const data = bufferAuthorizationList[i];
24+
const chainId = (0, util_1.bytesToHex)(data[0]);
25+
const address = (0, util_1.bytesToHex)(data[1]);
26+
- const nonces = data[2];
27+
- const nonceList = [];
28+
- for (let j = 0; j < nonces.length; j++) {
29+
- nonceList.push((0, util_1.bytesToHex)(nonces[j]));
30+
- }
31+
+ const nonce = (0, util_1.bytesToHex)(data[2]);
32+
const yParity = (0, util_1.bytesToHex)(data[3]);
33+
const r = (0, util_1.bytesToHex)(data[4]);
34+
const s = (0, util_1.bytesToHex)(data[5]);
35+
const jsonItem = {
36+
chainId,
37+
address,
38+
- nonce: nonceList,
39+
+ nonce,
40+
yParity,
41+
r,
42+
s,
43+
@@ -167,20 +160,14 @@ class AuthorizationLists {
44+
for (let key = 0; key < authorizationList.length; key++) {
45+
const authorizationListItem = authorizationList[key];
46+
const address = authorizationListItem[1];
47+
- const nonceList = authorizationListItem[2];
48+
+ const nonce = authorizationListItem[2];
49+
const yParity = authorizationListItem[3];
50+
const r = authorizationListItem[4];
51+
const s = authorizationListItem[5];
52+
- (0, util_1.validateNoLeadingZeroes)({ yParity, r, s });
53+
+ (0, util_1.validateNoLeadingZeroes)({ yParity, r, s, nonce });
54+
if (address.length !== 20) {
55+
throw new Error('Invalid EIP-7702 transaction: address length should be 20 bytes');
56+
}
57+
- if (nonceList.length > 1) {
58+
- throw new Error('Invalid EIP-7702 transaction: nonce list should consist of at most 1 item');
59+
- }
60+
- else if (nonceList.length === 1) {
61+
- (0, util_1.validateNoLeadingZeroes)({ nonce: nonceList[0] });
62+
- }
63+
}
64+
}
65+
static getDataFeeEIP7702(authorityList, common) {
66+
diff --git a/dist/esm/util.js b/dist/esm/util.js
67+
index 36fa7e9917f72b042efc753ba5d1c9cd0d5572db..de454fa2f09a98b2a8716e173d1cd1f87101a490 100644
68+
--- a/dist/esm/util.js
69+
+++ b/dist/esm/util.js
70+
@@ -114,14 +114,11 @@ export class AuthorizationLists {
71+
}
72+
const chainId = hexToBytes(item.chainId);
73+
const addressBytes = hexToBytes(item.address);
74+
- const nonceList = [];
75+
- for (let j = 0; j < item.nonce.length; j++) {
76+
- nonceList.push(hexToBytes(item.nonce[j]));
77+
- }
78+
+ const nonce = hexToBytes(item.nonce);
79+
const yParity = hexToBytes(item.yParity);
80+
const r = hexToBytes(item.r);
81+
const s = hexToBytes(item.s);
82+
- newAuthorizationList.push([chainId, addressBytes, nonceList, yParity, r, s]);
83+
+ newAuthorizationList.push([chainId, addressBytes, nonce, yParity, r, s]);
84+
}
85+
bufferAuthorizationList = newAuthorizationList;
86+
}
87+
@@ -133,18 +130,14 @@ export class AuthorizationLists {
88+
const data = bufferAuthorizationList[i];
89+
const chainId = bytesToHex(data[0]);
90+
const address = bytesToHex(data[1]);
91+
- const nonces = data[2];
92+
- const nonceList = [];
93+
- for (let j = 0; j < nonces.length; j++) {
94+
- nonceList.push(bytesToHex(nonces[j]));
95+
- }
96+
+ const nonce = bytesToHex(data[2]);
97+
const yParity = bytesToHex(data[3]);
98+
const r = bytesToHex(data[4]);
99+
const s = bytesToHex(data[5]);
100+
const jsonItem = {
101+
chainId,
102+
address,
103+
- nonce: nonceList,
104+
+ nonce,
105+
yParity,
106+
r,
107+
s,
108+
@@ -162,20 +155,14 @@ export class AuthorizationLists {
109+
for (let key = 0; key < authorizationList.length; key++) {
110+
const authorizationListItem = authorizationList[key];
111+
const address = authorizationListItem[1];
112+
- const nonceList = authorizationListItem[2];
113+
+ const nonce = authorizationListItem[2];
114+
const yParity = authorizationListItem[3];
115+
const r = authorizationListItem[4];
116+
const s = authorizationListItem[5];
117+
- validateNoLeadingZeroes({ yParity, r, s });
118+
+ validateNoLeadingZeroes({ yParity, r, s, nonce });
119+
if (address.length !== 20) {
120+
throw new Error('Invalid EIP-7702 transaction: address length should be 20 bytes');
121+
}
122+
- if (nonceList.length > 1) {
123+
- throw new Error('Invalid EIP-7702 transaction: nonce list should consist of at most 1 item');
124+
- }
125+
- else if (nonceList.length === 1) {
126+
- validateNoLeadingZeroes({ nonce: nonceList[0] });
127+
- }
128+
}
129+
}
130+
static getDataFeeEIP7702(authorityList, common) {

.yarn/patches/@metamask-transaction-controller-npm-45.0.0-010fef9da6.patch

Lines changed: 0 additions & 17 deletions
This file was deleted.

app/scripts/controller-init/messengers/transaction-controller-messenger.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { AccountsControllerGetSelectedAccountAction } from '@metamask/accounts-controller';
1+
import {
2+
AccountsControllerGetSelectedAccountAction,
3+
AccountsControllerGetStateAction,
4+
} from '@metamask/accounts-controller';
25
import { ApprovalControllerActions } from '@metamask/approval-controller';
36
import { Messenger } from '@metamask/base-controller';
47
import {
@@ -21,6 +24,8 @@ import {
2124
TransactionControllerUnapprovedTransactionAddedEvent,
2225
} from '@metamask/transaction-controller';
2326
import { SmartTransactionsControllerSmartTransactionEvent } from '@metamask/smart-transactions-controller';
27+
import { RemoteFeatureFlagControllerGetStateAction } from '@metamask/remote-feature-flag-controller';
28+
import { KeyringControllerSignEip7702AuthorizationAction } from '@metamask/keyring-controller';
2429
import {
2530
SwapsControllerSetApproveTxIdAction,
2631
SwapsControllerSetTradeTxIdAction,
@@ -29,9 +34,12 @@ import {
2934
type MessengerActions =
3035
| ApprovalControllerActions
3136
| AccountsControllerGetSelectedAccountAction
37+
| AccountsControllerGetStateAction
38+
| KeyringControllerSignEip7702AuthorizationAction
3239
| NetworkControllerFindNetworkClientIdByChainIdAction
3340
| NetworkControllerGetEIP1559CompatibilityAction
3441
| NetworkControllerGetNetworkClientByIdAction
42+
| RemoteFeatureFlagControllerGetStateAction
3543
| SwapsControllerSetApproveTxIdAction
3644
| SwapsControllerSetTradeTxIdAction;
3745

@@ -60,9 +68,12 @@ export function getTransactionControllerMessenger(
6068
name: 'TransactionController',
6169
allowedActions: [
6270
'AccountsController:getSelectedAccount',
71+
'AccountsController:getState',
6372
`ApprovalController:addRequest`,
73+
'KeyringController:signEip7702Authorization',
6474
'NetworkController:findNetworkClientIdByChainId',
6575
'NetworkController:getNetworkClientById',
76+
'RemoteFeatureFlagController:getState',
6677
],
6778
allowedEvents: [`NetworkController:stateChange`],
6879
});
@@ -92,6 +103,7 @@ export function getTransactionControllerInitMessenger(
92103
'ApprovalController:startFlow',
93104
'ApprovalController:updateRequestState',
94105
'NetworkController:getEIP1559Compatibility',
106+
'RemoteFeatureFlagController:getState',
95107
'SwapsController:setApproveTxId',
96108
'SwapsController:setTradeTxId',
97109
],

app/scripts/controllers/permissions/specifications.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,13 @@ export const unrestrictedMethods = Object.freeze([
153153
'personal_ecRecover',
154154
'personal_sign',
155155
'wallet_addEthereumChain',
156+
'wallet_getCallsStatus',
157+
'wallet_getCapabilities',
156158
'wallet_getPermissions',
157159
'wallet_requestPermissions',
158160
'wallet_revokePermissions',
159161
'wallet_registerOnboarding',
162+
'wallet_sendCalls',
160163
'wallet_switchEthereumChain',
161164
'wallet_watchAsset',
162165
'web3_clientVersion',

app/scripts/lib/createMetamaskMiddleware.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,16 @@ import {
1111
export default function createMetamaskMiddleware({
1212
version,
1313
getAccounts,
14+
getCapabilities,
15+
getTransactionReceiptsByBatchId,
1416
processTransaction,
1517
processTypedMessage,
1618
processTypedMessageV3,
1719
processTypedMessageV4,
1820
processPersonalMessage,
1921
processDecryptMessage,
2022
processEncryptionPublicKey,
23+
processSendCalls,
2124
getPendingNonce,
2225
getPendingTransactionByHash,
2326
}) {
@@ -28,13 +31,16 @@ export default function createMetamaskMiddleware({
2831
}),
2932
createWalletMiddleware({
3033
getAccounts,
34+
getCapabilities,
35+
getTransactionReceiptsByBatchId,
3136
processTransaction,
3237
processTypedMessage,
3338
processTypedMessageV3,
3439
processTypedMessageV4,
3540
processPersonalMessage,
3641
processDecryptMessage,
3742
processEncryptionPublicKey,
43+
processSendCalls,
3844
}),
3945
createPendingNonceMiddleware({ getPendingNonce }),
4046
createPendingTxMiddleware({ getPendingTransactionByHash }),

0 commit comments

Comments
 (0)