diff --git a/packages/transaction-controller/CHANGELOG.md b/packages/transaction-controller/CHANGELOG.md index 3c61b2ee26..73ce879b0d 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 `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)) diff --git a/packages/transaction-controller/src/types.ts b/packages/transaction-controller/src/types.ts index 0f07ae1443..541dd360d1 100644 --- a/packages/transaction-controller/src/types.ts +++ b/packages/transaction-controller/src/types.ts @@ -845,6 +845,11 @@ export enum TransactionType { */ predictAcrossDeposit = 'predictAcrossDeposit', + /** + * Withdraw funds for Across quote via Predict. + */ + predictAcrossWithdraw = 'predictAcrossWithdraw', + /** * Buy a position via Predict. * diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index f70e98602d..6710a8d235 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -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 diff --git a/packages/transaction-pay-controller/src/strategy/across/AcrossStrategy.test.ts b/packages/transaction-pay-controller/src/strategy/across/AcrossStrategy.test.ts index efe6eb6234..6539a87c93 100644 --- a/packages/transaction-pay-controller/src/strategy/across/AcrossStrategy.test.ts +++ b/packages/transaction-pay-controller/src/strategy/across/AcrossStrategy.test.ts @@ -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( diff --git a/packages/transaction-pay-controller/src/strategy/across/AcrossStrategy.ts b/packages/transaction-pay-controller/src/strategy/across/AcrossStrategy.ts index c54cde6187..fc141acfa0 100644 --- a/packages/transaction-pay-controller/src/strategy/across/AcrossStrategy.ts +++ b/packages/transaction-pay-controller/src/strategy/across/AcrossStrategy.ts @@ -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'; @@ -52,15 +54,20 @@ export class AcrossStrategy implements PayStrategy { } } - // 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; diff --git a/packages/transaction-pay-controller/src/strategy/across/authorization-list.ts b/packages/transaction-pay-controller/src/strategy/across/authorization-list.ts new file mode 100644 index 0000000000..c157098458 --- /dev/null +++ b/packages/transaction-pay-controller/src/strategy/across/authorization-list.ts @@ -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) + ); +} diff --git a/packages/transaction-pay-controller/src/strategy/across/requests.ts b/packages/transaction-pay-controller/src/strategy/across/requests.ts index 77b967af56..f662dc7ae6 100644 --- a/packages/transaction-pay-controller/src/strategy/across/requests.ts +++ b/packages/transaction-pay-controller/src/strategy/across/requests.ts @@ -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') ); diff --git a/packages/transaction-pay-controller/src/types.ts b/packages/transaction-pay-controller/src/types.ts index fbee108053..f1e26a291f 100644 --- a/packages/transaction-pay-controller/src/types.ts +++ b/packages/transaction-pay-controller/src/types.ts @@ -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. @@ -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; @@ -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;