From 4fddaecb311ffa3e46189c7eeeb59bc23fb973f6 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Wed, 20 May 2026 17:22:29 +0530 Subject: [PATCH 01/13] feat: add transaction data property useMoneyAccount --- .../src/TransactionPayController.test.ts | 12 ++++++++++++ .../src/TransactionPayController.ts | 2 ++ packages/transaction-pay-controller/src/types.ts | 6 ++++++ 3 files changed, 20 insertions(+) diff --git a/packages/transaction-pay-controller/src/TransactionPayController.test.ts b/packages/transaction-pay-controller/src/TransactionPayController.test.ts index 76d3ecd6d6..734e3ebeae 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.test.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.test.ts @@ -206,6 +206,18 @@ describe('TransactionPayController', () => { ).toBe(true); }); + it('updates useMoneyAccount in state', () => { + const controller = createController(); + + controller.setTransactionConfig(TRANSACTION_ID_MOCK, (config) => { + config.useMoneyAccount = true; + }); + + expect( + controller.state.transactionData[TRANSACTION_ID_MOCK].useMoneyAccount, + ).toBe(true); + }); + it('triggers source amounts and quotes update when only isPostQuote changes', () => { const controller = createController(); diff --git a/packages/transaction-pay-controller/src/TransactionPayController.ts b/packages/transaction-pay-controller/src/TransactionPayController.ts index 70b4b3f8d6..f851b686d9 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.ts @@ -140,6 +140,7 @@ export class TransactionPayController extends BaseController< isPolymarketDepositWallet: transactionData.isPolymarketDepositWallet, refundTo: transactionData.refundTo, accountOverride: transactionData.accountOverride, + useMoneyAccount: transactionData.useMoneyAccount, }; const previousAccountOverride = config.accountOverride; @@ -153,6 +154,7 @@ export class TransactionPayController extends BaseController< transactionData.isPolymarketDepositWallet = config.isPolymarketDepositWallet; transactionData.refundTo = config.refundTo; + transactionData.useMoneyAccount = config.useMoneyAccount; if ( !config.isPostQuote && diff --git a/packages/transaction-pay-controller/src/types.ts b/packages/transaction-pay-controller/src/types.ts index 8844400512..b46019fc57 100644 --- a/packages/transaction-pay-controller/src/types.ts +++ b/packages/transaction-pay-controller/src/types.ts @@ -140,6 +140,9 @@ export type TransactionConfig = { * When `isPostQuote` is false, it provides the funds and pays for gas. */ accountOverride?: Hex; + + /** Whether to use the Money Account (Money Keyring) as the payment source. */ + useMoneyAccount?: boolean; }; /** Callback to update transaction config. */ @@ -246,6 +249,9 @@ export type TransactionData = { */ accountOverride?: Hex; + /** Whether to use the Money Account (Money Keyring) as the payment source. */ + useMoneyAccount?: boolean; + /** * Token selected for the transaction. * - For standard flows (isPostQuote=false): This is the SOURCE/payment token From a1258e6fdc606fe2335f899eea6277514cdd166b Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Wed, 20 May 2026 17:25:38 +0530 Subject: [PATCH 02/13] update changelog --- packages/transaction-pay-controller/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index fc2ba23cda..92fc201ea4 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `useMoneyAccount` property to `TransactionConfig` and `TransactionData`, settable via `setTransactionConfig` ([#8858](https://github.com/MetaMask/core/pull/8858)) + ## [22.6.1] ### Changed From 3ecc08618163227361ff966c41beaffd5ff2b15b Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Thu, 21 May 2026 11:05:46 +0530 Subject: [PATCH 03/13] feat: support for money account source for MM pay deposit transactions --- .../transaction-pay-controller/CHANGELOG.md | 1 + ...actionPayController-method-action-types.ts | 17 +++ .../src/TransactionPayController.test.ts | 39 +++++++ .../src/TransactionPayController.ts | 23 ++++ .../src/strategy/relay/relay-quotes.test.ts | 107 ++++++++++++++++++ .../src/strategy/relay/relay-quotes.ts | 90 ++++++++++++++- .../src/tests/messenger-mock.ts | 11 ++ .../transaction-pay-controller/src/types.ts | 19 ++++ .../src/utils/quotes.ts | 9 ++ 9 files changed, 314 insertions(+), 2 deletions(-) diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index 92fc201ea4..e4f29a8f8d 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `useMoneyAccount` property to `TransactionConfig` and `TransactionData`, settable via `setTransactionConfig` ([#8858](https://github.com/MetaMask/core/pull/8858)) +- Add `getMoneyAccountTransactions` callback to `TransactionPayControllerOptions`; when `useMoneyAccount` is true on a transaction, the publish hook calls this callback and passes the resulting `additionalTransactions` to the strategy's `execute` request, ordered before or after the quote batch based on `isPostQuote` ([#8858](https://github.com/MetaMask/core/pull/8858)) ## [22.6.1] diff --git a/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts b/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts index 8afd1b0559..55a173aff4 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts @@ -49,6 +49,22 @@ export type TransactionPayControllerUpdateFiatPaymentAction = { handler: TransactionPayController['updateFiatPayment']; }; +/** + * Returns additional transactions for the money account flow. + * + * Delegates to the client-supplied callback. Called during quote execution + * when `useMoneyAccount` is true. Returns an empty array when no callback + * is configured. + * + * @param args - The arguments forwarded to the {@link GetMoneyAccountTransactionsCallback}, + * containing the transaction ID. + * @returns A promise resolving to the additional transactions array. + */ +export type TransactionPayControllerGetMoneyAccountTransactionsAction = { + type: `TransactionPayController:getMoneyAccountTransactions`; + handler: TransactionPayController['getMoneyAccountTransactions']; +}; + /** * Gets the delegation transaction for a given transaction. * @@ -113,6 +129,7 @@ export type TransactionPayControllerMethodActions = | TransactionPayControllerUpdatePaymentTokenAction | TransactionPayControllerUpdateFiatPaymentAction | TransactionPayControllerGetDelegationTransactionAction + | TransactionPayControllerGetMoneyAccountTransactionsAction | TransactionPayControllerGetStrategyAction | TransactionPayControllerPolymarketGetDepositWalletAddressAction | TransactionPayControllerPolymarketSubmitDepositWalletBatchAction; diff --git a/packages/transaction-pay-controller/src/TransactionPayController.test.ts b/packages/transaction-pay-controller/src/TransactionPayController.test.ts index 734e3ebeae..4bec5185d3 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.test.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.test.ts @@ -469,6 +469,45 @@ describe('TransactionPayController', () => { }); }); + describe('getMoneyAccountTransactions', () => { + it('delegates to the callback', async () => { + const txMock = { from: '0xabc', to: '0xdef' }; + const getMoneyAccountTransactionsMock = jest + .fn() + .mockResolvedValue([txMock]); + + new TransactionPayController({ + getDelegationTransaction: jest.fn(), + getMoneyAccountTransactions: getMoneyAccountTransactionsMock, + messenger, + }); + + const result = await messenger.call( + 'TransactionPayController:getMoneyAccountTransactions', + TRANSACTION_ID_MOCK, + ); + + expect(getMoneyAccountTransactionsMock).toHaveBeenCalledWith( + TRANSACTION_ID_MOCK, + ); + expect(result).toStrictEqual([txMock]); + }); + + it('returns empty array when no callback is configured', async () => { + new TransactionPayController({ + getDelegationTransaction: jest.fn(), + messenger, + }); + + const result = await messenger.call( + 'TransactionPayController:getMoneyAccountTransactions', + TRANSACTION_ID_MOCK, + ); + + expect(result).toStrictEqual([]); + }); + }); + describe('polymarket callbacks', () => { const EOA_MOCK = '0x1111111111111111111111111111111111111111' as Hex; const DEPOSIT_WALLET_MOCK = diff --git a/packages/transaction-pay-controller/src/TransactionPayController.ts b/packages/transaction-pay-controller/src/TransactionPayController.ts index f851b686d9..85920508a3 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.ts @@ -15,6 +15,7 @@ import { QuoteRefresher } from './helpers/QuoteRefresher'; import { deriveFiatAssetForFiatPayment } from './strategy/fiat/utils'; import type { GetDelegationTransactionCallback, + GetMoneyAccountTransactionsCallback, PolymarketCallbacks, TransactionConfigCallback, TransactionData, @@ -36,6 +37,7 @@ import { const MESSENGER_EXPOSED_METHODS = [ 'getDelegationTransaction', + 'getMoneyAccountTransactions', 'getStrategy', 'polymarketGetDepositWalletAddress', 'polymarketSubmitDepositWalletBatch', @@ -64,6 +66,8 @@ export class TransactionPayController extends BaseController< > { readonly #getDelegationTransaction: GetDelegationTransactionCallback; + readonly #getMoneyAccountTransactions?: GetMoneyAccountTransactionsCallback; + readonly #getStrategy?: ( transaction: TransactionMeta, ) => TransactionPayStrategy; @@ -76,6 +80,7 @@ export class TransactionPayController extends BaseController< constructor({ getDelegationTransaction, + getMoneyAccountTransactions, getStrategy, getStrategies, messenger, @@ -90,6 +95,7 @@ export class TransactionPayController extends BaseController< }); this.#getDelegationTransaction = getDelegationTransaction; + this.#getMoneyAccountTransactions = getMoneyAccountTransactions; this.#getStrategy = getStrategy; this.#getStrategies = getStrategies; this.#polymarket = polymarket; @@ -217,6 +223,23 @@ export class TransactionPayController extends BaseController< return this.#getDelegationTransaction(...args); } + /** + * Returns additional transactions for the money account flow. + * + * Delegates to the client-supplied {@link GetMoneyAccountTransactionsCallback}. + * Called during quote execution when `useMoneyAccount` is true on the transaction. + * Returns an empty array when no callback is configured. + * + * @param args - The arguments forwarded to the {@link GetMoneyAccountTransactionsCallback}, + * containing the transaction ID. + * @returns A promise resolving to the additional transactions array. + */ + getMoneyAccountTransactions( + ...args: Parameters + ): ReturnType { + return this.#getMoneyAccountTransactions?.(...args) ?? Promise.resolve([]); + } + /** * Gets the preferred strategy for a transaction. * diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index 9439faf4cc..4a37ed6112 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -183,6 +183,7 @@ describe('Relay Quotes Utils', () => { getDelegationTransactionMock, getGasFeeTokensMock, getKeyringControllerStateMock, + getMoneyAccountTransactionsMock, getRemoteFeatureFlagControllerStateMock, polymarketGetDepositWalletAddressMock, } = getMessengerMock(); @@ -3375,6 +3376,112 @@ describe('Relay Quotes Utils', () => { }); }); + describe('money account deposit step injection (useMoneyAccount)', () => { + const DEPOSIT_TX_MOCK = { + from: FROM_MOCK, + to: '0xmoneyaccount' as Hex, + data: '0xdeposit' as Hex, + value: '0x0', + gas: '30000', + maxFeePerGas: '1000000000', + maxPriorityFeePerGas: '2000000000', + }; + + beforeEach(() => { + successfulFetchMock.mockResolvedValue({ + ok: true, + json: async () => QUOTE_MOCK, + } as never); + + estimateGasBatchMock.mockResolvedValue({ + gasLimits: [30000, 21000], + totalGasLimit: 51000, + }); + }); + + it('prepends deposit step before relay steps for standard (non-post-quote) flow', async () => { + getMoneyAccountTransactionsMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [{ ...QUOTE_REQUEST_MOCK, useMoneyAccount: true }], + transaction: TRANSACTION_META_MOCK, + }); + + const txSteps = quote.original.steps.filter( + (s): s is RelayTransactionStep => s.kind === 'transaction', + ); + expect(txSteps[0].id).toBe('money-account-deposit'); + expect(txSteps[0].items[0].data.to).toBe(DEPOSIT_TX_MOCK.to); + expect(txSteps[1].id).toBe(STEP_MOCK.id); + }); + + it('appends deposit step after relay steps for post-quote flow', async () => { + getMoneyAccountTransactionsMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [ + { ...QUOTE_REQUEST_MOCK, useMoneyAccount: true, isPostQuote: true }, + ], + transaction: TRANSACTION_META_MOCK, + }); + + const txSteps = quote.original.steps.filter( + (s): s is RelayTransactionStep => s.kind === 'transaction', + ); + expect(txSteps[0].id).toBe(STEP_MOCK.id); + expect(txSteps[1].id).toBe('money-account-deposit'); + }); + + it('does not inject when useMoneyAccount is false', async () => { + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [QUOTE_REQUEST_MOCK], + transaction: TRANSACTION_META_MOCK, + }); + + expect( + quote.original.steps.every((s) => s.id !== 'money-account-deposit'), + ).toBe(true); + expect(getMoneyAccountTransactionsMock).not.toHaveBeenCalled(); + }); + + it('does not inject when callback returns empty array', async () => { + getMoneyAccountTransactionsMock.mockResolvedValue([]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [{ ...QUOTE_REQUEST_MOCK, useMoneyAccount: true }], + transaction: TRANSACTION_META_MOCK, + }); + + expect(quote.original.steps).toHaveLength(1); + expect(quote.original.steps[0].id).toBe(STEP_MOCK.id); + }); + + it('uses sourceChainId to set chainId on deposit step items', async () => { + getMoneyAccountTransactionsMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [{ ...QUOTE_REQUEST_MOCK, useMoneyAccount: true }], + transaction: TRANSACTION_META_MOCK, + }); + + const depositStep = quote.original.steps.find( + (s) => s.id === 'money-account-deposit', + ) as RelayTransactionStep; + // QUOTE_REQUEST_MOCK.sourceChainId is '0x1' → chainId 1 + expect(depositStep.items[0].data.chainId).toBe(1); + }); + }); + describe('gas buffer support', () => { it('applies buffer to single transaction gas estimate', async () => { const quoteMock = cloneDeep(QUOTE_MOCK); diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index 08077cd823..6961798028 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -2,7 +2,10 @@ import { Interface } from '@ethersproject/abi'; import { toHex } from '@metamask/controller-utils'; -import type { TransactionMeta } from '@metamask/transaction-controller'; +import type { + TransactionMeta, + TransactionParams, +} from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; import { createModuleLogger } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; @@ -275,7 +278,11 @@ async function getSingleQuote( log('Fetched relay quote', quote); - return await normalizeQuote(quote, request, fullRequest); + const quoteWithDeposits = request.useMoneyAccount + ? await injectMoneyAccountDepositSteps(quote, request, fullRequest) + : quote; + + return await normalizeQuote(quoteWithDeposits, request, fullRequest); } catch (error) { log('Error fetching relay quote', error); throw error; @@ -1015,6 +1022,85 @@ function getSubsidizedFeeAmountUsd(quote: RelayQuote): BigNumber { return isSubsidizedStablecoin ? amountFormatted : amountUsd; } +/** + * Fetches deposit transactions from the money account callback and injects + * them into the relay quote's steps so they are submitted alongside the relay + * transactions and included in gas estimation. + * + * For standard flows (`isPostQuote` false) the deposit step is prepended so it + * executes before the relay bridge transaction. For post-quote flows it is + * appended so it executes after. + * + * @param quote - Relay quote to mutate in place. + * @param request - Quote request, used to determine ordering and chain ID. + * @param fullRequest - Full quotes request, provides messenger and transaction ID. + */ +async function injectMoneyAccountDepositSteps( + quote: RelayQuote, + request: QuoteRequest, + fullRequest: PayStrategyGetQuotesRequest, +): Promise { + const { messenger, transaction } = fullRequest; + + const depositTxs = await messenger.call( + 'TransactionPayController:getMoneyAccountTransactions', + transaction.id, + ); + + if (!depositTxs.length) { + return quote; + } + + const depositStep = buildDepositStep(depositTxs, request.sourceChainId); + + const steps = request.isPostQuote + ? [...quote.steps, depositStep] + : [depositStep, ...quote.steps]; + + log('Injected money account deposit step', { + transactionId: transaction.id, + isPostQuote: request.isPostQuote, + depositTxCount: depositTxs.length, + }); + + return { ...quote, steps }; +} + +/** + * Converts an array of TransactionParams into a single RelayTransactionStep + * so they can be injected into a relay quote's steps array. + * + * @param txParams - Deposit transactions from the money account callback. + * @param sourceChainId - Hex chain ID of the source network. + * @returns A relay transaction step wrapping the deposit transactions. + */ +function buildDepositStep( + txParams: TransactionParams[], + sourceChainId: Hex, +): RelayTransactionStep { + const chainId = parseInt(sourceChainId, 16); + + return { + id: 'money-account-deposit', + kind: 'transaction', + requestId: 'money-account-deposit', + items: txParams.map((params) => ({ + check: { endpoint: '', method: 'GET' as const }, + status: 'incomplete' as const, + data: { + chainId, + data: (params.data as Hex) ?? '0x', + from: params.from as Hex, + gas: params.gas as string | undefined, + maxFeePerGas: (params.maxFeePerGas as string) ?? '0x0', + maxPriorityFeePerGas: (params.maxPriorityFeePerGas as string) ?? '0x0', + to: params.to as Hex, + value: params.value as string | undefined, + }, + })), + }; +} + function isStablecoin(chainId: string, tokenAddress: string): boolean { return Boolean( STABLECOINS[chainId as Hex]?.includes(tokenAddress.toLowerCase() as Hex), diff --git a/packages/transaction-pay-controller/src/tests/messenger-mock.ts b/packages/transaction-pay-controller/src/tests/messenger-mock.ts index 1931aa202a..ca049ce39c 100644 --- a/packages/transaction-pay-controller/src/tests/messenger-mock.ts +++ b/packages/transaction-pay-controller/src/tests/messenger-mock.ts @@ -29,6 +29,7 @@ import type { TransactionControllerUpdateTransactionAction } from '@metamask/tra import type { TransactionPayControllerMessenger } from '..'; import type { TransactionPayControllerGetDelegationTransactionAction, + TransactionPayControllerGetMoneyAccountTransactionsAction, TransactionPayControllerGetStrategyAction, TransactionPayControllerPolymarketGetDepositWalletAddressAction, TransactionPayControllerPolymarketSubmitDepositWalletBatchAction, @@ -120,6 +121,10 @@ export function getMessengerMock({ TransactionPayControllerGetDelegationTransactionAction['handler'] > = jest.fn(); + const getMoneyAccountTransactionsMock: jest.MockedFn< + TransactionPayControllerGetMoneyAccountTransactionsAction['handler'] + > = jest.fn().mockResolvedValue([]); + const polymarketGetDepositWalletAddressMock: jest.MockedFn< TransactionPayControllerPolymarketGetDepositWalletAddressAction['handler'] > = jest.fn(); @@ -255,6 +260,11 @@ export function getMessengerMock({ getDelegationTransactionMock, ); + messenger.registerActionHandler( + 'TransactionPayController:getMoneyAccountTransactions', + getMoneyAccountTransactionsMock, + ); + messenger.registerActionHandler( 'TransactionPayController:polymarketGetDepositWalletAddress', polymarketGetDepositWalletAddressMock, @@ -306,6 +316,7 @@ export function getMessengerMock({ getControllerStateMock, getCurrencyRateControllerStateMock, getDelegationTransactionMock, + getMoneyAccountTransactionsMock, getGasFeeControllerStateMock, getGasFeeTokensMock, getKeyringControllerStateMock, diff --git a/packages/transaction-pay-controller/src/types.ts b/packages/transaction-pay-controller/src/types.ts index b46019fc57..04927b98b3 100644 --- a/packages/transaction-pay-controller/src/types.ts +++ b/packages/transaction-pay-controller/src/types.ts @@ -54,6 +54,7 @@ import type { TransactionControllerStateChangeEvent, TransactionControllerUpdateTransactionAction, TransactionMeta, + TransactionParams, } from '@metamask/transaction-controller'; import type { Hex, Json } from '@metamask/utils'; import type { Draft } from 'immer'; @@ -148,6 +149,15 @@ export type TransactionConfig = { /** Callback to update transaction config. */ export type TransactionConfigCallback = (config: TransactionConfig) => void; +/** + * Callback invoked during quote execution when `useMoneyAccount` is true. + * Returns additional transactions to be submitted alongside the quote, + * ordered before or after the quote batch depending on `isPostQuote`. + */ +export type GetMoneyAccountTransactionsCallback = ( + transactionId: string, +) => Promise; + /** Callback to update fiat payment state. */ export type TransactionFiatPaymentCallback = ( fiatPayment: TransactionFiatPayment, @@ -199,6 +209,12 @@ export type TransactionPayControllerOptions = { /** Callbacks for the Polymarket relayer; required only for the Polymarket deposit-wallet flow. */ polymarket?: PolymarketCallbacks; + /** + * Optional callback invoked during quote execution when `useMoneyAccount` is true. + * Returns additional transactions to be submitted alongside the quote batch. + */ + getMoneyAccountTransactions?: GetMoneyAccountTransactionsCallback; + /** Initial state of the controller. */ state?: Partial; }; @@ -420,6 +436,9 @@ export type QuoteRequest = { /** Whether the source of funds is a Polymarket deposit wallet. */ isPolymarketDepositWallet?: boolean; + /** Whether the money account is the source of funds for this quote. */ + useMoneyAccount?: boolean; + /** * Optional address to receive refunds if the quote provider transaction fails. * When set, overrides the default refund recipient (EOA) in the quote diff --git a/packages/transaction-pay-controller/src/utils/quotes.ts b/packages/transaction-pay-controller/src/utils/quotes.ts index ba2ec2afd7..0ae0ccc825 100644 --- a/packages/transaction-pay-controller/src/utils/quotes.ts +++ b/packages/transaction-pay-controller/src/utils/quotes.ts @@ -86,6 +86,7 @@ export async function updateQuotes( isPostQuote, isHyperliquidSource, isPolymarketDepositWallet, + useMoneyAccount, paymentToken: originalPaymentToken, refundTo, sourceAmounts, @@ -122,6 +123,7 @@ export async function updateQuotes( isPostQuote, isHyperliquidSource, isPolymarketDepositWallet, + useMoneyAccount, paymentToken, refundTo, sourceAmounts, @@ -339,6 +341,7 @@ function buildQuoteRequests({ isPostQuote, isHyperliquidSource, isPolymarketDepositWallet, + useMoneyAccount, paymentToken, refundTo, sourceAmounts, @@ -350,6 +353,7 @@ function buildQuoteRequests({ isPostQuote?: boolean; isHyperliquidSource?: boolean; isPolymarketDepositWallet?: boolean; + useMoneyAccount?: boolean; paymentToken: TransactionPaymentToken | undefined; refundTo?: Hex; sourceAmounts: TransactionPaySourceAmount[] | undefined; @@ -366,6 +370,7 @@ function buildQuoteRequests({ isMaxAmount, isHyperliquidSource, isPolymarketDepositWallet, + useMoneyAccount, destinationToken: paymentToken, refundTo, sourceAmounts, @@ -382,6 +387,7 @@ function buildQuoteRequests({ return { from, isMaxAmount, + useMoneyAccount, sourceBalanceRaw: paymentToken.balanceRaw, sourceTokenAmount: sourceAmount.sourceAmountRaw, sourceChainId: paymentToken.chainId, @@ -420,6 +426,7 @@ function buildPostQuoteRequests({ isMaxAmount, isHyperliquidSource, isPolymarketDepositWallet, + useMoneyAccount, destinationToken, refundTo, sourceAmounts, @@ -429,6 +436,7 @@ function buildPostQuoteRequests({ isMaxAmount: boolean; isHyperliquidSource?: boolean; isPolymarketDepositWallet?: boolean; + useMoneyAccount?: boolean; destinationToken: TransactionPaymentToken; refundTo?: Hex; sourceAmounts: TransactionPaySourceAmount[] | undefined; @@ -459,6 +467,7 @@ function buildPostQuoteRequests({ isPostQuote: true, isHyperliquidSource, isPolymarketDepositWallet, + useMoneyAccount, refundTo, sourceBalanceRaw: sourceAmount.sourceBalanceRaw, sourceTokenAmount: sourceAmount.sourceAmountRaw, From 886efa9e41c767065eb4ae49f4c80d87129bd09b Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Fri, 22 May 2026 19:28:24 +0530 Subject: [PATCH 04/13] update --- packages/transaction-controller/src/types.ts | 4 +-- ...actionPayController-method-action-types.ts | 14 +++++----- .../src/TransactionPayController.test.ts | 12 ++++----- .../src/TransactionPayController.ts | 26 +++++++++---------- .../src/strategy/relay/relay-quotes.test.ts | 22 ++++++++-------- .../src/strategy/relay/relay-quotes.ts | 19 +++++++------- .../src/tests/messenger-mock.ts | 12 ++++----- .../transaction-pay-controller/src/types.ts | 8 +++--- .../src/utils/quotes.ts | 2 ++ 9 files changed, 61 insertions(+), 58 deletions(-) diff --git a/packages/transaction-controller/src/types.ts b/packages/transaction-controller/src/types.ts index 6694b67dba..162cc7ab5a 100644 --- a/packages/transaction-controller/src/types.ts +++ b/packages/transaction-controller/src/types.ts @@ -791,12 +791,12 @@ export enum TransactionType { lendingWithdraw = 'lendingWithdraw', /** - * A transaction that deposits funds into a money account. + * A transaction that deposits funds when paymentOverride is set. */ moneyAccountDeposit = 'moneyAccountDeposit', /** - * A transaction that withdraws funds from a money account. + * A transaction that withdraws funds when paymentOverride is set. */ moneyAccountWithdraw = 'moneyAccountWithdraw', diff --git a/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts b/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts index 41855f805f..45671cd85a 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts @@ -50,19 +50,19 @@ export type TransactionPayControllerUpdateFiatPaymentAction = { }; /** - * Returns additional transactions for the money account flow. + * Returns additional transactions for the paymentOverride flow. * * Delegates to the client-supplied callback. Called during quote execution - * when `paymentOverride === PaymentOverride.MoneyAccount` on the transaction. Returns an empty array when no callback + * when paymentOverride is true. Returns an empty array when no callback * is configured. * - * @param args - The arguments forwarded to the {@link GetMoneyAccountTransactionsCallback}, + * @param args - The arguments forwarded to the {@link GetPaymentOverrideDataCallback}, * containing the transaction ID. * @returns A promise resolving to the additional transactions array. */ -export type TransactionPayControllerGetMoneyAccountTransactionsAction = { - type: `TransactionPayController:getMoneyAccountTransactions`; - handler: TransactionPayController['getMoneyAccountTransactions']; +export type TransactionPayControllerGetPaymentOverrideDataAction = { + type: `TransactionPayController:getPaymentOverrideData`; + handler: TransactionPayController['getPaymentOverrideData']; }; /** @@ -129,7 +129,7 @@ export type TransactionPayControllerMethodActions = | TransactionPayControllerUpdatePaymentTokenAction | TransactionPayControllerUpdateFiatPaymentAction | TransactionPayControllerGetDelegationTransactionAction - | TransactionPayControllerGetMoneyAccountTransactionsAction + | TransactionPayControllerGetPaymentOverrideDataAction | TransactionPayControllerGetStrategyAction | TransactionPayControllerPolymarketGetDepositWalletAddressAction | TransactionPayControllerPolymarketSubmitDepositWalletBatchAction; diff --git a/packages/transaction-pay-controller/src/TransactionPayController.test.ts b/packages/transaction-pay-controller/src/TransactionPayController.test.ts index f28696444a..e6a572dac7 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.test.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.test.ts @@ -469,25 +469,25 @@ describe('TransactionPayController', () => { }); }); - describe('getMoneyAccountTransactions', () => { + describe('getPaymentOverrideData', () => { it('delegates to the callback', async () => { const txMock = { from: '0xabc', to: '0xdef' }; - const getMoneyAccountTransactionsMock = jest + const getPaymentOverrideDataMock = jest .fn() .mockResolvedValue([txMock]); new TransactionPayController({ getDelegationTransaction: jest.fn(), - getMoneyAccountTransactions: getMoneyAccountTransactionsMock, + getPaymentOverrideData: getPaymentOverrideDataMock, messenger, }); const result = await messenger.call( - 'TransactionPayController:getMoneyAccountTransactions', + 'TransactionPayController:getPaymentOverrideData', TRANSACTION_ID_MOCK, ); - expect(getMoneyAccountTransactionsMock).toHaveBeenCalledWith( + expect(getPaymentOverrideDataMock).toHaveBeenCalledWith( TRANSACTION_ID_MOCK, ); expect(result).toStrictEqual([txMock]); @@ -500,7 +500,7 @@ describe('TransactionPayController', () => { }); const result = await messenger.call( - 'TransactionPayController:getMoneyAccountTransactions', + 'TransactionPayController:getPaymentOverrideData', TRANSACTION_ID_MOCK, ); diff --git a/packages/transaction-pay-controller/src/TransactionPayController.ts b/packages/transaction-pay-controller/src/TransactionPayController.ts index 4c9ae07314..b0d0eafb20 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.ts @@ -15,7 +15,7 @@ import { QuoteRefresher } from './helpers/QuoteRefresher'; import { deriveFiatAssetForFiatPayment } from './strategy/fiat/utils'; import type { GetDelegationTransactionCallback, - GetMoneyAccountTransactionsCallback, + GetPaymentOverrideDataCallback, PolymarketCallbacks, TransactionConfigCallback, TransactionData, @@ -37,7 +37,7 @@ import { const MESSENGER_EXPOSED_METHODS = [ 'getDelegationTransaction', - 'getMoneyAccountTransactions', + 'getPaymentOverrideData', 'getStrategy', 'polymarketGetDepositWalletAddress', 'polymarketSubmitDepositWalletBatch', @@ -66,7 +66,7 @@ export class TransactionPayController extends BaseController< > { readonly #getDelegationTransaction: GetDelegationTransactionCallback; - readonly #getMoneyAccountTransactions?: GetMoneyAccountTransactionsCallback; + readonly #getPaymentOverrideData?: GetPaymentOverrideDataCallback; readonly #getStrategy?: ( transaction: TransactionMeta, @@ -80,7 +80,7 @@ export class TransactionPayController extends BaseController< constructor({ getDelegationTransaction, - getMoneyAccountTransactions, + getPaymentOverrideData, getStrategy, getStrategies, messenger, @@ -95,7 +95,7 @@ export class TransactionPayController extends BaseController< }); this.#getDelegationTransaction = getDelegationTransaction; - this.#getMoneyAccountTransactions = getMoneyAccountTransactions; + this.#getPaymentOverrideData = getPaymentOverrideData; this.#getStrategy = getStrategy; this.#getStrategies = getStrategies; this.#polymarket = polymarket; @@ -224,20 +224,20 @@ export class TransactionPayController extends BaseController< } /** - * Returns additional transactions for the money account flow. + * Returns additional transactions for the paymentOverride flow. * - * Delegates to the client-supplied {@link GetMoneyAccountTransactionsCallback}. - * Called during quote execution when `paymentOverride === PaymentOverride.MoneyAccount` on the transaction. + * Delegates to the client-supplied {@link GetPaymentOverrideDataCallback}. + * Called during quote execution when `paymentOverride` is defined on the transaction. * Returns an empty array when no callback is configured. * - * @param args - The arguments forwarded to the {@link GetMoneyAccountTransactionsCallback}, + * @param args - The arguments forwarded to the {@link GetPaymentOverrideDataCallback}, * containing the transaction ID. * @returns A promise resolving to the additional transactions array. */ - getMoneyAccountTransactions( - ...args: Parameters - ): ReturnType { - return this.#getMoneyAccountTransactions?.(...args) ?? Promise.resolve([]); + getPaymentOverrideData( + ...args: Parameters + ): ReturnType { + return this.#getPaymentOverrideData?.(...args) ?? Promise.resolve([]); } /** diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index c2a2f1049f..f2da52c47a 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -184,7 +184,7 @@ describe('Relay Quotes Utils', () => { getDelegationTransactionMock, getGasFeeTokensMock, getKeyringControllerStateMock, - getMoneyAccountTransactionsMock, + getPaymentOverrideDataMock, getRemoteFeatureFlagControllerStateMock, polymarketGetDepositWalletAddressMock, } = getMessengerMock(); @@ -3377,7 +3377,7 @@ describe('Relay Quotes Utils', () => { }); }); - describe('money account deposit step injection (paymentOverride === PaymentOverride.MoneyAccount)', () => { + describe('paymentOverride deposit step injection (paymentOverride defined)', () => { const DEPOSIT_TX_MOCK = { from: FROM_MOCK, to: '0xmoneyaccount' as Hex, @@ -3401,7 +3401,7 @@ describe('Relay Quotes Utils', () => { }); it('prepends deposit step before relay steps for standard (non-post-quote) flow', async () => { - getMoneyAccountTransactionsMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + getPaymentOverrideDataMock.mockResolvedValue([DEPOSIT_TX_MOCK]); const [quote] = await getRelayQuotes({ accountSupports7702: true, @@ -3411,7 +3411,7 @@ describe('Relay Quotes Utils', () => { }); const txSteps = quote.original.steps.filter( - (s): s is RelayTransactionStep => s.kind === 'transaction', + (step): step is RelayTransactionStep => step.kind === 'transaction', ); expect(txSteps[0].id).toBe('money-account-deposit'); expect(txSteps[0].items[0].data.to).toBe(DEPOSIT_TX_MOCK.to); @@ -3419,7 +3419,7 @@ describe('Relay Quotes Utils', () => { }); it('appends deposit step after relay steps for post-quote flow', async () => { - getMoneyAccountTransactionsMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + getPaymentOverrideDataMock.mockResolvedValue([DEPOSIT_TX_MOCK]); const [quote] = await getRelayQuotes({ accountSupports7702: true, @@ -3431,7 +3431,7 @@ describe('Relay Quotes Utils', () => { }); const txSteps = quote.original.steps.filter( - (s): s is RelayTransactionStep => s.kind === 'transaction', + (step): step is RelayTransactionStep => step.kind === 'transaction', ); expect(txSteps[0].id).toBe(STEP_MOCK.id); expect(txSteps[1].id).toBe('money-account-deposit'); @@ -3446,13 +3446,13 @@ describe('Relay Quotes Utils', () => { }); expect( - quote.original.steps.every((s) => s.id !== 'money-account-deposit'), + quote.original.steps.every((step) => step.id !== 'money-account-deposit'), ).toBe(true); - expect(getMoneyAccountTransactionsMock).not.toHaveBeenCalled(); + expect(getPaymentOverrideDataMock).not.toHaveBeenCalled(); }); it('does not inject when callback returns empty array', async () => { - getMoneyAccountTransactionsMock.mockResolvedValue([]); + getPaymentOverrideDataMock.mockResolvedValue([]); const [quote] = await getRelayQuotes({ accountSupports7702: true, @@ -3466,7 +3466,7 @@ describe('Relay Quotes Utils', () => { }); it('uses sourceChainId to set chainId on deposit step items', async () => { - getMoneyAccountTransactionsMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + getPaymentOverrideDataMock.mockResolvedValue([DEPOSIT_TX_MOCK]); const [quote] = await getRelayQuotes({ accountSupports7702: true, @@ -3476,7 +3476,7 @@ describe('Relay Quotes Utils', () => { }); const depositStep = quote.original.steps.find( - (s) => s.id === 'money-account-deposit', + (step) => step.id === 'money-account-deposit', ) as RelayTransactionStep; // QUOTE_REQUEST_MOCK.sourceChainId is '0x1' → chainId 1 expect(depositStep.items[0].data.chainId).toBe(1); diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index b8a5a87b04..72cd41cd70 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -278,8 +278,8 @@ async function getSingleQuote( log('Fetched relay quote', quote); - const quoteWithDeposits = request.paymentOverride === PaymentOverride.MoneyAccount - ? await injectMoneyAccountDepositSteps(quote, request, fullRequest) + const quoteWithDeposits = request.paymentOverride + ? await injectPaymentOverrideDepositSteps(quote, request, fullRequest) : quote; return await normalizeQuote(quoteWithDeposits, request, fullRequest); @@ -1023,7 +1023,7 @@ function getSubsidizedFeeAmountUsd(quote: RelayQuote): BigNumber { } /** - * Fetches deposit transactions from the money account callback and injects + * Fetches deposit transactions from the paymentOverride callback and injects * them into the relay quote's steps so they are submitted alongside the relay * transactions and included in gas estimation. * @@ -1034,8 +1034,9 @@ function getSubsidizedFeeAmountUsd(quote: RelayQuote): BigNumber { * @param quote - Relay quote to mutate in place. * @param request - Quote request, used to determine ordering and chain ID. * @param fullRequest - Full quotes request, provides messenger and transaction ID. + * @returns The relay quote with deposit steps injected, or the original quote if no deposits are returned. */ -async function injectMoneyAccountDepositSteps( +async function injectPaymentOverrideDepositSteps( quote: RelayQuote, request: QuoteRequest, fullRequest: PayStrategyGetQuotesRequest, @@ -1043,7 +1044,7 @@ async function injectMoneyAccountDepositSteps( const { messenger, transaction } = fullRequest; const depositTxs = await messenger.call( - 'TransactionPayController:getMoneyAccountTransactions', + 'TransactionPayController:getPaymentOverrideData', transaction.id, ); @@ -1057,7 +1058,7 @@ async function injectMoneyAccountDepositSteps( ? [...quote.steps, depositStep] : [depositStep, ...quote.steps]; - log('Injected money account deposit step', { + log('Injected paymentOverride deposit step', { transactionId: transaction.id, isPostQuote: request.isPostQuote, depositTxCount: depositTxs.length, @@ -1070,7 +1071,7 @@ async function injectMoneyAccountDepositSteps( * Converts an array of TransactionParams into a single RelayTransactionStep * so they can be injected into a relay quote's steps array. * - * @param txParams - Deposit transactions from the money account callback. + * @param txParams - Deposit transactions from the paymentOverride callback. * @param sourceChainId - Hex chain ID of the source network. * @returns A relay transaction step wrapping the deposit transactions. */ @@ -1091,11 +1092,11 @@ function buildDepositStep( chainId, data: (params.data as Hex) ?? '0x', from: params.from as Hex, - gas: params.gas as string | undefined, + gas: params.gas, maxFeePerGas: (params.maxFeePerGas as string) ?? '0x0', maxPriorityFeePerGas: (params.maxPriorityFeePerGas as string) ?? '0x0', to: params.to as Hex, - value: params.value as string | undefined, + value: params.value, }, })), }; diff --git a/packages/transaction-pay-controller/src/tests/messenger-mock.ts b/packages/transaction-pay-controller/src/tests/messenger-mock.ts index ca049ce39c..0a0892b0c1 100644 --- a/packages/transaction-pay-controller/src/tests/messenger-mock.ts +++ b/packages/transaction-pay-controller/src/tests/messenger-mock.ts @@ -29,7 +29,7 @@ import type { TransactionControllerUpdateTransactionAction } from '@metamask/tra import type { TransactionPayControllerMessenger } from '..'; import type { TransactionPayControllerGetDelegationTransactionAction, - TransactionPayControllerGetMoneyAccountTransactionsAction, + TransactionPayControllerGetPaymentOverrideDataAction, TransactionPayControllerGetStrategyAction, TransactionPayControllerPolymarketGetDepositWalletAddressAction, TransactionPayControllerPolymarketSubmitDepositWalletBatchAction, @@ -121,8 +121,8 @@ export function getMessengerMock({ TransactionPayControllerGetDelegationTransactionAction['handler'] > = jest.fn(); - const getMoneyAccountTransactionsMock: jest.MockedFn< - TransactionPayControllerGetMoneyAccountTransactionsAction['handler'] + const getPaymentOverrideDataMock: jest.MockedFn< + TransactionPayControllerGetPaymentOverrideDataAction['handler'] > = jest.fn().mockResolvedValue([]); const polymarketGetDepositWalletAddressMock: jest.MockedFn< @@ -261,8 +261,8 @@ export function getMessengerMock({ ); messenger.registerActionHandler( - 'TransactionPayController:getMoneyAccountTransactions', - getMoneyAccountTransactionsMock, + 'TransactionPayController:getPaymentOverrideData', + getPaymentOverrideDataMock, ); messenger.registerActionHandler( @@ -316,7 +316,7 @@ export function getMessengerMock({ getControllerStateMock, getCurrencyRateControllerStateMock, getDelegationTransactionMock, - getMoneyAccountTransactionsMock, + getPaymentOverrideDataMock, getGasFeeControllerStateMock, getGasFeeTokensMock, getKeyringControllerStateMock, diff --git a/packages/transaction-pay-controller/src/types.ts b/packages/transaction-pay-controller/src/types.ts index 0c140ec4f1..119a005a80 100644 --- a/packages/transaction-pay-controller/src/types.ts +++ b/packages/transaction-pay-controller/src/types.ts @@ -154,11 +154,11 @@ export type TransactionConfig = { export type TransactionConfigCallback = (config: TransactionConfig) => void; /** - * Callback invoked during quote execution when `paymentOverride === PaymentOverride.MoneyAccount`. + * Callback invoked during quote execution when `paymentOverride` is defined. * Returns additional transactions to be submitted alongside the quote, * ordered before or after the quote batch depending on `isPostQuote`. */ -export type GetMoneyAccountTransactionsCallback = ( +export type GetPaymentOverrideDataCallback = ( transactionId: string, ) => Promise; @@ -214,10 +214,10 @@ export type TransactionPayControllerOptions = { polymarket?: PolymarketCallbacks; /** - * Optional callback invoked during quote execution when `paymentOverride === PaymentOverride.MoneyAccount`. + * Optional callback invoked during quote execution when `paymentOverride` is defined. * Returns additional transactions to be submitted alongside the quote batch. */ - getMoneyAccountTransactions?: GetMoneyAccountTransactionsCallback; + getPaymentOverrideData?: GetPaymentOverrideDataCallback; /** Initial state of the controller. */ state?: Partial; diff --git a/packages/transaction-pay-controller/src/utils/quotes.ts b/packages/transaction-pay-controller/src/utils/quotes.ts index 867d55cee6..e332b63285 100644 --- a/packages/transaction-pay-controller/src/utils/quotes.ts +++ b/packages/transaction-pay-controller/src/utils/quotes.ts @@ -328,6 +328,7 @@ function clearControllerIfCurrent( * @param request.isHyperliquidSource - Whether the source of funds is HyperLiquid. * @param request.isPolymarketDepositWallet - Whether the source of funds is a Polymarket deposit wallet. * @param request.isPostQuote - Whether this is a post-quote flow. + * @param request.paymentOverride - Optional payment override type for the transaction. * @param request.paymentToken - Payment token (source for standard flows, destination for post-quote). * @param request.refundTo - Optional address to receive refunds if the Relay transaction fails. * @param request.sourceAmounts - Source amounts for the transaction. @@ -415,6 +416,7 @@ function buildQuoteRequests({ * @param request.isMaxAmount - Whether the transaction is a maximum amount transaction. * @param request.isHyperliquidSource - Whether the source of funds is HyperLiquid. * @param request.isPolymarketDepositWallet - Whether the source of funds is a Polymarket deposit wallet. + * @param request.paymentOverride - Optional payment override type for the transaction. * @param request.destinationToken - Destination token (paymentToken in post-quote mode). * @param request.refundTo - Optional address to receive refunds if the Relay transaction fails. * @param request.sourceAmounts - Source amounts for the transaction (includes source token info). From c6f336a2498653e637294b5c495de13cb43d9a61 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Fri, 22 May 2026 20:23:51 +0530 Subject: [PATCH 05/13] update --- packages/transaction-controller/src/types.ts | 4 ++-- .../src/strategy/relay/relay-quotes.test.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/transaction-controller/src/types.ts b/packages/transaction-controller/src/types.ts index 162cc7ab5a..6694b67dba 100644 --- a/packages/transaction-controller/src/types.ts +++ b/packages/transaction-controller/src/types.ts @@ -791,12 +791,12 @@ export enum TransactionType { lendingWithdraw = 'lendingWithdraw', /** - * A transaction that deposits funds when paymentOverride is set. + * A transaction that deposits funds into a money account. */ moneyAccountDeposit = 'moneyAccountDeposit', /** - * A transaction that withdraws funds when paymentOverride is set. + * A transaction that withdraws funds from a money account. */ moneyAccountWithdraw = 'moneyAccountWithdraw', diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index f2da52c47a..ca45c8bd79 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -3437,7 +3437,7 @@ describe('Relay Quotes Utils', () => { expect(txSteps[1].id).toBe('money-account-deposit'); }); - it('does not inject when paymentOverride is not PaymentOverride.MoneyAccount', async () => { + it('does not inject when paymentOverride is not defined', async () => { const [quote] = await getRelayQuotes({ accountSupports7702: true, messenger, From 3c024f34ebb12aa762ea88392ff727e0be1d09b3 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Fri, 22 May 2026 20:29:08 +0530 Subject: [PATCH 06/13] update --- packages/transaction-pay-controller/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index a8c6916269..3298a0779b 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `getMoneyAccountTransactions` callback to `TransactionPayControllerOptions`; when `paymentOverride` is true on a transaction, the publish hook calls this callback and passes the resulting `additionalTransactions` to the strategy's `execute` request, ordered before or after the quote batch based on `isPostQuote` ([#8858](https://github.com/MetaMask/core/pull/8858)) +- Add `getPaymentOverrideData` callback (type `GetPaymentOverrideDataCallback`) to `TransactionPayControllerOptions`; when `paymentOverride` is defined on a transaction, this callback is invoked during quote execution and the resulting transactions are injected into the relay quote steps, ordered before or after the quote batch based on `isPostQuote` ([#8858](https://github.com/MetaMask/core/pull/8858)) + - Exposed as the `TransactionPayController:getPaymentOverrideData` messenger action (type `TransactionPayControllerGetPaymentOverrideDataAction`) ## [22.7.0] From b270c54dd98967ddb84f41b16f5119c1ddb5c1dc Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Sat, 23 May 2026 02:08:11 +0530 Subject: [PATCH 07/13] update --- .../src/strategy/relay/relay-quotes.test.ts | 37 +++++++++---------- .../src/strategy/relay/relay-quotes.ts | 37 +++++++++---------- .../src/strategy/relay/relay-submit.ts | 10 +---- .../src/strategy/relay/types.ts | 4 +- 4 files changed, 37 insertions(+), 51 deletions(-) diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index ca45c8bd79..a631b67ab9 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -3377,15 +3377,12 @@ describe('Relay Quotes Utils', () => { }); }); - describe('paymentOverride deposit step injection (paymentOverride defined)', () => { - const DEPOSIT_TX_MOCK = { + describe('paymentOverride step injection (paymentOverride defined)', () => { + const PAYMENT_OVERRIDE_TX_MOCK = { from: FROM_MOCK, - to: '0xmoneyaccount' as Hex, - data: '0xdeposit' as Hex, + to: '0xpaymentoverride' as Hex, + data: '0xpaymentoverride' as Hex, value: '0x0', - gas: '30000', - maxFeePerGas: '1000000000', - maxPriorityFeePerGas: '2000000000', }; beforeEach(() => { @@ -3400,8 +3397,8 @@ describe('Relay Quotes Utils', () => { }); }); - it('prepends deposit step before relay steps for standard (non-post-quote) flow', async () => { - getPaymentOverrideDataMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + it('prepends paymentOverride step before relay steps for standard (non-post-quote) flow', async () => { + getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); const [quote] = await getRelayQuotes({ accountSupports7702: true, @@ -3413,13 +3410,13 @@ describe('Relay Quotes Utils', () => { const txSteps = quote.original.steps.filter( (step): step is RelayTransactionStep => step.kind === 'transaction', ); - expect(txSteps[0].id).toBe('money-account-deposit'); - expect(txSteps[0].items[0].data.to).toBe(DEPOSIT_TX_MOCK.to); + expect(txSteps[0].id).toBe('payment-override'); + expect(txSteps[0].items[0].data.to).toBe(PAYMENT_OVERRIDE_TX_MOCK.to); expect(txSteps[1].id).toBe(STEP_MOCK.id); }); - it('appends deposit step after relay steps for post-quote flow', async () => { - getPaymentOverrideDataMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + it('appends paymentOverride step after relay steps for post-quote flow', async () => { + getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); const [quote] = await getRelayQuotes({ accountSupports7702: true, @@ -3434,7 +3431,7 @@ describe('Relay Quotes Utils', () => { (step): step is RelayTransactionStep => step.kind === 'transaction', ); expect(txSteps[0].id).toBe(STEP_MOCK.id); - expect(txSteps[1].id).toBe('money-account-deposit'); + expect(txSteps[1].id).toBe('payment-override'); }); it('does not inject when paymentOverride is not defined', async () => { @@ -3446,7 +3443,7 @@ describe('Relay Quotes Utils', () => { }); expect( - quote.original.steps.every((step) => step.id !== 'money-account-deposit'), + quote.original.steps.every((step) => step.id !== 'payment-override'), ).toBe(true); expect(getPaymentOverrideDataMock).not.toHaveBeenCalled(); }); @@ -3465,8 +3462,8 @@ describe('Relay Quotes Utils', () => { expect(quote.original.steps[0].id).toBe(STEP_MOCK.id); }); - it('uses sourceChainId to set chainId on deposit step items', async () => { - getPaymentOverrideDataMock.mockResolvedValue([DEPOSIT_TX_MOCK]); + it('uses sourceChainId to set chainId on paymentOverride step items', async () => { + getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); const [quote] = await getRelayQuotes({ accountSupports7702: true, @@ -3475,11 +3472,11 @@ describe('Relay Quotes Utils', () => { transaction: TRANSACTION_META_MOCK, }); - const depositStep = quote.original.steps.find( - (step) => step.id === 'money-account-deposit', + const overrideStep = quote.original.steps.find( + (step) => step.id === 'payment-override', ) as RelayTransactionStep; // QUOTE_REQUEST_MOCK.sourceChainId is '0x1' → chainId 1 - expect(depositStep.items[0].data.chainId).toBe(1); + expect(overrideStep.items[0].data.chainId).toBe(1); }); }); diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index 72cd41cd70..f158e5da9a 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -279,7 +279,7 @@ async function getSingleQuote( log('Fetched relay quote', quote); const quoteWithDeposits = request.paymentOverride - ? await injectPaymentOverrideDepositSteps(quote, request, fullRequest) + ? await injectPaymentOverrideSteps(quote, request, fullRequest) : quote; return await normalizeQuote(quoteWithDeposits, request, fullRequest); @@ -1023,45 +1023,45 @@ function getSubsidizedFeeAmountUsd(quote: RelayQuote): BigNumber { } /** - * Fetches deposit transactions from the paymentOverride callback and injects + * Fetches transactions from the paymentOverride callback and injects * them into the relay quote's steps so they are submitted alongside the relay * transactions and included in gas estimation. * - * For standard flows (`isPostQuote` false) the deposit step is prepended so it + * For standard flows (`isPostQuote` false) the step is prepended so it * executes before the relay bridge transaction. For post-quote flows it is * appended so it executes after. * * @param quote - Relay quote to mutate in place. * @param request - Quote request, used to determine ordering and chain ID. * @param fullRequest - Full quotes request, provides messenger and transaction ID. - * @returns The relay quote with deposit steps injected, or the original quote if no deposits are returned. + * @returns The relay quote with paymentOverride steps injected, or the original quote if the callback returns no transactions. */ -async function injectPaymentOverrideDepositSteps( +async function injectPaymentOverrideSteps( quote: RelayQuote, request: QuoteRequest, fullRequest: PayStrategyGetQuotesRequest, ): Promise { const { messenger, transaction } = fullRequest; - const depositTxs = await messenger.call( + const overrideTxs = await messenger.call( 'TransactionPayController:getPaymentOverrideData', transaction.id, ); - if (!depositTxs.length) { + if (!overrideTxs.length) { return quote; } - const depositStep = buildDepositStep(depositTxs, request.sourceChainId); + const overrideStep = buildPaymentOverrideStep(overrideTxs, request.sourceChainId); const steps = request.isPostQuote - ? [...quote.steps, depositStep] - : [depositStep, ...quote.steps]; + ? [...quote.steps, overrideStep] + : [overrideStep, ...quote.steps]; - log('Injected paymentOverride deposit step', { + log('Injected paymentOverride step', { transactionId: transaction.id, isPostQuote: request.isPostQuote, - depositTxCount: depositTxs.length, + txCount: overrideTxs.length, }); return { ...quote, steps }; @@ -1071,20 +1071,20 @@ async function injectPaymentOverrideDepositSteps( * Converts an array of TransactionParams into a single RelayTransactionStep * so they can be injected into a relay quote's steps array. * - * @param txParams - Deposit transactions from the paymentOverride callback. + * @param txParams - Transactions from the paymentOverride callback. * @param sourceChainId - Hex chain ID of the source network. - * @returns A relay transaction step wrapping the deposit transactions. + * @returns A relay transaction step wrapping the paymentOverride transactions. */ -function buildDepositStep( +function buildPaymentOverrideStep( txParams: TransactionParams[], sourceChainId: Hex, ): RelayTransactionStep { const chainId = parseInt(sourceChainId, 16); return { - id: 'money-account-deposit', + id: 'payment-override', kind: 'transaction', - requestId: 'money-account-deposit', + requestId: 'payment-override', items: txParams.map((params) => ({ check: { endpoint: '', method: 'GET' as const }, status: 'incomplete' as const, @@ -1092,9 +1092,6 @@ function buildDepositStep( chainId, data: (params.data as Hex) ?? '0x', from: params.from as Hex, - gas: params.gas, - maxFeePerGas: (params.maxFeePerGas as string) ?? '0x0', - maxPriorityFeePerGas: (params.maxPriorityFeePerGas as string) ?? '0x0', to: params.to as Hex, value: params.value, }, diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts index b6f0fc5431..346f96dab2 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts @@ -16,7 +16,6 @@ import type { TransactionPayQuote, } from '../../types'; import { - getFeatureFlags, getRelayPollingInterval, getRelayPollingTimeout, } from '../../utils/feature-flags'; @@ -266,21 +265,14 @@ async function waitForRelayCompletion( * Normalize the parameters from a relay quote step to match TransactionParams. * * @param params - Parameters from a relay quote step. - * @param messenger - Controller messenger. * @returns Normalized transaction parameters. */ function normalizeParams( params: RelayTransactionStep['items'][0]['data'], - messenger: TransactionPayControllerMessenger, ): TransactionParams { - const featureFlags = getFeatureFlags(messenger); - return { data: params.data, from: params.from, - gas: toHex(params.gas ?? featureFlags.relayFallbackGas.max), - maxFeePerGas: toHex(params.maxFeePerGas), - maxPriorityFeePerGas: toHex(params.maxPriorityFeePerGas), to: params.to, value: toHex(params.value ?? '0'), }; @@ -385,7 +377,7 @@ async function submitTransactions( } const normalizedParams = params.map((singleParams) => - normalizeParams(singleParams, messenger), + normalizeParams(singleParams), ); // For post-quote flows, prepend the original transaction so it gets diff --git a/packages/transaction-pay-controller/src/strategy/relay/types.ts b/packages/transaction-pay-controller/src/strategy/relay/types.ts index 442097d2e0..9b5d089e55 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/types.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/types.ts @@ -96,8 +96,8 @@ export type RelayTransactionStep = { data: Hex; from: Hex; gas?: string; - maxFeePerGas: string; - maxPriorityFeePerGas: string; + maxFeePerGas?: string; + maxPriorityFeePerGas?: string; to: Hex; value?: string; }; From 0abfc0041a738128aadd51ba8e61508449bf1d66 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Sat, 23 May 2026 02:11:36 +0530 Subject: [PATCH 08/13] update --- .../src/strategy/relay/relay-quotes.test.ts | 2 ++ .../src/strategy/relay/relay-quotes.ts | 2 ++ .../src/strategy/relay/relay-submit.ts | 10 +++++++++- .../src/strategy/relay/types.ts | 4 ++-- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index a631b67ab9..ab8ae47cf2 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -3383,6 +3383,8 @@ describe('Relay Quotes Utils', () => { to: '0xpaymentoverride' as Hex, data: '0xpaymentoverride' as Hex, value: '0x0', + maxFeePerGas: '1000000000', + maxPriorityFeePerGas: '2000000000', }; beforeEach(() => { diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index f158e5da9a..5a72b981b8 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -1092,6 +1092,8 @@ function buildPaymentOverrideStep( chainId, data: (params.data as Hex) ?? '0x', from: params.from as Hex, + maxFeePerGas: params.maxFeePerGas as string, + maxPriorityFeePerGas: params.maxPriorityFeePerGas as string, to: params.to as Hex, value: params.value, }, diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts index 346f96dab2..b6f0fc5431 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts @@ -16,6 +16,7 @@ import type { TransactionPayQuote, } from '../../types'; import { + getFeatureFlags, getRelayPollingInterval, getRelayPollingTimeout, } from '../../utils/feature-flags'; @@ -265,14 +266,21 @@ async function waitForRelayCompletion( * Normalize the parameters from a relay quote step to match TransactionParams. * * @param params - Parameters from a relay quote step. + * @param messenger - Controller messenger. * @returns Normalized transaction parameters. */ function normalizeParams( params: RelayTransactionStep['items'][0]['data'], + messenger: TransactionPayControllerMessenger, ): TransactionParams { + const featureFlags = getFeatureFlags(messenger); + return { data: params.data, from: params.from, + gas: toHex(params.gas ?? featureFlags.relayFallbackGas.max), + maxFeePerGas: toHex(params.maxFeePerGas), + maxPriorityFeePerGas: toHex(params.maxPriorityFeePerGas), to: params.to, value: toHex(params.value ?? '0'), }; @@ -377,7 +385,7 @@ async function submitTransactions( } const normalizedParams = params.map((singleParams) => - normalizeParams(singleParams), + normalizeParams(singleParams, messenger), ); // For post-quote flows, prepend the original transaction so it gets diff --git a/packages/transaction-pay-controller/src/strategy/relay/types.ts b/packages/transaction-pay-controller/src/strategy/relay/types.ts index 9b5d089e55..442097d2e0 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/types.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/types.ts @@ -96,8 +96,8 @@ export type RelayTransactionStep = { data: Hex; from: Hex; gas?: string; - maxFeePerGas?: string; - maxPriorityFeePerGas?: string; + maxFeePerGas: string; + maxPriorityFeePerGas: string; to: Hex; value?: string; }; From 4a19e414c97b27de79a362c464fad5c3461b7467 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Sat, 23 May 2026 02:16:29 +0530 Subject: [PATCH 09/13] update --- .../src/strategy/relay/relay-quotes.test.ts | 20 ++++++++++++++++++- .../src/strategy/relay/relay-quotes.ts | 9 ++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index ab8ae47cf2..381751bc6e 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -3464,7 +3464,25 @@ describe('Relay Quotes Utils', () => { expect(quote.original.steps[0].id).toBe(STEP_MOCK.id); }); - it('uses sourceChainId to set chainId on paymentOverride step items', async () => { + it('uses the transaction chainId when present', async () => { + getPaymentOverrideDataMock.mockResolvedValue([ + { ...PAYMENT_OVERRIDE_TX_MOCK, chainId: '0xa' }, + ]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [{ ...QUOTE_REQUEST_MOCK, paymentOverride: PaymentOverride.MoneyAccount }], + transaction: TRANSACTION_META_MOCK, + }); + + const overrideStep = quote.original.steps.find( + (step) => step.id === 'payment-override', + ) as RelayTransactionStep; + expect(overrideStep.items[0].data.chainId).toBe(10); + }); + + it('falls back to sourceChainId when transaction chainId is absent', async () => { getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); const [quote] = await getRelayQuotes({ diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index 5a72b981b8..ad6ff6fd3e 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -1071,15 +1071,18 @@ async function injectPaymentOverrideSteps( * Converts an array of TransactionParams into a single RelayTransactionStep * so they can be injected into a relay quote's steps array. * + * Each transaction's `chainId` is used when present; `sourceChainId` is the + * fallback for transactions that do not specify one. + * * @param txParams - Transactions from the paymentOverride callback. - * @param sourceChainId - Hex chain ID of the source network. + * @param sourceChainId - Fallback hex chain ID used when a transaction omits its own. * @returns A relay transaction step wrapping the paymentOverride transactions. */ function buildPaymentOverrideStep( txParams: TransactionParams[], sourceChainId: Hex, ): RelayTransactionStep { - const chainId = parseInt(sourceChainId, 16); + const fallbackChainId = parseInt(sourceChainId, 16); return { id: 'payment-override', @@ -1089,7 +1092,7 @@ function buildPaymentOverrideStep( check: { endpoint: '', method: 'GET' as const }, status: 'incomplete' as const, data: { - chainId, + chainId: params.chainId ? parseInt(params.chainId, 16) : fallbackChainId, data: (params.data as Hex) ?? '0x', from: params.from as Hex, maxFeePerGas: params.maxFeePerGas as string, From a0501e5c28f9b34e048fc3c967267bef173de113 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Sat, 23 May 2026 02:17:03 +0530 Subject: [PATCH 10/13] update --- .../src/strategy/relay/relay-quotes.test.ts | 20 +------------------ .../src/strategy/relay/relay-quotes.ts | 9 +++------ 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index 381751bc6e..ab8ae47cf2 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -3464,25 +3464,7 @@ describe('Relay Quotes Utils', () => { expect(quote.original.steps[0].id).toBe(STEP_MOCK.id); }); - it('uses the transaction chainId when present', async () => { - getPaymentOverrideDataMock.mockResolvedValue([ - { ...PAYMENT_OVERRIDE_TX_MOCK, chainId: '0xa' }, - ]); - - const [quote] = await getRelayQuotes({ - accountSupports7702: true, - messenger, - requests: [{ ...QUOTE_REQUEST_MOCK, paymentOverride: PaymentOverride.MoneyAccount }], - transaction: TRANSACTION_META_MOCK, - }); - - const overrideStep = quote.original.steps.find( - (step) => step.id === 'payment-override', - ) as RelayTransactionStep; - expect(overrideStep.items[0].data.chainId).toBe(10); - }); - - it('falls back to sourceChainId when transaction chainId is absent', async () => { + it('uses sourceChainId to set chainId on paymentOverride step items', async () => { getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); const [quote] = await getRelayQuotes({ diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index ad6ff6fd3e..5a72b981b8 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -1071,18 +1071,15 @@ async function injectPaymentOverrideSteps( * Converts an array of TransactionParams into a single RelayTransactionStep * so they can be injected into a relay quote's steps array. * - * Each transaction's `chainId` is used when present; `sourceChainId` is the - * fallback for transactions that do not specify one. - * * @param txParams - Transactions from the paymentOverride callback. - * @param sourceChainId - Fallback hex chain ID used when a transaction omits its own. + * @param sourceChainId - Hex chain ID of the source network. * @returns A relay transaction step wrapping the paymentOverride transactions. */ function buildPaymentOverrideStep( txParams: TransactionParams[], sourceChainId: Hex, ): RelayTransactionStep { - const fallbackChainId = parseInt(sourceChainId, 16); + const chainId = parseInt(sourceChainId, 16); return { id: 'payment-override', @@ -1092,7 +1089,7 @@ function buildPaymentOverrideStep( check: { endpoint: '', method: 'GET' as const }, status: 'incomplete' as const, data: { - chainId: params.chainId ? parseInt(params.chainId, 16) : fallbackChainId, + chainId, data: (params.data as Hex) ?? '0x', from: params.from as Hex, maxFeePerGas: params.maxFeePerGas as string, From edec73a92e797fc708679c6d2e0f17c7d5c271ac Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Sat, 23 May 2026 02:23:47 +0530 Subject: [PATCH 11/13] update --- packages/transaction-pay-controller/CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index 3298a0779b..eee671e161 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -9,8 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `getPaymentOverrideData` callback (type `GetPaymentOverrideDataCallback`) to `TransactionPayControllerOptions`; when `paymentOverride` is defined on a transaction, this callback is invoked during quote execution and the resulting transactions are injected into the relay quote steps, ordered before or after the quote batch based on `isPostQuote` ([#8858](https://github.com/MetaMask/core/pull/8858)) - - Exposed as the `TransactionPayController:getPaymentOverrideData` messenger action (type `TransactionPayControllerGetPaymentOverrideDataAction`) +- Add `getPaymentOverrideData` callback to `TransactionPayControllerOptions`, when `paymentOverride` is defined on a transaction, this callback is invoked the resulting transactions are injected into the relay quote steps ([#8858](https://github.com/MetaMask/core/pull/8858)) ## [22.7.0] From 0b42cf3fc9a39133f05c9f94b254a2c77a68f71c Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Sat, 23 May 2026 02:37:30 +0530 Subject: [PATCH 12/13] update --- .../src/strategy/relay/relay-quotes.test.ts | 20 ++++++++++++++++++- .../src/strategy/relay/relay-quotes.ts | 9 ++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index ab8ae47cf2..381751bc6e 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -3464,7 +3464,25 @@ describe('Relay Quotes Utils', () => { expect(quote.original.steps[0].id).toBe(STEP_MOCK.id); }); - it('uses sourceChainId to set chainId on paymentOverride step items', async () => { + it('uses the transaction chainId when present', async () => { + getPaymentOverrideDataMock.mockResolvedValue([ + { ...PAYMENT_OVERRIDE_TX_MOCK, chainId: '0xa' }, + ]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [{ ...QUOTE_REQUEST_MOCK, paymentOverride: PaymentOverride.MoneyAccount }], + transaction: TRANSACTION_META_MOCK, + }); + + const overrideStep = quote.original.steps.find( + (step) => step.id === 'payment-override', + ) as RelayTransactionStep; + expect(overrideStep.items[0].data.chainId).toBe(10); + }); + + it('falls back to sourceChainId when transaction chainId is absent', async () => { getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); const [quote] = await getRelayQuotes({ diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index 5a72b981b8..ad6ff6fd3e 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -1071,15 +1071,18 @@ async function injectPaymentOverrideSteps( * Converts an array of TransactionParams into a single RelayTransactionStep * so they can be injected into a relay quote's steps array. * + * Each transaction's `chainId` is used when present; `sourceChainId` is the + * fallback for transactions that do not specify one. + * * @param txParams - Transactions from the paymentOverride callback. - * @param sourceChainId - Hex chain ID of the source network. + * @param sourceChainId - Fallback hex chain ID used when a transaction omits its own. * @returns A relay transaction step wrapping the paymentOverride transactions. */ function buildPaymentOverrideStep( txParams: TransactionParams[], sourceChainId: Hex, ): RelayTransactionStep { - const chainId = parseInt(sourceChainId, 16); + const fallbackChainId = parseInt(sourceChainId, 16); return { id: 'payment-override', @@ -1089,7 +1092,7 @@ function buildPaymentOverrideStep( check: { endpoint: '', method: 'GET' as const }, status: 'incomplete' as const, data: { - chainId, + chainId: params.chainId ? parseInt(params.chainId, 16) : fallbackChainId, data: (params.data as Hex) ?? '0x', from: params.from as Hex, maxFeePerGas: params.maxFeePerGas as string, From d32a3f4dfcdc7b0cfcb098f51fb6c7931e4fe3d7 Mon Sep 17 00:00:00 2001 From: Jyoti Puri Date: Sat, 23 May 2026 02:43:07 +0530 Subject: [PATCH 13/13] update --- .../src/TransactionPayController.test.ts | 4 +- .../src/strategy/relay/relay-quotes.test.ts | 46 +++++++++++++++---- .../src/strategy/relay/relay-quotes.ts | 9 +++- 3 files changed, 46 insertions(+), 13 deletions(-) diff --git a/packages/transaction-pay-controller/src/TransactionPayController.test.ts b/packages/transaction-pay-controller/src/TransactionPayController.test.ts index e6a572dac7..26d8d43039 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.test.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.test.ts @@ -472,9 +472,7 @@ describe('TransactionPayController', () => { describe('getPaymentOverrideData', () => { it('delegates to the callback', async () => { const txMock = { from: '0xabc', to: '0xdef' }; - const getPaymentOverrideDataMock = jest - .fn() - .mockResolvedValue([txMock]); + const getPaymentOverrideDataMock = jest.fn().mockResolvedValue([txMock]); new TransactionPayController({ getDelegationTransaction: jest.fn(), diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index 381751bc6e..448d04fd30 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -3400,12 +3400,19 @@ describe('Relay Quotes Utils', () => { }); it('prepends paymentOverride step before relay steps for standard (non-post-quote) flow', async () => { - getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); + getPaymentOverrideDataMock.mockResolvedValue([ + PAYMENT_OVERRIDE_TX_MOCK, + ]); const [quote] = await getRelayQuotes({ accountSupports7702: true, messenger, - requests: [{ ...QUOTE_REQUEST_MOCK, paymentOverride: PaymentOverride.MoneyAccount }], + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + }, + ], transaction: TRANSACTION_META_MOCK, }); @@ -3418,13 +3425,19 @@ describe('Relay Quotes Utils', () => { }); it('appends paymentOverride step after relay steps for post-quote flow', async () => { - getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); + getPaymentOverrideDataMock.mockResolvedValue([ + PAYMENT_OVERRIDE_TX_MOCK, + ]); const [quote] = await getRelayQuotes({ accountSupports7702: true, messenger, requests: [ - { ...QUOTE_REQUEST_MOCK, paymentOverride: PaymentOverride.MoneyAccount, isPostQuote: true }, + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + isPostQuote: true, + }, ], transaction: TRANSACTION_META_MOCK, }); @@ -3456,7 +3469,12 @@ describe('Relay Quotes Utils', () => { const [quote] = await getRelayQuotes({ accountSupports7702: true, messenger, - requests: [{ ...QUOTE_REQUEST_MOCK, paymentOverride: PaymentOverride.MoneyAccount }], + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + }, + ], transaction: TRANSACTION_META_MOCK, }); @@ -3472,7 +3490,12 @@ describe('Relay Quotes Utils', () => { const [quote] = await getRelayQuotes({ accountSupports7702: true, messenger, - requests: [{ ...QUOTE_REQUEST_MOCK, paymentOverride: PaymentOverride.MoneyAccount }], + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + }, + ], transaction: TRANSACTION_META_MOCK, }); @@ -3483,12 +3506,19 @@ describe('Relay Quotes Utils', () => { }); it('falls back to sourceChainId when transaction chainId is absent', async () => { - getPaymentOverrideDataMock.mockResolvedValue([PAYMENT_OVERRIDE_TX_MOCK]); + getPaymentOverrideDataMock.mockResolvedValue([ + PAYMENT_OVERRIDE_TX_MOCK, + ]); const [quote] = await getRelayQuotes({ accountSupports7702: true, messenger, - requests: [{ ...QUOTE_REQUEST_MOCK, paymentOverride: PaymentOverride.MoneyAccount }], + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + }, + ], transaction: TRANSACTION_META_MOCK, }); diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index ad6ff6fd3e..2c5c9c33d8 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -1052,7 +1052,10 @@ async function injectPaymentOverrideSteps( return quote; } - const overrideStep = buildPaymentOverrideStep(overrideTxs, request.sourceChainId); + const overrideStep = buildPaymentOverrideStep( + overrideTxs, + request.sourceChainId, + ); const steps = request.isPostQuote ? [...quote.steps, overrideStep] @@ -1092,7 +1095,9 @@ function buildPaymentOverrideStep( check: { endpoint: '', method: 'GET' as const }, status: 'incomplete' as const, data: { - chainId: params.chainId ? parseInt(params.chainId, 16) : fallbackChainId, + chainId: params.chainId + ? parseInt(params.chainId, 16) + : fallbackChainId, data: (params.data as Hex) ?? '0x', from: params.from as Hex, maxFeePerGas: params.maxFeePerGas as string,