Skip to content

Commit e38a988

Browse files
committed
Add 5792 middleware hooks
Support batch transaction type.
1 parent 5535411 commit e38a988

File tree

20 files changed

+675
-226
lines changed

20 files changed

+675
-226
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
diff --git a/dist/cjs/util.js b/dist/cjs/util.js
2+
index d60cf924ee8144ee49dd07e8e910d87a2c4bfb6a..5651d71e272db4c507956d162b89da7a70b9c97f 100644
3+
--- a/dist/cjs/util.js
4+
+++ b/dist/cjs/util.js
5+
@@ -119,10 +119,7 @@ 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 nonceList = (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+
@@ -138,11 +135,7 @@ class AuthorizationLists {
18+
const data = bufferAuthorizationList[i];
19+
const chainId = (0, util_1.bytesToHex)(data[0]);
20+
const address = (0, util_1.bytesToHex)(data[1]);
21+
- const nonces = data[2];
22+
- const nonceList = [];
23+
- for (let j = 0; j < nonces.length; j++) {
24+
- nonceList.push((0, util_1.bytesToHex)(nonces[j]));
25+
- }
26+
+ const nonceList = (0, util_1.bytesToHex)(data[2]);
27+
const yParity = (0, util_1.bytesToHex)(data[3]);
28+
const r = (0, util_1.bytesToHex)(data[4]);
29+
const s = (0, util_1.bytesToHex)(data[5]);
30+
@@ -175,12 +168,6 @@ class AuthorizationLists {
31+
if (address.length !== 20) {
32+
throw new Error('Invalid EIP-7702 transaction: address length should be 20 bytes');
33+
}
34+
- if (nonceList.length > 1) {
35+
- throw new Error('Invalid EIP-7702 transaction: nonce list should consist of at most 1 item');
36+
- }
37+
- else if (nonceList.length === 1) {
38+
- (0, util_1.validateNoLeadingZeroes)({ nonce: nonceList[0] });
39+
- }
40+
}
41+
}
42+
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 }),
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import {
2+
TransactionController,
3+
TransactionControllerGetStateAction,
4+
TransactionControllerState,
5+
} from '@metamask/transaction-controller';
6+
import {
7+
AutoManagedNetworkClient,
8+
CustomNetworkClientConfiguration,
9+
NetworkControllerGetNetworkClientByIdAction,
10+
} from '@metamask/network-controller';
11+
import { SendCalls, SendCallsParams } from '@metamask/eth-json-rpc-middleware';
12+
import { JsonRpcRequest } from '@metamask/utils';
13+
import { Messenger } from '@metamask/base-controller';
14+
import {
15+
EIP5792Messenger,
16+
getCapabilities,
17+
getTransactionReceiptsByBatchId,
18+
processSendCalls,
19+
} from './eip5792';
20+
21+
const CHAIN_ID_MOCK = '0x123';
22+
const CHAIN_ID_2_MOCK = '0xabc';
23+
const BATCH_ID_MOCK = '123-456';
24+
const NETWORK_CLIENT_ID_MOCK = 'test-client';
25+
const ORIGIN_MOCK = 'test.com';
26+
27+
const RECEIPT_MOCK = {
28+
status: '0x1',
29+
transactionHash: '0x123',
30+
};
31+
32+
const RECEIPT_2_MOCK = {
33+
status: '0x0',
34+
transactionHash: '0x123456',
35+
};
36+
37+
const SEND_CALLS_MOCK: SendCalls = {
38+
version: '1.0',
39+
calls: [{ to: '0x123' }],
40+
chainId: CHAIN_ID_MOCK,
41+
from: '0x123',
42+
};
43+
44+
const REQUEST_MOCK = {
45+
id: 1,
46+
jsonrpc: '2.0',
47+
method: 'wallet_sendCalls',
48+
networkClientId: NETWORK_CLIENT_ID_MOCK,
49+
origin: ORIGIN_MOCK,
50+
params: [SEND_CALLS_MOCK],
51+
} as JsonRpcRequest<SendCallsParams> & { networkClientId: string };
52+
53+
describe('EIP-5792', () => {
54+
let addTransactionBatchMock: jest.MockedFn<
55+
TransactionController['addTransactionBatch']
56+
>;
57+
58+
let isAtomicBatchSupportedMock: jest.MockedFn<
59+
TransactionController['isAtomicBatchSupported']
60+
>;
61+
62+
let getNetworkClientByIdMock: jest.MockedFn<
63+
NetworkControllerGetNetworkClientByIdAction['handler']
64+
>;
65+
66+
let getTransactionControllerStateMock: jest.MockedFn<
67+
TransactionControllerGetStateAction['handler']
68+
>;
69+
70+
let messenger: EIP5792Messenger;
71+
72+
beforeEach(() => {
73+
jest.resetAllMocks();
74+
75+
addTransactionBatchMock = jest.fn();
76+
isAtomicBatchSupportedMock = jest.fn();
77+
getTransactionControllerStateMock = jest.fn();
78+
getNetworkClientByIdMock = jest.fn();
79+
80+
messenger = new Messenger();
81+
82+
messenger.registerActionHandler(
83+
'NetworkController:getNetworkClientById',
84+
getNetworkClientByIdMock,
85+
);
86+
87+
messenger.registerActionHandler(
88+
'TransactionController:getState',
89+
getTransactionControllerStateMock,
90+
);
91+
92+
getNetworkClientByIdMock.mockReturnValue({
93+
configuration: {
94+
chainId: CHAIN_ID_MOCK,
95+
},
96+
} as unknown as AutoManagedNetworkClient<CustomNetworkClientConfiguration>);
97+
98+
addTransactionBatchMock.mockResolvedValue({ batchId: BATCH_ID_MOCK });
99+
});
100+
101+
describe('processSendCalls', () => {
102+
it('calls adds transaction batch hook', async () => {
103+
await processSendCalls(
104+
{ addTransactionBatch: addTransactionBatchMock },
105+
messenger,
106+
SEND_CALLS_MOCK,
107+
REQUEST_MOCK,
108+
);
109+
110+
expect(addTransactionBatchMock).toHaveBeenCalledWith({
111+
from: SEND_CALLS_MOCK.from,
112+
networkClientId: NETWORK_CLIENT_ID_MOCK,
113+
origin: ORIGIN_MOCK,
114+
transactions: [{ params: SEND_CALLS_MOCK.calls[0] }],
115+
});
116+
});
117+
118+
it('returns batch ID from hook', async () => {
119+
expect(
120+
await processSendCalls(
121+
{ addTransactionBatch: addTransactionBatchMock },
122+
messenger,
123+
SEND_CALLS_MOCK,
124+
REQUEST_MOCK,
125+
),
126+
).toBe(BATCH_ID_MOCK);
127+
});
128+
129+
it('throws if chain ID does not match network client', async () => {
130+
await expect(
131+
processSendCalls(
132+
{ addTransactionBatch: addTransactionBatchMock },
133+
messenger,
134+
{ ...SEND_CALLS_MOCK, chainId: CHAIN_ID_2_MOCK },
135+
REQUEST_MOCK,
136+
),
137+
).rejects.toThrow(
138+
`Chain ID must match the dApp selected network: Got ${CHAIN_ID_2_MOCK}, expected ${CHAIN_ID_MOCK}`,
139+
);
140+
});
141+
});
142+
143+
describe('getTransactionReceiptsByBatchId', () => {
144+
it('returns transaction receipts from transaction controller with matching ID', () => {
145+
getTransactionControllerStateMock.mockReturnValueOnce({
146+
transactions: [
147+
{ id: BATCH_ID_MOCK, txReceipt: RECEIPT_MOCK },
148+
{ id: '456-789', txReceipt: {} },
149+
{ id: BATCH_ID_MOCK, txReceipt: RECEIPT_2_MOCK },
150+
],
151+
} as TransactionControllerState);
152+
153+
expect(
154+
getTransactionReceiptsByBatchId(messenger, BATCH_ID_MOCK),
155+
).toStrictEqual([RECEIPT_MOCK, RECEIPT_2_MOCK]);
156+
});
157+
});
158+
159+
describe('getCapabilities', () => {
160+
it('returns atomic batch capabilities using hook', async () => {
161+
isAtomicBatchSupportedMock.mockResolvedValueOnce([
162+
CHAIN_ID_MOCK,
163+
CHAIN_ID_2_MOCK,
164+
]);
165+
166+
expect(
167+
await getCapabilities(
168+
{ isAtomicBatchSupported: isAtomicBatchSupportedMock },
169+
'0x123',
170+
),
171+
).toStrictEqual({
172+
[CHAIN_ID_MOCK]: {
173+
atomicBatch: {
174+
supported: true,
175+
},
176+
},
177+
[CHAIN_ID_2_MOCK]: {
178+
atomicBatch: {
179+
supported: true,
180+
},
181+
},
182+
});
183+
});
184+
});
185+
});

0 commit comments

Comments
 (0)