diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 2f2d631da26..947f1ac10db 100644 --- a/packages/transaction-controller/CHANGELOG.md +++ b/packages/transaction-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `predictRelayDeposit` and `perpsRelayDeposit` to `TransactionType` enum ([#7947](https://github.com/MetaMask/core/pull/7947)) + ## [62.17.0] ### Added diff --git a/packages/transaction-controller/src/types.ts b/packages/transaction-controller/src/types.ts index 8fa03cabc0a..ae992a298d5 100644 --- a/packages/transaction-controller/src/types.ts +++ b/packages/transaction-controller/src/types.ts @@ -796,6 +796,11 @@ export enum TransactionType { */ perpsDepositAndOrder = 'perpsDepositAndOrder', + /** + * Deposit funds for a Relay quote when the parent transaction is a Perps deposit. + */ + perpsRelayDeposit = 'perpsRelayDeposit', + /** * A transaction for personal sign. */ @@ -830,6 +835,11 @@ export enum TransactionType { */ predictWithdraw = 'predictWithdraw', + /** + * Deposit funds for a Relay quote when the parent transaction is a Predict deposit. + */ + predictRelayDeposit = 'predictRelayDeposit', + /** * Deposit funds for Relay quote. */ diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index 18f4f0edb93..f116ad740d4 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed +- Replace `relayDeposit` transaction type with `predictRelayDeposit` or `perpsRelayDeposit` based on the parent transaction type ([#7947](https://github.com/MetaMask/core/pull/7947)) - Bump `@metamask/assets-controllers` from `^99.3.2` to `^99.4.0` ([#7944](https://github.com/MetaMask/core/pull/7944)) ## [15.0.1] diff --git a/packages/transaction-pay-controller/src/strategy/relay/constants.ts b/packages/transaction-pay-controller/src/strategy/relay/constants.ts index 90d21012e44..1e673068efd 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/constants.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/constants.ts @@ -1,4 +1,11 @@ +import { TransactionType } from '@metamask/transaction-controller'; + export const RELAY_URL_BASE = 'https://api.relay.link'; export const RELAY_STATUS_URL = `${RELAY_URL_BASE}/intents/status/v3`; export const RELAY_POLLING_INTERVAL = 1000; // 1 Second export const TOKEN_TRANSFER_FOUR_BYTE = '0xa9059cbb'; + +export const RELAY_DEPOSIT_TYPES: Record = { + [TransactionType.predictDeposit]: TransactionType.predictRelayDeposit, + [TransactionType.perpsDeposit]: TransactionType.perpsRelayDeposit, +}; diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts index aa45ae94639..67a5a00b34f 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.test.ts @@ -188,6 +188,57 @@ describe('Relay Submit Utils', () => { ); }); + it('uses predictRelayDeposit type when parent transaction is predictDeposit', async () => { + request.transaction = { + ...request.transaction, + type: TransactionType.predictDeposit, + } as TransactionMeta; + + await submitRelayQuotes(request); + + expect(addTransactionMock).toHaveBeenCalledTimes(1); + expect(addTransactionMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + type: TransactionType.predictRelayDeposit, + }), + ); + }); + + it('uses perpsRelayDeposit type when parent transaction is perpsDeposit', async () => { + request.transaction = { + ...request.transaction, + type: TransactionType.perpsDeposit, + } as TransactionMeta; + + await submitRelayQuotes(request); + + expect(addTransactionMock).toHaveBeenCalledTimes(1); + expect(addTransactionMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + type: TransactionType.perpsRelayDeposit, + }), + ); + }); + + it('falls back to relayDeposit type when parent transaction type is not mapped', async () => { + request.transaction = { + ...request.transaction, + type: TransactionType.simpleSend, + } as TransactionMeta; + + await submitRelayQuotes(request); + + expect(addTransactionMock).toHaveBeenCalledTimes(1); + expect(addTransactionMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + type: TransactionType.relayDeposit, + }), + ); + }); + it('adds transaction with gas fee token if isSourceGasFeeToken', async () => { request.quotes[0].fees.isSourceGasFeeToken = true; @@ -329,6 +380,60 @@ describe('Relay Submit Utils', () => { }); }); + it('uses mapped relay deposit type in batch when parent is predictDeposit', async () => { + request.transaction = { + ...request.transaction, + type: TransactionType.predictDeposit, + } as TransactionMeta; + + request.quotes[0].original.steps[0].items.push({ + ...request.quotes[0].original.steps[0].items[0], + }); + + await submitRelayQuotes(request); + + expect(addTransactionBatchMock).toHaveBeenCalledTimes(1); + expect(addTransactionBatchMock).toHaveBeenCalledWith( + expect.objectContaining({ + transactions: [ + expect.objectContaining({ + type: TransactionType.tokenMethodApprove, + }), + expect.objectContaining({ + type: TransactionType.predictRelayDeposit, + }), + ], + }), + ); + }); + + it('uses mapped relay deposit type in batch when parent is perpsDeposit', async () => { + request.transaction = { + ...request.transaction, + type: TransactionType.perpsDeposit, + } as TransactionMeta; + + request.quotes[0].original.steps[0].items.push({ + ...request.quotes[0].original.steps[0].items[0], + }); + + await submitRelayQuotes(request); + + expect(addTransactionBatchMock).toHaveBeenCalledTimes(1); + expect(addTransactionBatchMock).toHaveBeenCalledWith( + expect.objectContaining({ + transactions: [ + expect.objectContaining({ + type: TransactionType.tokenMethodApprove, + }), + expect.objectContaining({ + type: TransactionType.perpsRelayDeposit, + }), + ], + }), + ); + }); + it('adds transaction batch with gas fee token if isSourceGasFeeToken', async () => { request.quotes[0].original.steps[0].items.push({ ...request.quotes[0].original.steps[0].items[0], @@ -577,6 +682,29 @@ describe('Relay Submit Utils', () => { ); }); + it('uses mapped relay deposit type in post-quote when parent is predictDeposit', async () => { + request.transaction = { + ...request.transaction, + type: TransactionType.predictDeposit, + } as TransactionMeta; + + await submitRelayQuotes(request); + + expect(addTransactionBatchMock).toHaveBeenCalledTimes(1); + expect(addTransactionBatchMock).toHaveBeenCalledWith( + expect.objectContaining({ + transactions: [ + expect.objectContaining({ + type: TransactionType.predictDeposit, + }), + expect.objectContaining({ + type: TransactionType.predictRelayDeposit, + }), + ], + }), + ); + }); + it('sets gas to undefined when gasLimits entry is missing', async () => { request.quotes[0].original.metamask.gasLimits = []; 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 6819a7a5122..282567ab88e 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-submit.ts @@ -12,7 +12,11 @@ import type { import type { Hex } from '@metamask/utils'; import { createModuleLogger } from '@metamask/utils'; -import { RELAY_POLLING_INTERVAL, RELAY_STATUS_URL } from './constants'; +import { + RELAY_DEPOSIT_TYPES, + RELAY_POLLING_INTERVAL, + RELAY_STATUS_URL, +} from './constants'; import type { RelayQuote, RelayStatusResponse } from './types'; import { projectLogger } from '../../logger'; import type { @@ -32,39 +36,6 @@ const FALLBACK_HASH = '0x0' as Hex; const log = createModuleLogger(projectLogger, 'relay-strategy'); -/** - * Determine the transaction type for a given index in the batch. - * - * @param isPostQuote - Whether this is a post-quote flow. - * @param index - Index of the transaction in the batch. - * @param originalType - Type of the original transaction (used for post-quote index 0). - * @param relayParamCount - Number of relay-only params (excludes prepended original tx). - * @returns The transaction type. - */ -function getTransactionType( - isPostQuote: boolean | undefined, - index: number, - originalType: TransactionMeta['type'], - relayParamCount: number, -): TransactionMeta['type'] { - // Post-quote index 0 is the original transaction - if (isPostQuote && index === 0) { - return originalType; - } - - // Adjust index for post-quote flows where original tx is prepended - const relayIndex = isPostQuote ? index - 1 : index; - - // Single relay step is always a deposit (no approval needed) - if (relayParamCount === 1) { - return TransactionType.relayDeposit; - } - - return relayIndex === 0 - ? TransactionType.tokenMethodApprove - : TransactionType.relayDeposit; -} - /** * Submits Relay quotes. * @@ -316,7 +287,7 @@ async function submitTransactions( networkClientId, origin: ORIGIN_METAMASK, requireApproval: false, - type: TransactionType.relayDeposit, + type: getRelayDepositType(transaction.type), }, ); } else { @@ -382,3 +353,51 @@ async function submitTransactions( return hash as Hex; } + +/** + * Determine the transaction type for a given index in the batch. + * + * @param isPostQuote - Whether this is a post-quote flow. + * @param index - Index of the transaction in the batch. + * @param originalType - Type of the original transaction (used for post-quote index 0). + * @param relayParamCount - Number of relay-only params (excludes prepended original tx). + * @returns The transaction type. + */ +function getTransactionType( + isPostQuote: boolean | undefined, + index: number, + originalType: TransactionMeta['type'], + relayParamCount: number, +): TransactionMeta['type'] { + // Post-quote index 0 is the original transaction + if (isPostQuote && index === 0) { + return originalType; + } + + // Adjust index for post-quote flows where original tx is prepended + const relayIndex = isPostQuote ? index - 1 : index; + + const depositType = getRelayDepositType(originalType); + + // Single relay step is always a deposit (no approval needed) + if (relayParamCount === 1) { + return depositType; + } + + return relayIndex === 0 ? TransactionType.tokenMethodApprove : depositType; +} + +/** + * Get the relay deposit transaction type based on the parent transaction type. + * + * @param originalType - Type of the parent transaction. + * @returns The mapped relay deposit type, or `relayDeposit` as a fallback. + */ +function getRelayDepositType( + originalType: TransactionMeta['type'], +): TransactionType { + return ( + (originalType && RELAY_DEPOSIT_TYPES[originalType]) ?? + TransactionType.relayDeposit + ); +}