Skip to content

Commit f15c67e

Browse files
committed
fix(transaction-controller): allow nested batch transactions with duplicate batchId when requireApproval is false
When a dapp triggers addTransactionBatch with a gas fee token, the nested ERC-20 fee-payment transaction is submitted via addTransaction with the same batchId as the parent batch and requireApproval: false. The existing duplicate- batch-ID guard was incorrectly throwing DuplicateBundleId (error code 5720) for these nested transactions. Guard now only applies when requireApproval is not false, since nested batch transactions always pass requireApproval: false to distinguish them from top-level dapp duplicate attempts.
1 parent 7c30d1d commit f15c67e

3 files changed

Lines changed: 35 additions & 1 deletion

File tree

packages/transaction-controller/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Fix `addTransactionBatch` incorrectly throwing `DuplicateBundleId` for nested batch transactions (e.g. ERC-20 gas-fee-token transfers) that share a `batchId` with their parent batch ([#8878](https://github.com/MetaMask/core/pull/8878))
13+
- The duplicate-batch-ID check now only applies when `requireApproval` is not `false`; nested transactions always pass `requireApproval: false`, correctly bypassing the guard
14+
1015
## [66.0.0]
1116

1217
### Changed

packages/transaction-controller/src/TransactionController.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3739,6 +3739,35 @@ describe('TransactionController', () => {
37393739
networkClientId: NETWORK_CLIENT_ID_MOCK,
37403740
});
37413741
});
3742+
3743+
it('does not throw if duplicate and requireApproval is false (nested batch transaction)', async () => {
3744+
const { controller } = setupController({
3745+
options: {
3746+
state: {
3747+
transactions: [
3748+
{
3749+
batchId: BATCH_ID_MOCK,
3750+
} as unknown as TransactionMeta,
3751+
],
3752+
},
3753+
},
3754+
updateToInitialState: true,
3755+
});
3756+
3757+
const txParams = {
3758+
from: ACCOUNT_MOCK,
3759+
to: ACCOUNT_MOCK,
3760+
};
3761+
3762+
const { result } = await controller.addTransaction(txParams, {
3763+
batchId: BATCH_ID_MOCK,
3764+
networkClientId: NETWORK_CLIENT_ID_MOCK,
3765+
origin: ORIGIN_MOCK,
3766+
requireApproval: false,
3767+
});
3768+
3769+
await result.catch(() => {});
3770+
});
37423771
});
37433772
});
37443773

packages/transaction-controller/src/TransactionController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1270,7 +1270,7 @@ export class TransactionController extends BaseController<
12701270
(tx) => tx.batchId?.toLowerCase() === batchId?.toLowerCase(),
12711271
);
12721272

1273-
if (isDuplicateBatchId && !isInternal) {
1273+
if (isDuplicateBatchId && !isInternal && requireApproval !== false) {
12741274
throw new JsonRpcError(
12751275
ErrorCode.DuplicateBundleId,
12761276
'Batch ID already exists',

0 commit comments

Comments
 (0)