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-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]

### Fixed

- Support Across quotes for the Arbitrum native-token gas top-up leg of perps deposits ([#8493](https://github.com/MetaMask/core/pull/8493))

## [19.2.0]

### Changed
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { TransactionType } from '@metamask/transaction-controller';
import type { Hex } from '@metamask/utils';

import {
ARBITRUM_USDC_ADDRESS,
CHAIN_ID_ARBITRUM,
NATIVE_TOKEN_ADDRESS,
} from '../../constants';
import type { QuoteRequest } from '../../types';
import {
ACROSS_HYPERCORE_USDC_PERPS_ADDRESS,
isSupportedAcrossPerpsDepositRequest,
normalizeAcrossRequest,
} from './perps';

const REQUEST_MOCK: QuoteRequest = {
from: '0x1234567890123456789012345678901234567890' as Hex,
sourceBalanceRaw: '1000000',
sourceChainId: '0x1' as Hex,
sourceTokenAddress: '0x1111111111111111111111111111111111111111' as Hex,
sourceTokenAmount: '1000000',
targetAmountMinimum: '1000000',
targetChainId: CHAIN_ID_ARBITRUM,
targetTokenAddress: ARBITRUM_USDC_ADDRESS,
};

describe('perps', () => {
describe('isSupportedAcrossPerpsDepositRequest', () => {
it('returns true for direct deposit requests on Arbitrum USDC', () => {
expect(
isSupportedAcrossPerpsDepositRequest(
{
...REQUEST_MOCK,
targetTokenAddress: ARBITRUM_USDC_ADDRESS.toUpperCase() as Hex,
},
TransactionType.perpsDeposit,
),
).toBe(true);
});

it('returns true for Arbitrum native-token gas top-up requests', () => {
expect(
isSupportedAcrossPerpsDepositRequest(
{
...REQUEST_MOCK,
targetTokenAddress: NATIVE_TOKEN_ADDRESS,
},
TransactionType.perpsDeposit,
),
).toBe(true);
});

it('returns false for post-quote requests', () => {
expect(
isSupportedAcrossPerpsDepositRequest(
{
...REQUEST_MOCK,
isPostQuote: true,
},
TransactionType.perpsDeposit,
),
).toBe(false);
});

it('returns false for unsupported transaction types and tokens', () => {
expect(
isSupportedAcrossPerpsDepositRequest(
REQUEST_MOCK,
TransactionType.bridge,
),
).toBe(false);

expect(
isSupportedAcrossPerpsDepositRequest(
{
...REQUEST_MOCK,
targetChainId: '0x1' as Hex,
targetTokenAddress:
'0x2222222222222222222222222222222222222222' as Hex,
},
TransactionType.perpsDeposit,
),
).toBe(false);
});
});

describe('normalizeAcrossRequest', () => {
it('normalizes direct perps deposits to the Across HyperCore route', () => {
expect(
normalizeAcrossRequest(REQUEST_MOCK, TransactionType.perpsDeposit),
).toStrictEqual({
...REQUEST_MOCK,
targetAmountMinimum: '100000000',
targetChainId: '0x539',
targetTokenAddress: ACROSS_HYPERCORE_USDC_PERPS_ADDRESS,
});
});

it('does not normalize gas top-up requests', () => {
const request = {
...REQUEST_MOCK,
targetTokenAddress: NATIVE_TOKEN_ADDRESS,
};

expect(
normalizeAcrossRequest(request, TransactionType.perpsDeposit),
).toBe(request);
});

it('does not normalize non-perps requests', () => {
expect(normalizeAcrossRequest(REQUEST_MOCK, TransactionType.bridge)).toBe(
REQUEST_MOCK,
);
});
});
});
45 changes: 35 additions & 10 deletions packages/transaction-pay-controller/src/strategy/across/perps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,48 @@ import {
CHAIN_ID_ARBITRUM,
CHAIN_ID_HYPERCORE,
HYPERCORE_USDC_DECIMALS,
NATIVE_TOKEN_ADDRESS,
USDC_DECIMALS,
} from '../../constants';
import type { QuoteRequest } from '../../types';

export const ACROSS_HYPERCORE_USDC_PERPS_ADDRESS =
'0x2100000000000000000000000000000000000000' as Hex;

function isAcrossPerpsDirectDepositRequest(
request: Pick<QuoteRequest, 'targetChainId' | 'targetTokenAddress'>,
): boolean {
return (
request.targetChainId === CHAIN_ID_ARBITRUM &&
request.targetTokenAddress.toLowerCase() ===
ARBITRUM_USDC_ADDRESS.toLowerCase()
);
}

function isAcrossPerpsGasTopUpRequest(
request: Pick<QuoteRequest, 'targetChainId' | 'targetTokenAddress'>,
): boolean {
return (
request.targetChainId === CHAIN_ID_ARBITRUM &&
request.targetTokenAddress.toLowerCase() ===
NATIVE_TOKEN_ADDRESS.toLowerCase()
);
}

/**
* Detect the quote-time parent transaction shape that Across can map to the
* new HyperCore USDC-PERPS direct-deposit route.
* Detect the quote-time parent transaction shape that Across can support for
* HyperCore perps deposits.
*
* The parent transaction remains `perpsDeposit` while quotes are being
* selected. `perpsAcrossDeposit` is only assigned later to the generated
* Across submission transaction(s).
* Across submission transaction(s). At quote time Across can support:
* - the direct HyperCore USDC deposit leg
* - the destination-chain native gas top-up leg
*
* @param request - Transaction pay quote request.
* @param parentTransactionType - Parent transaction type before Across
* execution.
* @returns Whether the request matches the supported direct-deposit path.
* @returns Whether the request matches a supported perps deposit leg.
*/
export function isSupportedAcrossPerpsDepositRequest(
request: Pick<
Expand All @@ -37,9 +60,8 @@ export function isSupportedAcrossPerpsDepositRequest(
return (
parentTransactionType === TransactionType.perpsDeposit &&
request.isPostQuote !== true &&
request.targetChainId === CHAIN_ID_ARBITRUM &&
request.targetTokenAddress.toLowerCase() ===
ARBITRUM_USDC_ADDRESS.toLowerCase()
(isAcrossPerpsDirectDepositRequest(request) ||
isAcrossPerpsGasTopUpRequest(request))
);
}

Expand All @@ -48,8 +70,8 @@ export function isSupportedAcrossPerpsDepositRequest(
* direct perps deposits.
*
* Transaction pay starts from the required on-chain asset identity
* (Arbitrum USDC, 6 decimals), while Across now expects the HyperCore
* USDC-PERPS destination token (8 decimals).
* (Arbitrum USDC, 6 decimals), while Across expects the HyperCore
* USDC-PERPS destination token (8 decimals) for the USDC deposit leg.
*
* @param request - Transaction pay quote request.
* @param parentTransactionType - Parent transaction type before Across
Expand All @@ -60,7 +82,10 @@ export function normalizeAcrossRequest(
request: QuoteRequest,
parentTransactionType?: TransactionType,
): QuoteRequest {
if (!isSupportedAcrossPerpsDepositRequest(request, parentTransactionType)) {
if (
parentTransactionType !== TransactionType.perpsDeposit ||
!isAcrossPerpsDirectDepositRequest(request)
) {
return request;
}

Expand Down
Loading