Skip to content
Open
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
4 changes: 4 additions & 0 deletions packages/transaction-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 `predictRelayDeposit` and `perpsRelayDeposit` to `TransactionType` enum ([#7947](https://github.com/MetaMask/core/pull/7947))

## [62.17.0]

### Added
Expand Down
10 changes: 10 additions & 0 deletions packages/transaction-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/transaction-pay-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Original file line number Diff line number Diff line change
@@ -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<string, TransactionType> = {
[TransactionType.predictDeposit]: TransactionType.predictRelayDeposit,
[TransactionType.perpsDeposit]: TransactionType.perpsRelayDeposit,
};
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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 = [];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
*
Expand Down Expand Up @@ -316,7 +287,7 @@ async function submitTransactions(
networkClientId,
origin: ORIGIN_METAMASK,
requireApproval: false,
type: TransactionType.relayDeposit,
type: getRelayDepositType(transaction.type),
},
);
} else {
Expand Down Expand Up @@ -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
);
}
Loading