Skip to content
Merged
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 `predictAcrossWithdraw` to the `TransactionType` enum ([#8759](https://github.com/MetaMask/core/pull/8759))

### Changed

- Bump `@metamask/network-controller` from `^31.0.0` to `^31.1.0` ([#8765](https://github.com/MetaMask/core/pull/8765))
Expand Down
5 changes: 5 additions & 0 deletions packages/transaction-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,11 @@ export enum TransactionType {
*/
predictAcrossDeposit = 'predictAcrossDeposit',

/**
* Withdraw funds for Across quote via Predict.
*/
predictAcrossWithdraw = 'predictAcrossWithdraw',

/**
* Buy a position via Predict.
*
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 @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added

- Add `POLYGON_PUSD_ADDRESS` constant and treat Polymarket pUSD as a Polygon stablecoin in display/fiat-rate logic ([#8735](https://github.com/MetaMask/core/pull/8735))
- Add Across strategy plumbing to identify post-quote Predict withdraw requests ([#8759](https://github.com/MetaMask/core/pull/8759))

### Fixed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,60 @@ describe('AcrossStrategy', () => {
).toBe(true);
});

it('supports post-quote predict withdraw requests with source-chain authorization lists', () => {
const strategy = new AcrossStrategy();
expect(
strategy.supports({
...baseRequest,
transaction: {
...TRANSACTION_META_MOCK,
nestedTransactions: [{ type: TransactionType.predictWithdraw }],
txParams: {
...TRANSACTION_META_MOCK.txParams,
authorizationList: [{ address: '0xabc' as Hex }],
data: '0x12345678' as Hex,
to: '0xdef' as Hex,
},
} as TransactionMeta,
requests: [
{
from: '0xabc' as Hex,
isPostQuote: true,
sourceBalanceRaw: '100',
sourceChainId: '0x1' as Hex,
sourceTokenAddress: '0xabc' as Hex,
sourceTokenAmount: '100',
targetAmountMinimum: '0',
targetChainId: '0x2' as Hex,
targetTokenAddress: '0xdef' as Hex,
},
],
}),
).toBe(true);
});

it('does not support post-quote requests outside predict withdraw', () => {
const strategy = new AcrossStrategy();
expect(
strategy.supports({
...baseRequest,
requests: [
{
from: '0xabc' as Hex,
isPostQuote: true,
sourceBalanceRaw: '100',
sourceChainId: '0x1' as Hex,
sourceTokenAddress: '0xabc' as Hex,
sourceTokenAmount: '100',
targetAmountMinimum: '0',
targetChainId: '0x2' as Hex,
targetTokenAddress: '0xdef' as Hex,
},
],
}),
).toBe(false);
});

it('returns false for unsupported perps deposits', () => {
const strategy = new AcrossStrategy();
expect(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ import type {
TransactionPayQuote,
} from '../../types';
import { getPayStrategiesConfig } from '../../utils/feature-flags';
import { isPredictWithdrawTransaction } from '../../utils/transaction';
import { getAcrossDestination } from './across-actions';
import { getAcrossQuotes } from './across-quotes';
import { submitAcrossQuotes } from './across-submit';
import { hasUnsupportedTransactionAuthorizationList } from './authorization-list';
import { isSupportedAcrossPerpsDepositRequest } from './perps';
import { isAcrossQuoteRequest } from './requests';
import type { AcrossQuote } from './types';
Expand Down Expand Up @@ -52,15 +54,20 @@ export class AcrossStrategy implements PayStrategy<AcrossQuote> {
}
}

// Across cannot submit EIP-7702 authorization lists. This pre-quote check
// catches transactions where the authorization list is already present.
// First-time 7702 upgrades discovered during gas planning are handled in
// `checkQuoteSupport` below.
if (request.transaction.txParams?.authorizationList?.length) {
if (
hasUnsupportedTransactionAuthorizationList(
request.transaction,
actionableRequests,
)
) {
return false;
}

return actionableRequests.every((singleRequest) => {
if (singleRequest.isPostQuote) {
return isPredictWithdrawTransaction(request.transaction);
}

try {
getAcrossDestination(request.transaction, singleRequest);
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { TransactionMeta } from '@metamask/transaction-controller';

import type { QuoteRequest } from '../../types';
import { isPredictWithdrawTransaction } from '../../utils/transaction';

/**
* Check whether an authorization list on the original transaction is unsupported by Across.
*
* Predict withdraw post-quote requests do not use Across destination actions;
* the original withdrawal is submitted by MetaMask on the source chain before
* the Across deposit leg. That keeps a source-chain authorization list out of
* Across' post-swap action payload.
*
* @param transaction - Original transaction metadata.
* @param requests - Across quote requests.
* @returns `true` if the authorization list should block Across.
*/
export function hasUnsupportedTransactionAuthorizationList(
transaction: TransactionMeta,
requests: QuoteRequest[],
): boolean {
if (!transaction.txParams?.authorizationList?.length) {
return false;
}

return (
!isPredictWithdrawTransaction(transaction) ||
requests.some((request) => request.isPostQuote !== true)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { QuoteRequest } from '../../types';
export function isAcrossQuoteRequest(request: QuoteRequest): boolean {
return (
request.isMaxAmount === true ||
request.isPostQuote === true ||
(request.targetAmountMinimum !== undefined &&
request.targetAmountMinimum !== '0')
);
Expand Down
12 changes: 6 additions & 6 deletions packages/transaction-pay-controller/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,8 @@ export type TransactionConfig = {
isPostQuote?: boolean;

/**
* Optional address to receive refunds if the Relay transaction fails.
* When set, overrides the default refund recipient (EOA) in the Relay quote
* Optional address to receive refunds if the quote provider transaction fails.
* When set, overrides the default refund recipient (EOA) in the quote
* request. Use this for post-quote flows where the user's funds originate
* from a smart contract account (e.g. Predict Safe proxy) so that refunds
* go back to that account rather than the EOA.
Expand Down Expand Up @@ -224,8 +224,8 @@ export type TransactionData = {
isHyperliquidSource?: boolean;

/**
* Optional address to receive refunds if the Relay transaction fails.
* When set, overrides the default refund recipient (EOA) in the Relay quote
* Optional address to receive refunds if the quote provider transaction fails.
* When set, overrides the default refund recipient (EOA) in the quote
* request.
*/
refundTo?: Hex;
Expand Down Expand Up @@ -403,8 +403,8 @@ export type QuoteRequest = {
isHyperliquidSource?: boolean;

/**
* Optional address to receive refunds if the Relay transaction fails.
* When set, overrides the default refund recipient (EOA) in the Relay quote
* Optional address to receive refunds if the quote provider transaction fails.
* When set, overrides the default refund recipient (EOA) in the quote
* request.
*/
refundTo?: Hex;
Expand Down
Loading