From e530f9b5ea0ab7d75bfc4df61fc977ca42c95e78 Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:18:06 +0200 Subject: [PATCH 1/3] fix(sentinel-api-service,transaction-controller): gasCost is a decimal string on the wire, not a number The live Sentinel API (tx-sentinel-{network}.api.cx.metamask.io, infura_simulateTransactions) emits gasCost as a quoted decimal string, not a JSON number. Both TS declarations claimed number. Verified with a live unauthenticated read-only simulation call - see PR body for the raw response bytes. SentinelSimulationResponseTransaction is public exported API, so a downstream consumer typing against number and doing arithmetic on gasCost gets silent string concatenation at runtime with no type error. The one internal consumer (balance-changes.ts, new BN(offset)) happens to tolerate both a number and a base-10 string, which is why this never surfaced as a visible bug. --- packages/sentinel-api-service/CHANGELOG.md | 4 ++++ packages/sentinel-api-service/src/types.ts | 4 ++-- packages/transaction-controller/CHANGELOG.md | 4 ++++ packages/transaction-controller/src/api/simulation-api.ts | 4 ++-- .../transaction-controller/src/utils/balance-changes.test.ts | 4 ++-- packages/transaction-controller/src/utils/balance-changes.ts | 2 +- 6 files changed, 15 insertions(+), 7 deletions(-) diff --git a/packages/sentinel-api-service/CHANGELOG.md b/packages/sentinel-api-service/CHANGELOG.md index f7ea8b2377b..76ca9401470 100644 --- a/packages/sentinel-api-service/CHANGELOG.md +++ b/packages/sentinel-api-service/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Correct `SentinelSimulationResponseTransaction.gasCost` type from `number` to `string`, matching the value the Sentinel API actually returns on the wire ([#PLACEHOLDER](https://github.com/MetaMask/core/pull/PLACEHOLDER)) + ## [1.0.1] ### Changed diff --git a/packages/sentinel-api-service/src/types.ts b/packages/sentinel-api-service/src/types.ts index 63658e6e4b8..13aef1c9697 100644 --- a/packages/sentinel-api-service/src/types.ts +++ b/packages/sentinel-api-service/src/types.ts @@ -449,10 +449,10 @@ export type SentinelSimulationResponseTransaction = { fees?: SentinelSimulationFeeLevel[]; /** - * Estimated total gas cost of the transaction, in wei as a decimal number. + * Estimated total gas cost of the transaction, in wei as a decimal string. * Included in the `stateDiff` when `withGas` is true. */ - gasCost?: number; + gasCost?: string; /** Required `gasLimit` for the transaction. */ gasLimit?: Hex; diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index f8c38eebac4..90d6fc4b8dc 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Type `SimulationResponseTransaction.gasCost` (and the internal `getSimulationBalanceChange` offset) as `string`, matching what the simulation API actually returns on the wire ([#PLACEHOLDER](https://github.com/MetaMask/core/pull/PLACEHOLDER)) + ## [69.7.0] ### Added diff --git a/packages/transaction-controller/src/api/simulation-api.ts b/packages/transaction-controller/src/api/simulation-api.ts index 4d080a3055e..cfc1494d0bc 100644 --- a/packages/transaction-controller/src/api/simulation-api.ts +++ b/packages/transaction-controller/src/api/simulation-api.ts @@ -225,10 +225,10 @@ export type SimulationResponseTransaction = { }[]; /** - * Estimated total gas cost of the transaction. + * Estimated total gas cost of the transaction, in wei as a decimal string. * Included in the stateDiff if `withGas` is true. */ - gasCost?: number; + gasCost?: string; /** Required `gasLimit` for the transaction. */ gasLimit?: Hex; diff --git a/packages/transaction-controller/src/utils/balance-changes.test.ts b/packages/transaction-controller/src/utils/balance-changes.test.ts index 91948e6eadf..610d58d7902 100644 --- a/packages/transaction-controller/src/utils/balance-changes.test.ts +++ b/packages/transaction-controller/src/utils/balance-changes.test.ts @@ -198,7 +198,7 @@ function createEventResponseMock( function createNativeBalanceResponse( previousBalance: string, newBalance: string, - gasCost: number = 0, + gasCost: string = '0', ): SimulationResponse { return { transactions: [ @@ -343,7 +343,7 @@ describe('Balance Change Utils', () => { it('ignoring gas cost', async () => { simulateTransactionsMock.mockResolvedValueOnce( - createNativeBalanceResponse('0x3', '0x8', 2), + createNativeBalanceResponse('0x3', '0x8', '2'), ); const result = await getBalanceChanges(REQUEST_MOCK); diff --git a/packages/transaction-controller/src/utils/balance-changes.ts b/packages/transaction-controller/src/utils/balance-changes.ts index b366a75ba53..ca30759a1c6 100644 --- a/packages/transaction-controller/src/utils/balance-changes.ts +++ b/packages/transaction-controller/src/utils/balance-changes.ts @@ -699,7 +699,7 @@ function extractRootRevert( function getSimulationBalanceChange( previousBalance: Hex, newBalance: Hex, - offset: number = 0, + offset: string = '0', ): SimulationBalanceChange | undefined { const newBalanceBN = hexToBN(newBalance).add(new BN(offset)); const previousBalanceBN = hexToBN(previousBalance); From c8914bf9d93312e441f2e582ff2fba5db3dd608a Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:28:29 +0200 Subject: [PATCH 2/3] fixup: apply codex review - breaking changelog marker, drop non-exported entry, add large-value test --- packages/sentinel-api-service/CHANGELOG.md | 5 ++-- packages/transaction-controller/CHANGELOG.md | 4 --- .../src/utils/balance-changes.test.ts | 27 +++++++++++++++++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/sentinel-api-service/CHANGELOG.md b/packages/sentinel-api-service/CHANGELOG.md index 76ca9401470..b65f9cd308d 100644 --- a/packages/sentinel-api-service/CHANGELOG.md +++ b/packages/sentinel-api-service/CHANGELOG.md @@ -7,9 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Fixed +### Changed -- Correct `SentinelSimulationResponseTransaction.gasCost` type from `number` to `string`, matching the value the Sentinel API actually returns on the wire ([#PLACEHOLDER](https://github.com/MetaMask/core/pull/PLACEHOLDER)) +- **BREAKING:** `SentinelSimulationResponseTransaction.gasCost` is now typed `string` instead of `number`, matching the value the Sentinel API actually returns on the wire (a quoted decimal string, not a JSON number) ([#PLACEHOLDER](https://github.com/MetaMask/core/pull/PLACEHOLDER)) + - Consumers reading `gasCost` and treating it as a `number` (e.g. doing arithmetic directly on it) must parse it first, e.g. via `new BN(gasCost)` or `BigInt(gasCost)`. ## [1.0.1] diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 90d6fc4b8dc..f8c38eebac4 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -7,10 +7,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -### Fixed - -- Type `SimulationResponseTransaction.gasCost` (and the internal `getSimulationBalanceChange` offset) as `string`, matching what the simulation API actually returns on the wire ([#PLACEHOLDER](https://github.com/MetaMask/core/pull/PLACEHOLDER)) - ## [69.7.0] ### Added diff --git a/packages/transaction-controller/src/utils/balance-changes.test.ts b/packages/transaction-controller/src/utils/balance-changes.test.ts index 610d58d7902..69a19c0627f 100644 --- a/packages/transaction-controller/src/utils/balance-changes.test.ts +++ b/packages/transaction-controller/src/utils/balance-changes.test.ts @@ -363,6 +363,33 @@ describe('Balance Change Utils', () => { simulationRevert: undefined, }); }); + + it('supports a gas cost above Number.MAX_SAFE_INTEGER', async () => { + // A wei-scale gas cost this large is realistic on chains such as + // Polygon, and would previously throw when the wire value was + // mistakenly parsed as a JS `number` via `new BN(offset)`. + const largeGasCost = '10500000000000000'; // 2^53 (9007199254740992) < this + simulateTransactionsMock.mockResolvedValueOnce( + createNativeBalanceResponse('0x0', '0x0', largeGasCost), + ); + + const result = await getBalanceChanges(REQUEST_MOCK); + + expect(result).toStrictEqual({ + simulationData: { + callTraceErrors: [], + nativeBalanceChange: { + difference: '0x254db1c2244000', + isDecrease: false, + newBalance: '0x254db1c2244000', + previousBalance: '0x0', + }, + tokenBalanceChanges: [], + }, + gasUsed: undefined, + simulationRevert: undefined, + }); + }); }); describe('returns token balance changes', () => { From 2a3c37e2add05d869d83f1387b60f99a8a53ec9b Mon Sep 17 00:00:00 2001 From: gomes <17035424+gomesalexandre@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:29:15 +0200 Subject: [PATCH 3/3] chore: backfill real PR number in changelog link --- packages/sentinel-api-service/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sentinel-api-service/CHANGELOG.md b/packages/sentinel-api-service/CHANGELOG.md index b65f9cd308d..11991fa8afd 100644 --- a/packages/sentinel-api-service/CHANGELOG.md +++ b/packages/sentinel-api-service/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed -- **BREAKING:** `SentinelSimulationResponseTransaction.gasCost` is now typed `string` instead of `number`, matching the value the Sentinel API actually returns on the wire (a quoted decimal string, not a JSON number) ([#PLACEHOLDER](https://github.com/MetaMask/core/pull/PLACEHOLDER)) +- **BREAKING:** `SentinelSimulationResponseTransaction.gasCost` is now typed `string` instead of `number`, matching the value the Sentinel API actually returns on the wire (a quoted decimal string, not a JSON number) ([#10050](https://github.com/MetaMask/core/pull/10050)) - Consumers reading `gasCost` and treating it as a `number` (e.g. doing arithmetic directly on it) must parse it first, e.g. via `new BN(gasCost)` or `BigInt(gasCost)`. ## [1.0.1]