Skip to content

Commit 509b61d

Browse files
fix: cp-7.60.2 overwrite account upgrade in metamask pay (MetaMask#23521)
## **Description** When generating delegation data for the `TransactionPayController`, overwrite any existing EIP-7702 delegation if not supported. Also bump the controller version to include missing authorization lists on Predict deposit source transactions, when payment token is on Polygon. ## **Changelog** CHANGELOG entry: Fixed bugs causing failed Perps and Predict deposits due to unsupported or missing account upgrades ## **Related issues** Fixes: MetaMask#23494 MetaMask#23514 ## **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 Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/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-mobile/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. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Refactors delegation to correctly build/overwrite EIP-7702 authorizations with validation, integrates Transaction Pay and Delegation 7702 hooks into publish flow, and bumps transaction-related controllers. > > - **Engine/TransactionController init**: > - Integrates `TransactionPayPublishHook` and `Delegation7702PublishHook` with fallback logic before smart transactions. > - Adds `isEIP7702GasFeeTokensEnabled`, `publicKeyEIP7702`, and `getNextNonce` helper. > - Exposes `getNetworkState` via `networkController.state`. > - **Delegation utils (`app/util/transactions/delegation.ts`)**: > - Refactors `buildAuthorizationList` to use `isAtomicBatchSupported` result per-chain, throwing on unsupported chains and missing `upgradeContractAddress`. > - Skips authorization if already upgraded; overwrites authorization when upgraded to a different contract. > - Signs EIP-7702 authorization using nonce lock and builds `AuthorizationList`. > - **Tests**: > - Expands `delegation.test.ts` with cases for already upgraded, different upgrade target, signing calls, and error paths; updates snapshot name. > - **Dependencies**: > - Bumps `@metamask/transaction-controller` to `62.4.0` and `@metamask/transaction-pay-controller` to `^10.3.0` (and associated `@metamask/network-controller` peer to `^27`). > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 923e879. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 66e5e4a commit 509b61d

6 files changed

Lines changed: 131 additions & 51 deletions

File tree

app/core/Engine/controllers/transaction-controller/transaction-controller-init.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,6 @@ export const TransactionControllerInit: ControllerInitFunction<
9696
gasFeeController.fetchGasFeeEstimates(...args),
9797
getNetworkClientRegistry: (...args) =>
9898
networkController.getNetworkClientRegistry(...args),
99-
// @ts-expect-error Type mismatch due to @metamask/network-controller version mismatch.
100-
// The latest version (v27.0.0+) adds NetworkStatus.Degraded enum value
101-
// See: https://github.com/MetaMask/core/pull/7186
10299
getNetworkState: () => networkController.state,
103100
hooks: {
104101
// @ts-expect-error - TransactionController actually sends a signedTx as a second argument, but its type doesn't reflect that.

app/util/transactions/__snapshots__/delegation.test.ts.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Jest Snapshot v1, https://goo.gl/fbAQLP
22

3-
exports[`Transaction Delegation Utils returns delegation data 1`] = `
3+
exports[`Transaction Delegation Utils getDelegationTransaction returns delegation data 1`] = `
44
{
55
"authorizationList": [
66
{

app/util/transactions/delegation.test.ts

Lines changed: 84 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ describe('Transaction Delegation Utils', () => {
7373
mockIsAtomicBatchSupported.mockResolvedValue([
7474
{
7575
chainId: TRANSACTION_META_MOCK.chainId,
76-
isSupported: true,
76+
isSupported: false,
7777
upgradeContractAddress: UPGRADE_CONTRACT_ADDRESS_MOCK,
7878
},
7979
]);
@@ -84,21 +84,92 @@ describe('Transaction Delegation Utils', () => {
8484
});
8585
});
8686

87-
it('returns delegation data', async () => {
88-
const result = await getDelegationTransaction(
89-
messengerMock,
90-
TRANSACTION_META_MOCK,
91-
);
87+
describe('getDelegationTransaction', () => {
88+
it('returns delegation data', async () => {
89+
const result = await getDelegationTransaction(
90+
messengerMock,
91+
TRANSACTION_META_MOCK,
92+
);
9293

93-
expect(result).toMatchSnapshot();
94-
});
94+
expect(result).toMatchSnapshot();
95+
});
96+
97+
it('does not include authorization if already upgraded', async () => {
98+
mockIsAtomicBatchSupported.mockResolvedValue([
99+
{
100+
chainId: TRANSACTION_META_MOCK.chainId,
101+
delegationAddress: UPGRADE_CONTRACT_ADDRESS_MOCK,
102+
isSupported: true,
103+
upgradeContractAddress: UPGRADE_CONTRACT_ADDRESS_MOCK,
104+
},
105+
]);
106+
107+
const result = await getDelegationTransaction(messengerMock, {
108+
...TRANSACTION_META_MOCK,
109+
delegationAddress: UPGRADE_CONTRACT_ADDRESS_MOCK,
110+
});
111+
112+
expect(result.authorizationList).toBeUndefined();
113+
});
114+
115+
it('includes authorization if upgraded to different contract', async () => {
116+
mockIsAtomicBatchSupported.mockResolvedValue([
117+
{
118+
chainId: TRANSACTION_META_MOCK.chainId,
119+
delegationAddress: UPGRADE_CONTRACT_ADDRESS_MOCK,
120+
isSupported: false,
121+
upgradeContractAddress: '0x789' as Hex,
122+
},
123+
]);
124+
125+
const result = await getDelegationTransaction(messengerMock, {
126+
...TRANSACTION_META_MOCK,
127+
delegationAddress: UPGRADE_CONTRACT_ADDRESS_MOCK,
128+
});
129+
130+
expect(result.authorizationList).toHaveLength(1);
131+
});
132+
133+
it('calls DelegationController to sign delegation', async () => {
134+
await getDelegationTransaction(messengerMock, TRANSACTION_META_MOCK);
95135

96-
it('does not include authorization if already upgraded', async () => {
97-
const result = await getDelegationTransaction(messengerMock, {
98-
...TRANSACTION_META_MOCK,
99-
delegationAddress: UPGRADE_CONTRACT_ADDRESS_MOCK,
136+
expect(signDelegationMock).toHaveBeenCalledWith({
137+
chainId: TRANSACTION_META_MOCK.chainId,
138+
delegation: expect.any(Object),
139+
});
140+
});
141+
142+
it('calls KeyringController to sign authorization', async () => {
143+
await getDelegationTransaction(messengerMock, TRANSACTION_META_MOCK);
144+
145+
expect(sign7702Mock).toHaveBeenCalledWith({
146+
chainId: 1,
147+
contractAddress: UPGRADE_CONTRACT_ADDRESS_MOCK,
148+
from: TRANSACTION_META_MOCK.txParams.from,
149+
nonce: NONCE_MOCK,
150+
});
100151
});
101152

102-
expect(result.authorizationList).toBeUndefined();
153+
it('throws if chain does not support EIP-7702', async () => {
154+
mockIsAtomicBatchSupported.mockResolvedValue([]);
155+
156+
await expect(
157+
getDelegationTransaction(messengerMock, TRANSACTION_META_MOCK),
158+
).rejects.toThrow('Chain does not support EIP-7702');
159+
});
160+
161+
it('throws if upgrade contract address is not found', async () => {
162+
mockIsAtomicBatchSupported.mockResolvedValue([
163+
{
164+
chainId: TRANSACTION_META_MOCK.chainId,
165+
isSupported: false,
166+
upgradeContractAddress: undefined,
167+
},
168+
]);
169+
170+
await expect(
171+
getDelegationTransaction(messengerMock, TRANSACTION_META_MOCK),
172+
).rejects.toThrow('Upgrade contract address not found');
173+
});
103174
});
104175
});

app/util/transactions/delegation.ts

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,27 +95,39 @@ async function buildAuthorizationList<MessengerType extends SignMessenger>(
9595
messenger: MessengerType,
9696
): Promise<AuthorizationList | undefined> {
9797
const { TransactionController } = Engine.context;
98-
99-
const { chainId, delegationAddress, networkClientId, txParams } =
100-
transactionMeta;
101-
98+
const { chainId, networkClientId, txParams } = transactionMeta;
10299
const { from } = txParams;
103100

104-
if (delegationAddress) {
105-
log('Skipping authorization list as already upgraded');
106-
return undefined;
107-
}
108-
109-
log('Including authorization as not upgraded');
110-
111101
const atomicBatchResult = await TransactionController.isAtomicBatchSupported({
112102
address: from as Hex,
113103
chainIds: [chainId],
114104
});
115105

116-
const upgradeContractAddress = atomicBatchResult.find(
106+
const chainResult = atomicBatchResult.find(
117107
(r) => r.chainId.toLowerCase() === chainId.toLowerCase(),
118-
)?.upgradeContractAddress;
108+
);
109+
110+
if (!chainResult) {
111+
throw new Error('Chain does not support EIP-7702');
112+
}
113+
114+
const { delegationAddress, isSupported, upgradeContractAddress } =
115+
chainResult;
116+
117+
if (isSupported) {
118+
log('Skipping authorization as already upgraded');
119+
return undefined;
120+
}
121+
122+
if (!delegationAddress) {
123+
log('Upgrading account to EIP-7702', { from, upgradeContractAddress });
124+
} else {
125+
log('Overwriting authorization as already upgraded', {
126+
from,
127+
current: delegationAddress,
128+
new: upgradeContractAddress,
129+
});
130+
}
119131

120132
if (!upgradeContractAddress) {
121133
throw new Error('Upgrade contract address not found');

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@
177177
"@scure/bip32": "1.7.0",
178178
"@metamask/snaps-sdk": "^10.0.0",
179179
"react-native@0.76.9": "patch:react-native@npm%3A0.76.9#./.yarn/patches/react-native-npm-0.76.9-1c25352097.patch",
180-
"@metamask/transaction-controller@npm:^62.3.1": "patch:@metamask/transaction-controller@npm%3A62.3.1#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch"
180+
"@metamask/transaction-controller@npm:^62.4.0": "patch:@metamask/transaction-controller@npm%3A62.4.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch"
181181
},
182182
"dependencies": {
183183
"@config-plugins/detox": "^9.0.0",
@@ -287,8 +287,8 @@
287287
"@metamask/swappable-obj-proxy": "^2.1.0",
288288
"@metamask/swaps-controller": "^15.0.0",
289289
"@metamask/token-search-discovery-controller": "^4.0.0",
290-
"@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.3.1#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch",
291-
"@metamask/transaction-pay-controller": "^10.2.0",
290+
"@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.4.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch",
291+
"@metamask/transaction-pay-controller": "^10.3.0",
292292
"@metamask/tron-wallet-snap": "^1.13.0",
293293
"@metamask/utils": "^11.8.1",
294294
"@ngraveio/bc-ur": "^1.1.6",

yarn.lock

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9872,9 +9872,9 @@ __metadata:
98729872
languageName: node
98739873
linkType: hard
98749874

9875-
"@metamask/transaction-controller@npm:62.3.1, @metamask/transaction-controller@npm:^62.3.0":
9876-
version: 62.3.1
9877-
resolution: "@metamask/transaction-controller@npm:62.3.1"
9875+
"@metamask/transaction-controller@npm:62.4.0, @metamask/transaction-controller@npm:^62.3.0":
9876+
version: 62.4.0
9877+
resolution: "@metamask/transaction-controller@npm:62.4.0"
98789878
dependencies:
98799879
"@ethereumjs/common": "npm:^4.4.0"
98809880
"@ethereumjs/tx": "npm:^5.4.0"
@@ -9891,7 +9891,7 @@ __metadata:
98919891
"@metamask/gas-fee-controller": "npm:^26.0.0"
98929892
"@metamask/messenger": "npm:^0.3.0"
98939893
"@metamask/metamask-eth-abis": "npm:^3.1.1"
9894-
"@metamask/network-controller": "npm:^26.0.0"
9894+
"@metamask/network-controller": "npm:^27.0.0"
98959895
"@metamask/nonce-tracker": "npm:^6.0.0"
98969896
"@metamask/remote-feature-flag-controller": "npm:^2.0.1"
98979897
"@metamask/rpc-errors": "npm:^7.0.2"
@@ -9906,7 +9906,7 @@ __metadata:
99069906
peerDependencies:
99079907
"@babel/runtime": ^7.0.0
99089908
"@metamask/eth-block-tracker": ">=9"
9909-
checksum: 10/bf1fc4b305fcdf295fdc75ff5ebc21014cbd00c7b7fb29d4b34deb3e95c4b621c3139e1426980bc2ca6fa6855b0b47046471d62b527841fe8309f8467133fd7f
9909+
checksum: 10/36a816c881babf7b71542857be50045cb25b1a5cf7fa5444c0ad0c101da3c6718cfd83942ad5f868b53088aa2601c234dcf47e324173014ee5037c084f783438
99109910
languageName: node
99119911
linkType: hard
99129912

@@ -9948,9 +9948,9 @@ __metadata:
99489948
languageName: node
99499949
linkType: hard
99509950

9951-
"@metamask/transaction-controller@patch:@metamask/transaction-controller@npm%3A62.3.1#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch":
9952-
version: 62.3.1
9953-
resolution: "@metamask/transaction-controller@patch:@metamask/transaction-controller@npm%3A62.3.1#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch::version=62.3.1&hash=1a3342"
9951+
"@metamask/transaction-controller@patch:@metamask/transaction-controller@npm%3A62.4.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch":
9952+
version: 62.4.0
9953+
resolution: "@metamask/transaction-controller@patch:@metamask/transaction-controller@npm%3A62.4.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch::version=62.4.0&hash=1a3342"
99549954
dependencies:
99559955
"@ethereumjs/common": "npm:^4.4.0"
99569956
"@ethereumjs/tx": "npm:^5.4.0"
@@ -9967,7 +9967,7 @@ __metadata:
99679967
"@metamask/gas-fee-controller": "npm:^26.0.0"
99689968
"@metamask/messenger": "npm:^0.3.0"
99699969
"@metamask/metamask-eth-abis": "npm:^3.1.1"
9970-
"@metamask/network-controller": "npm:^26.0.0"
9970+
"@metamask/network-controller": "npm:^27.0.0"
99719971
"@metamask/nonce-tracker": "npm:^6.0.0"
99729972
"@metamask/remote-feature-flag-controller": "npm:^2.0.1"
99739973
"@metamask/rpc-errors": "npm:^7.0.2"
@@ -9982,13 +9982,13 @@ __metadata:
99829982
peerDependencies:
99839983
"@babel/runtime": ^7.0.0
99849984
"@metamask/eth-block-tracker": ">=9"
9985-
checksum: 10/2413d51478ee4af037b0eff190fb89747c9bea61087087b3d00ee82e0dd0ffeb27a941da1a7b86293ba6129e5b660e58aea36e1a478e3b6f09235b238a293b5e
9985+
checksum: 10/de9c227ae3d846e60b7f4860c65d8ea75fe6c399cf51750a2baf96fe361b3453e22ab614b8f937a71ffa7dc60d86f17b408a2d23f38baf59919f307ea60ac7d2
99869986
languageName: node
99879987
linkType: hard
99889988

9989-
"@metamask/transaction-pay-controller@npm:^10.2.0":
9990-
version: 10.2.0
9991-
resolution: "@metamask/transaction-pay-controller@npm:10.2.0"
9989+
"@metamask/transaction-pay-controller@npm:^10.3.0":
9990+
version: 10.3.0
9991+
resolution: "@metamask/transaction-pay-controller@npm:10.3.0"
99929992
dependencies:
99939993
"@ethersproject/abi": "npm:^5.7.0"
99949994
"@ethersproject/contracts": "npm:^5.7.0"
@@ -10000,15 +10000,15 @@ __metadata:
1000010000
"@metamask/gas-fee-controller": "npm:^26.0.0"
1000110001
"@metamask/messenger": "npm:^0.3.0"
1000210002
"@metamask/metamask-eth-abis": "npm:^3.1.1"
10003-
"@metamask/network-controller": "npm:^26.0.0"
10003+
"@metamask/network-controller": "npm:^27.0.0"
1000410004
"@metamask/remote-feature-flag-controller": "npm:^2.0.1"
10005-
"@metamask/transaction-controller": "npm:^62.3.1"
10005+
"@metamask/transaction-controller": "npm:^62.4.0"
1000610006
"@metamask/utils": "npm:^11.8.1"
1000710007
bignumber.js: "npm:^9.1.2"
1000810008
bn.js: "npm:^5.2.1"
1000910009
immer: "npm:^9.0.6"
1001010010
lodash: "npm:^4.17.21"
10011-
checksum: 10/20b251ae57cf48f1ed4da018c638b0b380a6a1c1fc051a976fc5e37ee5ebbb755c03d3d261418b63f9f926a602be6614160f860102b8628d69d5f744ce57cf8e
10011+
checksum: 10/511f7f58791b31a752e80229e35749fc86a5b1333aa3dc956b6b294f0680d0881464548eaf9b7e659a85977a2dae00928e80a5bcf84638cefb9b408ed3336701
1001210012
languageName: node
1001310013
linkType: hard
1001410014

@@ -36045,8 +36045,8 @@ __metadata:
3604536045
"@metamask/test-dapp-multichain": "npm:^0.17.1"
3604636046
"@metamask/test-dapp-solana": "npm:^0.3.0"
3604736047
"@metamask/token-search-discovery-controller": "npm:^4.0.0"
36048-
"@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.3.1#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch"
36049-
"@metamask/transaction-pay-controller": "npm:^10.2.0"
36048+
"@metamask/transaction-controller": "patch:@metamask/transaction-controller@npm%3A62.4.0#~/.yarn/patches/@metamask-transaction-controller-npm-61.0.0-cccac388c7.patch"
36049+
"@metamask/transaction-pay-controller": "npm:^10.3.0"
3605036050
"@metamask/tron-wallet-snap": "npm:^1.13.0"
3605136051
"@metamask/utils": "npm:^11.8.1"
3605236052
"@ngraveio/bc-ur": "npm:^1.1.6"

0 commit comments

Comments
 (0)