Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -2721,6 +2721,102 @@ describe('Relay Quotes Utils', () => {
]);
expect(result[0].original.metamask.is7702).toBe(true);
});

it('appends extra gas when both postQuote and paymentOverride are set', async () => {
successfulFetchMock.mockResolvedValue({
ok: true,
json: async () => QUOTE_MOCK,
} as never);

getGasBufferMock.mockReturnValue(1);

const result = await getRelayQuotes({
accountSupports7702: true,
messenger,
requests: [
{
...QUOTE_REQUEST_MOCK,
targetAmountMinimum: '0',
isPostQuote: true,
paymentOverride: PaymentOverride.MoneyAccount,
},
],
transaction: {
...TRANSACTION_META_MOCK,
chainId: '0x1' as Hex,
txParams: {
from: FROM_MOCK,
to: '0x9' as Hex,
data: '0xaaa' as Hex,
gas: '0x13498',
value: '0',
},
} as TransactionMeta,
});

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

it('appends extra gas to combined 7702 limit when both postQuote and paymentOverride are 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],
});

getGasBufferMock.mockReturnValue(1);

const result = await getRelayQuotes({
accountSupports7702: true,
messenger,
requests: [
{
...QUOTE_REQUEST_MOCK,
targetAmountMinimum: '0',
isPostQuote: true,
paymentOverride: PaymentOverride.MoneyAccount,
},
],
transaction: {
...TRANSACTION_META_MOCK,
chainId: '0x1' as Hex,
txParams: {
from: FROM_MOCK,
to: '0x9' as Hex,
data: '0xaaa' as Hex,
gas: '0x13498',
value: '0',
},
} as TransactionMeta,
});

expect(result[0].original.metamask.gasLimits).toStrictEqual([
51000 + 79000 + 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 @@ -914,7 +914,15 @@ function combinePrependedGas(
? combinePostQuoteGas(relayOnlyGas, transaction)
: relayOnlyGas;

return request.paymentOverride ? addPaymentOverrideGas(gas) : gas;
if (!request.paymentOverride) {
return gas;
}

// Combined: override gas goes last (appended after relay + original tx)
// Override-only: override gas goes first (prepended before relay txs)
return request.isPostQuote
? appendPaymentOverrideGas(gas)
: prependPaymentOverrideGas(gas);
}

/**
Expand Down Expand Up @@ -974,7 +982,7 @@ function combinePostQuoteGas(
};
}

function addPaymentOverrideGas(relayGas: RelayGasResult): RelayGasResult {
function prependPaymentOverrideGas(relayGas: RelayGasResult): RelayGasResult {
const gasLimits = relayGas.is7702
? [relayGas.gasLimits[0] + PAYMENT_OVERRIDE_GAS]
: [PAYMENT_OVERRIDE_GAS, ...relayGas.gasLimits];
Expand All @@ -987,6 +995,19 @@ function addPaymentOverrideGas(relayGas: RelayGasResult): RelayGasResult {
};
}

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

return {
totalGasEstimate: relayGas.totalGasEstimate + PAYMENT_OVERRIDE_GAS,
totalGasLimit: relayGas.totalGasLimit + PAYMENT_OVERRIDE_GAS,
gasLimits,
is7702: relayGas.is7702,
};
}

/**
* Calculate the provider fee for a Relay quote.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1275,6 +1275,185 @@ describe('Relay Submit Utils', () => {
});
});

describe('combined post-quote + paymentOverride flow', () => {
const TRANSACTION_DATA_MOCK = {
isLoading: false,
tokens: [],
};

const PAYMENT_OVERRIDE_TX_MOCK: BatchTransactionParams = {
to: '0xpaymentoverride' as Hex,
data: '0xpaymentoverride' as Hex,
value: '0x0' as Hex,
};

beforeEach(() => {
request.quotes[0].request.isPostQuote = true;
request.quotes[0].request.paymentOverride =
PaymentOverride.MoneyAccount;
request.transaction = {
id: ORIGINAL_TRANSACTION_ID_MOCK,
txParams: {
from: FROM_MOCK,
to: '0xrecipient' as Hex,
data: '0xorigdata' as Hex,
value: '0x100' as Hex,
},
type: TransactionType.simpleSend,
} as TransactionMeta;

getControllerStateMock.mockReturnValue({
transactionData: {
[ORIGINAL_TRANSACTION_ID_MOCK]: TRANSACTION_DATA_MOCK,
},
});

getPaymentOverrideDataMock.mockResolvedValue({
calls: [PAYMENT_OVERRIDE_TX_MOCK],
});
});

it('prepends original tx and appends override tx', async () => {
await submitRelayQuotes(request);

expect(addTransactionBatchMock).toHaveBeenCalledTimes(1);

const { transactions } = addTransactionBatchMock.mock
.calls[0][0] as unknown as Record<string, unknown[]>;

expect(transactions).toHaveLength(3);
expect(transactions[0]).toStrictEqual(
expect.objectContaining({
params: expect.objectContaining({
data: '0xorigdata',
to: '0xrecipient',
value: '0x100',
}),
}),
);
expect(transactions[2]).toStrictEqual(
expect.objectContaining({
params: expect.objectContaining({
data: PAYMENT_OVERRIDE_TX_MOCK.data,
to: PAYMENT_OVERRIDE_TX_MOCK.to,
value: PAYMENT_OVERRIDE_TX_MOCK.value,
}),
}),
);
});

it('assigns correct transaction types', async () => {
await submitRelayQuotes(request);

const { transactions } = addTransactionBatchMock.mock
.calls[0][0] as unknown as Record<string, unknown[]>;

expect(transactions).toHaveLength(3);
expect(transactions[0]).toStrictEqual(
expect.objectContaining({
type: TransactionType.simpleSend,
}),
);
expect(transactions[1]).toStrictEqual(
expect.objectContaining({
type: TransactionType.relayDeposit,
}),
);
expect(transactions[2]).toStrictEqual(
expect.objectContaining({
type: TransactionType.simpleSend,
}),
);
});

it('assigns correct types with multi-step relay (approve + deposit)', async () => {
request.quotes[0].original.steps[0].items.push({
...request.quotes[0].original.steps[0].items[0],
data: {
...request.quotes[0].original.steps[0].items[0].data,
data: '0xapprove' as Hex,
to: '0xapproveTarget' as Hex,
},
});

request.quotes[0].original.metamask.gasLimits = [
21000, 30000, 50000, 75000,
];

await submitRelayQuotes(request);

const { transactions } = addTransactionBatchMock.mock
.calls[0][0] as unknown as Record<string, unknown[]>;

expect(transactions).toHaveLength(4);
expect(transactions[0]).toStrictEqual(
expect.objectContaining({
type: TransactionType.simpleSend,
}),
);
expect(transactions[1]).toStrictEqual(
expect.objectContaining({
type: TransactionType.tokenMethodApprove,
}),
);
expect(transactions[2]).toStrictEqual(
expect.objectContaining({
type: TransactionType.relayDeposit,
}),
);
expect(transactions[3]).toStrictEqual(
expect.objectContaining({
type: TransactionType.simpleSend,
}),
);
});

it('assigns correct gas limits', async () => {
request.quotes[0].original.metamask.gasLimits = [
21000, 30000, 75000,
];

await submitRelayQuotes(request);

const { transactions } = addTransactionBatchMock.mock
.calls[0][0] as unknown as Record<
string,
{ params: { gas?: string } }[]
>;

expect(transactions).toHaveLength(3);
expect(transactions[0].params.gas).toBe('0x5208');
expect(transactions[1].params.gas).toBe('0x7530');
expect(transactions[2].params.gas).toBe('0x124f8');
});

it('skips source balance validation', async () => {
getLiveTokenBalanceMock.mockResolvedValue('0');

await submitRelayQuotes(request);

expect(getLiveTokenBalanceMock).not.toHaveBeenCalled();
});

it('does not append when callback returns empty array', async () => {
getPaymentOverrideDataMock.mockResolvedValue({ calls: [] });

await submitRelayQuotes(request);

const { transactions } = addTransactionBatchMock.mock
.calls[0][0] as unknown as Record<string, unknown[]>;

expect(transactions).toHaveLength(2);
expect(transactions[0]).toStrictEqual(
expect.objectContaining({
params: expect.objectContaining({
data: '0xorigdata',
}),
}),
);
});
});

it('adds transaction batch with single gasLimit7702', async () => {
request.quotes[0].original.steps[0].items.push({
...request.quotes[0].original.steps[0].items[0],
Expand Down
Loading
Loading