Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
4530da6
feat: add getAmountData callback to TransactionPayController
OGPoyraz Jun 3, 2026
70c06c3
fix: fiat moneyAccountDeposit three-phase relay submit
OGPoyraz Jun 3, 2026
876a525
fix: lint errors and update tests for fiat submit changes
OGPoyraz Jun 3, 2026
61064db
Fix lint
OGPoyraz Jun 3, 2026
d5ac30b
docs: update transaction-pay-controller changelog
OGPoyraz Jun 3, 2026
c894ce0
Update changelog
OGPoyraz Jun 3, 2026
59236fb
fix: only reject rate drift when rate worsens, not improves
OGPoyraz Jun 3, 2026
4786428
Update changelog
OGPoyraz Jun 3, 2026
e87e784
fix: lint errors in fiat-submit tests
OGPoyraz Jun 3, 2026
176207b
fix: skip source balance check for relay execute flow
OGPoyraz Jun 3, 2026
48d856b
fix: use accountOverride for wallet address in fiat flow
OGPoyraz Jun 3, 2026
3dbd8f2
fix: revert isExecute balance skip test changes
OGPoyraz Jun 3, 2026
794ad91
fix: remove hasFiatStrategy totals test
OGPoyraz Jun 3, 2026
b63168e
fix: pass fiatPaymentAmount to totals for correct fiat total calculation
OGPoyraz Jun 3, 2026
26ef707
Fix the total derivation
OGPoyraz Jun 3, 2026
56e7a33
test: add coverage for fiat strategy payment amount in totals
OGPoyraz Jun 3, 2026
e36f8af
Address PR review feedback from matthewwalsh0
OGPoyraz Jun 4, 2026
eee7376
feat: skip discovery quote for simple fiat deposits (Perps, Predict)
OGPoyraz Jun 4, 2026
e8acf57
feat: fee-as-buffer strategy, simple relay path, and file split for f…
OGPoyraz Jun 4, 2026
fe4fc11
fix: decouple processTransactions from isPostQuote in relay-quotes
OGPoyraz Jun 4, 2026
50d7476
fix: set skipProcessTransactions for simple fiat relay to fix recipie…
OGPoyraz Jun 4, 2026
f4f7ba4
fix: sum nested call values for EIP-7702 batch transaction
OGPoyraz Jun 5, 2026
90e9a8e
fix: post-quote gas handling for zero-balance fiat-funded accounts
OGPoyraz Jun 5, 2026
fcb6e36
test: add coverage for EIP-7702 batch value summing and update changelog
OGPoyraz Jun 5, 2026
25ece51
test: achieve 100% coverage for relay-quotes, feature-flags, and fiat…
OGPoyraz Jun 5, 2026
e03e64b
revert: remove unvalidated chainId/networkClientId override in fiat-s…
OGPoyraz Jun 5, 2026
55c412f
fix: detect Polygon native token for post-quote gas subtraction
OGPoyraz Jun 5, 2026
ba47bef
fix: skip batch gas estimation when accountOverride diverges from txP…
OGPoyraz Jun 5, 2026
7c27231
fix: use accountOverride for wallet address in fiat-quotes
OGPoyraz Jun 5, 2026
6e97dae
fix: skip source balance check for relay execute flow
OGPoyraz Jun 5, 2026
57a14f2
fix: bump fee reserve multiplier to 1.2 and move rate drift check to …
OGPoyraz Jun 5, 2026
0f35336
Merge branch 'main' into ogp/fiat-money-account-deposit-fix
OGPoyraz Jun 5, 2026
d4d107c
fix: add missing 67.0.0 link in transaction-controller changelog
OGPoyraz Jun 5, 2026
4808a41
Merge branch 'main' into ogp/fiat-money-account-deposit-fix
OGPoyraz Jun 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/transaction-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added required `AllowedActions`: `GasFeeController:fetchGasFeeEstimates`, `KeyringController:signTransaction`, `NetworkController:getEIP1559Compatibility`, `NetworkController:getNetworkClientRegistry`, `NetworkController:getState`
- Removed resubmit logic from `PendingTransactionTracker`

### Fixed

- Fix EIP-7702 batch transactions not forwarding native value from nested calls ([#8987](https://github.com/MetaMask/core/pull/8987))
- `generateEIP7702BatchTransaction` now sums the `value` fields of all nested calls and sets the total as the top-level transaction value. Previously the top-level value was always omitted, causing batches that include native token transfers (e.g. POL swaps) to revert.

## [66.0.1]

### Changed
Expand Down
41 changes: 41 additions & 0 deletions packages/transaction-controller/src/utils/eip7702.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ describe('EIP-7702 Utils', () => {
expect(result).toStrictEqual({
data: DATA_MOCK,
to: ADDRESS_MOCK,
value: '0x13568',
});
});

Expand Down Expand Up @@ -665,6 +666,7 @@ describe('EIP-7702 Utils', () => {
expect(result).toStrictEqual({
data: DATA_MOCK,
to: ADDRESS_MOCK,
value: '0x13568',
});
});

Expand All @@ -689,9 +691,47 @@ describe('EIP-7702 Utils', () => {
expect(result).toStrictEqual({
data: DATA_MOCK,
to: ADDRESS_MOCK,
value: '0x13568',
});
});

it('omits value when all nested calls have zero value', () => {
const result = generateEIP7702BatchTransaction(ADDRESS_MOCK, [
{
data: '0x1234',
to: ADDRESS_2_MOCK,
value: '0x0',
},
{
data: '0x9abc',
to: ADDRESS_3_MOCK,
},
]);

expect(result).toStrictEqual({
data: expect.any(String),
to: ADDRESS_MOCK,
});
expect(result.value).toBeUndefined();
});

it('sums values from nested calls with mixed zero and non-zero values', () => {
const result = generateEIP7702BatchTransaction(ADDRESS_MOCK, [
{
data: '0x1234',
to: ADDRESS_2_MOCK,
value: '0x0',
},
{
data: '0x9abc',
to: ADDRESS_3_MOCK,
value: '0x5678',
},
]);

expect(result.value).toBe('0x5678');
});

it('uses non-atomic mode when atomic is false', () => {
const result = generateEIP7702BatchTransaction(
ADDRESS_MOCK,
Expand All @@ -713,6 +753,7 @@ describe('EIP-7702 Utils', () => {
expect(result).toStrictEqual({
data: DATA_NON_ATOMIC_MOCK,
to: ADDRESS_MOCK,
value: '0x13568',
});
});
});
Expand Down
6 changes: 6 additions & 0 deletions packages/transaction-controller/src/utils/eip7702.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,9 +205,15 @@ export function generateEIP7702BatchTransaction(

log('Transaction data', data);

const totalValue = transactions.reduce(
(sum, tx) => sum + BigInt(tx.value ?? '0x0'),
BigInt(0),
);

return {
data,
to: from,
...(totalValue > BigInt(0) ? { value: toHex(totalValue) } : {}),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't how EIP-7702 works. The to is the from so you'd just be passing value to yourself, plus each call is empowered to pass value as it uses a solidity call that includes its own data, to, and value.

What issue were you trying to fix?

};
}

Expand Down
6 changes: 6 additions & 0 deletions packages/transaction-pay-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Adding processing for postQuote transactions with paymentOverride defined ([#8967](https://github.com/MetaMask/core/pull/8967))
- Add optional `getAmountData` callback and `TransactionPayController:getAmountData` messenger action for client-side nested calldata re-encoding ([#8987](https://github.com/MetaMask/core/pull/8987))
- Add `@metamask/keyring-controller` `^26.0.0` as a dependency ([#8972](https://github.com/MetaMask/core/pull/8972))
- The package was already imported at runtime by `src/strategy/relay/hyperliquid-withdraw.ts` but wasn't declared in `package.json`; this PR fixes the omission.

### Changed

- Fiat submit now uses a three-phase relay flow with fee-as-buffer strategy after on-ramp settlement, and simple deposits (Perps, Predict) skip to a single EXACT_INPUT relay quote for cheaper fees ([#8987](https://github.com/MetaMask/core/pull/8987))
- Fiat quote submission now treats the provider code (e.g. `transak-native`) as the canonical form when resolving the provider from a ramps quote, while continuing to accept the legacy path form (e.g. `/providers/transak-native`) for backwards compatibility ([#9004](https://github.com/MetaMask/core/pull/9004))
- Live token balance queries now respect the `confirmations_pay_extended.excludeChainIdsFromInfura` feature flag, skipping the Infura endpoint preference for excluded chains ([#8992](https://github.com/MetaMask/core/pull/8992))
- Bump `@metamask/assets-controllers` from `^108.3.0` to `^108.5.0` ([#8981](https://github.com/MetaMask/core/pull/8981), [#8999](https://github.com/MetaMask/core/pull/8999))
- Bump `@metamask/assets-controller` from `^8.0.2` to `^8.3.2` ([#8981](https://github.com/MetaMask/core/pull/8981), [#8985](https://github.com/MetaMask/core/pull/8985), [#8999](https://github.com/MetaMask/core/pull/8999))

### Fixed

- Fix fiat `moneyAccountDeposit` failing after on-ramp settlement by adding `getAmountData` callback for calldata re-encoding and correcting wallet address, quote amount, and slippage validation ([#8987](https://github.com/MetaMask/core/pull/8987))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this not the same as in changed, could we combine into one?

- Bump `@metamask/remote-feature-flag-controller` from `^4.2.1` to `^4.2.2` ([#8986](https://github.com/MetaMask/core/pull/8986))
- Bump `@metamask/ramps-controller` from `^14.1.0` to `^14.1.1` ([#8989](https://github.com/MetaMask/core/pull/8989))
- Bump `@metamask/bridge-status-controller` from `^72.0.0` to `^72.0.2` ([#8990](https://github.com/MetaMask/core/pull/8990), [#8999](https://github.com/MetaMask/core/pull/8999))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ export type TransactionPayControllerGetDelegationTransactionAction = {
* @param args - The arguments forwarded to the {@link GetPaymentOverrideDataCallback}.
* @returns A promise resolving to the additional transactions array.
*/
export type TransactionPayControllerGetAmountDataAction = {
type: `TransactionPayController:getAmountData`;
handler: TransactionPayController['getAmountData'];
};

export type TransactionPayControllerGetPaymentOverrideDataAction = {
type: `TransactionPayController:getPaymentOverrideData`;
handler: TransactionPayController['getPaymentOverrideData'];
Expand Down Expand Up @@ -128,6 +133,7 @@ export type TransactionPayControllerMethodActions =
| TransactionPayControllerUpdatePaymentTokenAction
| TransactionPayControllerUpdateFiatPaymentAction
| TransactionPayControllerGetDelegationTransactionAction
| TransactionPayControllerGetAmountDataAction
| TransactionPayControllerGetPaymentOverrideDataAction
| TransactionPayControllerGetStrategyAction
| TransactionPayControllerPolymarketGetDepositWalletAddressAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,51 @@ describe('TransactionPayController', () => {
});
});

describe('getAmountData', () => {
it('delegates to the callback', async () => {
const resultMock = {
updates: [{ nestedTransactionIndex: 0, data: '0xabc' as const }],
};
const getAmountDataMock = jest.fn().mockResolvedValue(resultMock);

new TransactionPayController({
getAmountData: getAmountDataMock,
getDelegationTransaction: jest.fn(),
messenger,
});

const requestMock = {
amount: '5000000',
transaction: TRANSACTION_META_MOCK,
};

const result = await messenger.call(
'TransactionPayController:getAmountData',
requestMock,
);

expect(getAmountDataMock).toHaveBeenCalledWith(requestMock);
expect(result).toStrictEqual(resultMock);
});

it('returns empty updates when no callback is configured', async () => {
new TransactionPayController({
getDelegationTransaction: jest.fn(),
messenger,
});

const result = await messenger.call(
'TransactionPayController:getAmountData',
{
amount: '5000000',
transaction: TRANSACTION_META_MOCK,
},
);

expect(result).toStrictEqual({ updates: [] });
});
});

describe('polymarket callbacks', () => {
const EOA_MOCK = '0x1111111111111111111111111111111111111111' as Hex;
const DEPOSIT_WALLET_MOCK =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
import { QuoteRefresher } from './helpers/QuoteRefresher';
import { deriveFiatAssetForFiatPayment } from './strategy/fiat/utils';
import type {
GetAmountDataCallback,
GetDelegationTransactionCallback,
GetPaymentOverrideDataCallback,
PolymarketCallbacks,
Expand All @@ -36,6 +37,7 @@ import {
} from './utils/transaction';

const MESSENGER_EXPOSED_METHODS = [
'getAmountData',
'getDelegationTransaction',
'getPaymentOverrideData',
'getStrategy',
Expand Down Expand Up @@ -64,6 +66,8 @@ export class TransactionPayController extends BaseController<
TransactionPayControllerState,
TransactionPayControllerMessenger
> {
readonly #getAmountData?: GetAmountDataCallback;

readonly #getDelegationTransaction: GetDelegationTransactionCallback;

readonly #getPaymentOverrideData?: GetPaymentOverrideDataCallback;
Expand All @@ -79,6 +83,7 @@ export class TransactionPayController extends BaseController<
readonly #polymarket?: PolymarketCallbacks;

constructor({
getAmountData,
getDelegationTransaction,
getPaymentOverrideData,
getStrategy,
Expand All @@ -94,6 +99,7 @@ export class TransactionPayController extends BaseController<
state: { ...getDefaultState(), ...state },
});

this.#getAmountData = getAmountData;
this.#getDelegationTransaction = getDelegationTransaction;
this.#getPaymentOverrideData = getPaymentOverrideData;
this.#getStrategy = getStrategy;
Expand Down Expand Up @@ -233,6 +239,12 @@ export class TransactionPayController extends BaseController<
* @param args - The arguments forwarded to the {@link GetPaymentOverrideDataCallback}.
* @returns A promise resolving to the additional transactions array.
*/
getAmountData(
...args: Parameters<GetAmountDataCallback>
): ReturnType<GetAmountDataCallback> {
return this.#getAmountData?.(...args) ?? Promise.resolve({ updates: [] });
}

getPaymentOverrideData(
...args: Parameters<GetPaymentOverrideDataCallback>
): ReturnType<GetPaymentOverrideDataCallback> {
Expand Down
4 changes: 4 additions & 0 deletions packages/transaction-pay-controller/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export type {
GetAmountDataCallback,
GetAmountDataRequest,
GetAmountDataResponse,
GetPaymentOverrideDataRequest,
GetPaymentOverrideDataResponse,
TransactionConfig,
Expand All @@ -23,6 +26,7 @@ export type {
UpdatePaymentTokenRequest,
} from './types';
export type {
TransactionPayControllerGetAmountDataAction,
TransactionPayControllerGetDelegationTransactionAction,
TransactionPayControllerGetStrategyAction,
TransactionPayControllerPolymarketGetDepositWalletAddressAction,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ export async function getFiatQuotes(
const state = messenger.call('TransactionPayController:getState');
const transactionData = state.transactionData[transactionId];
const amountFiat = transactionData?.fiatPayment?.amountFiat;
const walletAddress = transaction.txParams.from as Hex;
const walletAddress = (transactionData?.accountOverride ??
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still not needed right as quotes.ts does this in the request?

transaction.txParams.from) as Hex;
const requiredTokens = getRequiredTokens(transactionData?.tokens);
const fiatAsset = deriveFiatAssetForFiatPayment(transaction, messenger);

Expand Down
Loading
Loading