Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
4fddaec
feat: add transaction data property useMoneyAccount
jpuri May 20, 2026
a1258e6
update changelog
jpuri May 20, 2026
3ecc086
feat: support for money account source for MM pay deposit transactions
jpuri May 21, 2026
1fc9d29
merge
jpuri May 22, 2026
886efa9
update
jpuri May 22, 2026
c6f336a
update
jpuri May 22, 2026
3c024f3
update
jpuri May 22, 2026
b270c54
update
jpuri May 22, 2026
0abfc00
update
jpuri May 22, 2026
4a19e41
update
jpuri May 22, 2026
a0501e5
update
jpuri May 22, 2026
edec73a
update
jpuri May 22, 2026
0b42cf3
update
jpuri May 22, 2026
d32a3f4
update
jpuri May 22, 2026
7801d59
update
jpuri May 25, 2026
fb4ff92
update
jpuri May 25, 2026
8d683c8
update
jpuri May 26, 2026
9c6080f
Merge branch 'main' of https://github.com/MetaMask/core into build_ad…
jpuri May 26, 2026
e3897dd
update
jpuri May 26, 2026
f4a6fea
update
jpuri May 26, 2026
df9209e
Merge branch 'main' into build_additional_trxn_steps
jpuri May 27, 2026
c392af5
update
jpuri May 27, 2026
889084c
Merge branch 'build_additional_trxn_steps' of https://github.com/Meta…
jpuri May 27, 2026
26439e5
update
jpuri May 27, 2026
287808b
update
jpuri May 27, 2026
02c0695
update
jpuri May 27, 2026
d86d98a
update
jpuri May 27, 2026
3209a5d
update
jpuri May 27, 2026
5db823f
Merge branch 'main' into build_additional_trxn_steps
jpuri May 27, 2026
62c33a8
Merge branch 'main' into build_additional_trxn_steps
jpuri May 27, 2026
aa8c59e
update
jpuri May 27, 2026
78f2c9c
Merge branch 'build_additional_trxn_steps' of https://github.com/Meta…
jpuri May 27, 2026
8d907b2
Merge branch 'main' into build_additional_trxn_steps
jpuri May 27, 2026
be8c99a
update
jpuri May 27, 2026
ce8333c
Merge branch 'build_additional_trxn_steps' of https://github.com/Meta…
jpuri May 27, 2026
8332556
update
jpuri May 27, 2026
2fb76df
update
jpuri May 27, 2026
c2afcb4
update
jpuri May 27, 2026
cf9615b
Merge branch 'main' into build_additional_trxn_steps
jpuri May 27, 2026
9f70f71
update
jpuri May 27, 2026
1bf0deb
Merge branch 'main' into build_additional_trxn_steps
jpuri May 27, 2026
aa25cca
Merge branch 'build_additional_trxn_steps' of https://github.com/Meta…
jpuri May 27, 2026
d54d178
update
jpuri May 27, 2026
6444b39
update
jpuri May 27, 2026
44e34c2
Merge branch 'main' into build_additional_trxn_steps
jpuri May 28, 2026
12757b5
update
jpuri May 28, 2026
0ee79e9
Merge branch 'build_additional_trxn_steps' of https://github.com/Meta…
jpuri May 28, 2026
7c064a9
update
jpuri May 28, 2026
31a8152
update
jpuri May 28, 2026
33accc9
Merge branch 'main' into build_additional_trxn_steps
jpuri May 28, 2026
e4c41e3
Merge branch 'main' into build_additional_trxn_steps
jpuri May 28, 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
4 changes: 4 additions & 0 deletions packages/transaction-pay-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- 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 ([#8870](https://github.com/MetaMask/core/pull/8870))

### Changed

- Bump `@metamask/assets-controllers` from `^108.1.0` to `^108.2.0` ([#8911](https://github.com/MetaMask/core/pull/8911))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,21 @@ export type TransactionPayControllerGetDelegationTransactionAction = {
handler: TransactionPayController['getDelegationTransaction'];
};

/**
* Returns additional transactions for the paymentOverride flow.
*
* 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 GetPaymentOverrideDataCallback}.
* @returns A promise resolving to the additional transactions array.
*/
export type TransactionPayControllerGetPaymentOverrideDataAction = {
type: `TransactionPayController:getPaymentOverrideData`;
handler: TransactionPayController['getPaymentOverrideData'];
};
Comment thread
jpuri marked this conversation as resolved.

/**
* Gets the preferred strategy for a transaction.
*
Expand Down Expand Up @@ -113,6 +128,7 @@ export type TransactionPayControllerMethodActions =
| TransactionPayControllerUpdatePaymentTokenAction
| TransactionPayControllerUpdateFiatPaymentAction
| TransactionPayControllerGetDelegationTransactionAction
| TransactionPayControllerGetPaymentOverrideDataAction
| TransactionPayControllerGetStrategyAction
| TransactionPayControllerPolymarketGetDepositWalletAddressAction
| TransactionPayControllerPolymarketSubmitDepositWalletBatchAction;
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,55 @@ describe('TransactionPayController', () => {
});
});

describe('getPaymentOverrideData', () => {
it('delegates to the callback', async () => {
const resultMock = {
calls: [{ to: '0xdef' as const, data: '0xabc' as const }],
};
const getPaymentOverrideDataMock = jest
.fn()
.mockResolvedValue(resultMock);

new TransactionPayController({
getDelegationTransaction: jest.fn(),
getPaymentOverrideData: getPaymentOverrideDataMock,
messenger,
});

const requestMock = {
amount: '1.5',
transaction: TRANSACTION_META_MOCK,
transactionData: { isLoading: false, tokens: [] },
};

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

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

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

const result = await messenger.call(
'TransactionPayController:getPaymentOverrideData',
{
amount: '1.5',
transaction: TRANSACTION_META_MOCK,
transactionData: { isLoading: false, tokens: [] },
},
);

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

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 @@ -15,6 +15,7 @@ import { QuoteRefresher } from './helpers/QuoteRefresher';
import { deriveFiatAssetForFiatPayment } from './strategy/fiat/utils';
import type {
GetDelegationTransactionCallback,
GetPaymentOverrideDataCallback,
PolymarketCallbacks,
TransactionConfigCallback,
TransactionData,
Expand All @@ -36,6 +37,7 @@ import {

const MESSENGER_EXPOSED_METHODS = [
'getDelegationTransaction',
'getPaymentOverrideData',
'getStrategy',
'polymarketGetDepositWalletAddress',
'polymarketSubmitDepositWalletBatch',
Expand Down Expand Up @@ -64,6 +66,8 @@ export class TransactionPayController extends BaseController<
> {
readonly #getDelegationTransaction: GetDelegationTransactionCallback;

readonly #getPaymentOverrideData?: GetPaymentOverrideDataCallback;

readonly #getStrategy?: (
transaction: TransactionMeta,
) => TransactionPayStrategy;
Expand All @@ -76,6 +80,7 @@ export class TransactionPayController extends BaseController<

constructor({
getDelegationTransaction,
getPaymentOverrideData,
getStrategy,
getStrategies,
messenger,
Expand All @@ -90,6 +95,7 @@ export class TransactionPayController extends BaseController<
});

this.#getDelegationTransaction = getDelegationTransaction;
this.#getPaymentOverrideData = getPaymentOverrideData;
this.#getStrategy = getStrategy;
this.#getStrategies = getStrategies;
this.#polymarket = polymarket;
Expand Down Expand Up @@ -217,6 +223,24 @@ export class TransactionPayController extends BaseController<
return this.#getDelegationTransaction(...args);
}

/**
* Returns additional transactions for the paymentOverride flow.
*
* 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 GetPaymentOverrideDataCallback}.
* @returns A promise resolving to the additional transactions array.
*/
getPaymentOverrideData(
...args: Parameters<GetPaymentOverrideDataCallback>
): ReturnType<GetPaymentOverrideDataCallback> {
return (
this.#getPaymentOverrideData?.(...args) ?? Promise.resolve({ calls: [] })
);
}

/**
* Gets the preferred strategy for a transaction.
*
Expand Down
2 changes: 2 additions & 0 deletions packages/transaction-pay-controller/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type {
GetPaymentOverrideDataRequest,
GetPaymentOverrideDataResponse,
Comment thread
jpuri marked this conversation as resolved.
TransactionConfig,
TransactionConfigCallback,
TransactionData,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
CHAIN_ID_HYPERCORE,
CHAIN_ID_POLYGON,
NATIVE_TOKEN_ADDRESS,
PaymentOverride,
POLYGON_USDCE_ADDRESS,
} from '../../constants';
import { getMessengerMock } from '../../tests/messenger-mock';
Expand Down Expand Up @@ -2648,6 +2649,78 @@ describe('Relay Quotes Utils', () => {

expect(result[0].original.metamask.gasLimits).toStrictEqual([]);
});

it('adds extra gas when paymentOverride is set', async () => {
successfulFetchMock.mockResolvedValue({
ok: true,
json: async () => QUOTE_MOCK,
} as never);

const result = await getRelayQuotes({
accountSupports7702: true,
messenger,
requests: [
{
...QUOTE_REQUEST_MOCK,
paymentOverride: PaymentOverride.MoneyAccount,
},
],
transaction: TRANSACTION_META_MOCK,
});

expect(calculateGasCostMock).toHaveBeenCalledWith(
expect.objectContaining({ gas: 21000 + 75000 }),
);

expect(result[0].original.metamask.gasLimits).toStrictEqual([
75000, 21000,
]);
});

it('adds extra gas to combined 7702 limit when paymentOverride is set', async () => {
const multiStepQuote = {
...QUOTE_MOCK,
steps: [
{
...STEP_MOCK,
items: [
STEP_MOCK.items[0],
{
...STEP_MOCK.items[0],
data: { ...STEP_MOCK.items[0].data, gas: '30000' },
},
],
},
],
};

successfulFetchMock.mockResolvedValue({
ok: true,
json: async () => multiStepQuote,
} as never);

estimateGasBatchMock.mockResolvedValue({
totalGasLimit: 51000,
gasLimits: [51000],
});

const result = await getRelayQuotes({
accountSupports7702: true,
messenger,
requests: [
{
...QUOTE_REQUEST_MOCK,
paymentOverride: PaymentOverride.MoneyAccount,
},
],
transaction: TRANSACTION_META_MOCK,
});

expect(result[0].original.metamask.gasLimits).toStrictEqual([
51000 + 75000,
]);
expect(result[0].original.metamask.is7702).toBe(true);
});
});

describe('HyperLiquid source (isHyperliquidSource)', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ import type {

const log = createModuleLogger(projectLogger, 'relay-strategy');

// Hardcoded gas allowance for the prepended payment override transaction(s).
const PAYMENT_OVERRIDE_GAS = 75_000;

/**
* Fetches Relay quotes.
*
Expand Down Expand Up @@ -697,9 +700,7 @@ async function calculateSourceNetworkCost(
);

const { gasLimits, is7702, totalGasEstimate, totalGasLimit } =
request.isPostQuote
? combinePostQuoteGas(relayOnlyGas, transaction)
: relayOnlyGas;
combinePrependedGas(relayOnlyGas, request, transaction);

log('Gas limit', {
is7702,
Expand Down Expand Up @@ -897,6 +898,25 @@ function toRelayQuoteGasTransaction(
};
}

type RelayGasResult = {
totalGasEstimate: number;
totalGasLimit: number;
gasLimits: number[];
is7702: boolean;
};

function combinePrependedGas(
relayOnlyGas: RelayGasResult,
request: QuoteRequest,
transaction: TransactionMeta,
): RelayGasResult {
const gas = request.isPostQuote
? combinePostQuoteGas(relayOnlyGas, transaction)
: relayOnlyGas;

return request.paymentOverride ? addPaymentOverrideGas(gas) : gas;
}
Comment thread
jpuri marked this conversation as resolved.

/**
* Combine the original transaction's gas with relay gas for post-quote flows.
*
Expand All @@ -913,19 +933,9 @@ function toRelayQuoteGasTransaction(
* @returns Combined gas estimates including the original transaction.
*/
function combinePostQuoteGas(
relayGas: {
totalGasEstimate: number;
totalGasLimit: number;
gasLimits: number[];
is7702: boolean;
},
relayGas: RelayGasResult,
transaction: TransactionMeta,
): {
totalGasEstimate: number;
totalGasLimit: number;
gasLimits: number[];
is7702: boolean;
} {
): RelayGasResult {
const nestedGas = transaction.nestedTransactions?.find((tx) => tx.gas)?.gas;
const rawGas = nestedGas ?? transaction.txParams.gas;
const originalTxGas = rawGas ? new BigNumber(rawGas).toNumber() : undefined;
Expand Down Expand Up @@ -964,6 +974,19 @@ function combinePostQuoteGas(
};
}

function addPaymentOverrideGas(relayGas: RelayGasResult): RelayGasResult {
const gasLimits = relayGas.is7702
? [relayGas.gasLimits[0] + PAYMENT_OVERRIDE_GAS]
: [PAYMENT_OVERRIDE_GAS, ...relayGas.gasLimits];

return {
totalGasEstimate: relayGas.totalGasEstimate + PAYMENT_OVERRIDE_GAS,
totalGasLimit: relayGas.totalGasLimit + PAYMENT_OVERRIDE_GAS,
gasLimits,
is7702: relayGas.is7702,
};
}
Comment thread
jpuri marked this conversation as resolved.

/**
* Calculate the provider fee for a Relay quote.
*
Expand Down
Loading
Loading