diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index 9c9eded4ce..eee671e161 100644 --- a/packages/transaction-pay-controller/CHANGELOG.md +++ b/packages/transaction-pay-controller/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add `getPaymentOverrideData` callback to `TransactionPayControllerOptions`, when `paymentOverride` is defined on a transaction, this callback is invoked the resulting transactions are injected into the relay quote steps ([#8858](https://github.com/MetaMask/core/pull/8858)) + ## [22.7.0] ### Added diff --git a/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts b/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts index 8afd1b0559..45671cd85a 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController-method-action-types.ts @@ -49,6 +49,22 @@ export type TransactionPayControllerUpdateFiatPaymentAction = { handler: TransactionPayController['updateFiatPayment']; }; +/** + * Returns additional transactions for the paymentOverride flow. + * + * Delegates to the client-supplied callback. Called during quote execution + * when paymentOverride is true. Returns an empty array when no callback + * is configured. + * + * @param args - The arguments forwarded to the {@link GetPaymentOverrideDataCallback}, + * containing the transaction ID. + * @returns A promise resolving to the additional transactions array. + */ +export type TransactionPayControllerGetPaymentOverrideDataAction = { + type: `TransactionPayController:getPaymentOverrideData`; + handler: TransactionPayController['getPaymentOverrideData']; +}; + /** * Gets the delegation transaction for a given transaction. * @@ -113,6 +129,7 @@ export type TransactionPayControllerMethodActions = | TransactionPayControllerUpdatePaymentTokenAction | TransactionPayControllerUpdateFiatPaymentAction | TransactionPayControllerGetDelegationTransactionAction + | TransactionPayControllerGetPaymentOverrideDataAction | TransactionPayControllerGetStrategyAction | TransactionPayControllerPolymarketGetDepositWalletAddressAction | TransactionPayControllerPolymarketSubmitDepositWalletBatchAction; diff --git a/packages/transaction-pay-controller/src/TransactionPayController.test.ts b/packages/transaction-pay-controller/src/TransactionPayController.test.ts index a8a93c81cc..26d8d43039 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.test.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.test.ts @@ -469,6 +469,43 @@ describe('TransactionPayController', () => { }); }); + describe('getPaymentOverrideData', () => { + it('delegates to the callback', async () => { + const txMock = { from: '0xabc', to: '0xdef' }; + const getPaymentOverrideDataMock = jest.fn().mockResolvedValue([txMock]); + + new TransactionPayController({ + getDelegationTransaction: jest.fn(), + getPaymentOverrideData: getPaymentOverrideDataMock, + messenger, + }); + + const result = await messenger.call( + 'TransactionPayController:getPaymentOverrideData', + TRANSACTION_ID_MOCK, + ); + + expect(getPaymentOverrideDataMock).toHaveBeenCalledWith( + TRANSACTION_ID_MOCK, + ); + expect(result).toStrictEqual([txMock]); + }); + + it('returns empty array when no callback is configured', async () => { + new TransactionPayController({ + getDelegationTransaction: jest.fn(), + messenger, + }); + + const result = await messenger.call( + 'TransactionPayController:getPaymentOverrideData', + TRANSACTION_ID_MOCK, + ); + + expect(result).toStrictEqual([]); + }); + }); + describe('polymarket callbacks', () => { const EOA_MOCK = '0x1111111111111111111111111111111111111111' as Hex; const DEPOSIT_WALLET_MOCK = diff --git a/packages/transaction-pay-controller/src/TransactionPayController.ts b/packages/transaction-pay-controller/src/TransactionPayController.ts index 567bfedabc..b0d0eafb20 100644 --- a/packages/transaction-pay-controller/src/TransactionPayController.ts +++ b/packages/transaction-pay-controller/src/TransactionPayController.ts @@ -15,6 +15,7 @@ import { QuoteRefresher } from './helpers/QuoteRefresher'; import { deriveFiatAssetForFiatPayment } from './strategy/fiat/utils'; import type { GetDelegationTransactionCallback, + GetPaymentOverrideDataCallback, PolymarketCallbacks, TransactionConfigCallback, TransactionData, @@ -36,6 +37,7 @@ import { const MESSENGER_EXPOSED_METHODS = [ 'getDelegationTransaction', + 'getPaymentOverrideData', 'getStrategy', 'polymarketGetDepositWalletAddress', 'polymarketSubmitDepositWalletBatch', @@ -64,6 +66,8 @@ export class TransactionPayController extends BaseController< > { readonly #getDelegationTransaction: GetDelegationTransactionCallback; + readonly #getPaymentOverrideData?: GetPaymentOverrideDataCallback; + readonly #getStrategy?: ( transaction: TransactionMeta, ) => TransactionPayStrategy; @@ -76,6 +80,7 @@ export class TransactionPayController extends BaseController< constructor({ getDelegationTransaction, + getPaymentOverrideData, getStrategy, getStrategies, messenger, @@ -90,6 +95,7 @@ export class TransactionPayController extends BaseController< }); this.#getDelegationTransaction = getDelegationTransaction; + this.#getPaymentOverrideData = getPaymentOverrideData; this.#getStrategy = getStrategy; this.#getStrategies = getStrategies; this.#polymarket = polymarket; @@ -217,6 +223,23 @@ export class TransactionPayController extends BaseController< return this.#getDelegationTransaction(...args); } + /** + * Returns additional transactions for the paymentOverride flow. + * + * Delegates to the client-supplied {@link GetPaymentOverrideDataCallback}. + * Called during quote execution when `paymentOverride` is defined on the transaction. + * Returns an empty array when no callback is configured. + * + * @param args - The arguments forwarded to the {@link GetPaymentOverrideDataCallback}, + * containing the transaction ID. + * @returns A promise resolving to the additional transactions array. + */ + getPaymentOverrideData( + ...args: Parameters + ): ReturnType { + return this.#getPaymentOverrideData?.(...args) ?? Promise.resolve([]); + } + /** * Gets the preferred strategy for a transaction. * diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts index 9439faf4cc..448d04fd30 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.test.ts @@ -14,6 +14,7 @@ import { CHAIN_ID_HYPERCORE, CHAIN_ID_POLYGON, NATIVE_TOKEN_ADDRESS, + PaymentOverride, POLYGON_USDCE_ADDRESS, } from '../../constants'; import { getMessengerMock } from '../../tests/messenger-mock'; @@ -183,6 +184,7 @@ describe('Relay Quotes Utils', () => { getDelegationTransactionMock, getGasFeeTokensMock, getKeyringControllerStateMock, + getPaymentOverrideDataMock, getRemoteFeatureFlagControllerStateMock, polymarketGetDepositWalletAddressMock, } = getMessengerMock(); @@ -3375,6 +3377,159 @@ describe('Relay Quotes Utils', () => { }); }); + describe('paymentOverride step injection (paymentOverride defined)', () => { + const PAYMENT_OVERRIDE_TX_MOCK = { + from: FROM_MOCK, + to: '0xpaymentoverride' as Hex, + data: '0xpaymentoverride' as Hex, + value: '0x0', + maxFeePerGas: '1000000000', + maxPriorityFeePerGas: '2000000000', + }; + + beforeEach(() => { + successfulFetchMock.mockResolvedValue({ + ok: true, + json: async () => QUOTE_MOCK, + } as never); + + estimateGasBatchMock.mockResolvedValue({ + gasLimits: [30000, 21000], + totalGasLimit: 51000, + }); + }); + + it('prepends paymentOverride step before relay steps for standard (non-post-quote) flow', async () => { + getPaymentOverrideDataMock.mockResolvedValue([ + PAYMENT_OVERRIDE_TX_MOCK, + ]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + }, + ], + transaction: TRANSACTION_META_MOCK, + }); + + const txSteps = quote.original.steps.filter( + (step): step is RelayTransactionStep => step.kind === 'transaction', + ); + expect(txSteps[0].id).toBe('payment-override'); + expect(txSteps[0].items[0].data.to).toBe(PAYMENT_OVERRIDE_TX_MOCK.to); + expect(txSteps[1].id).toBe(STEP_MOCK.id); + }); + + it('appends paymentOverride step after relay steps for post-quote flow', async () => { + getPaymentOverrideDataMock.mockResolvedValue([ + PAYMENT_OVERRIDE_TX_MOCK, + ]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + isPostQuote: true, + }, + ], + transaction: TRANSACTION_META_MOCK, + }); + + const txSteps = quote.original.steps.filter( + (step): step is RelayTransactionStep => step.kind === 'transaction', + ); + expect(txSteps[0].id).toBe(STEP_MOCK.id); + expect(txSteps[1].id).toBe('payment-override'); + }); + + it('does not inject when paymentOverride is not defined', async () => { + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [QUOTE_REQUEST_MOCK], + transaction: TRANSACTION_META_MOCK, + }); + + expect( + quote.original.steps.every((step) => step.id !== 'payment-override'), + ).toBe(true); + expect(getPaymentOverrideDataMock).not.toHaveBeenCalled(); + }); + + it('does not inject when callback returns empty array', async () => { + getPaymentOverrideDataMock.mockResolvedValue([]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + }, + ], + transaction: TRANSACTION_META_MOCK, + }); + + expect(quote.original.steps).toHaveLength(1); + expect(quote.original.steps[0].id).toBe(STEP_MOCK.id); + }); + + it('uses the transaction chainId when present', async () => { + getPaymentOverrideDataMock.mockResolvedValue([ + { ...PAYMENT_OVERRIDE_TX_MOCK, chainId: '0xa' }, + ]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + }, + ], + transaction: TRANSACTION_META_MOCK, + }); + + const overrideStep = quote.original.steps.find( + (step) => step.id === 'payment-override', + ) as RelayTransactionStep; + expect(overrideStep.items[0].data.chainId).toBe(10); + }); + + it('falls back to sourceChainId when transaction chainId is absent', async () => { + getPaymentOverrideDataMock.mockResolvedValue([ + PAYMENT_OVERRIDE_TX_MOCK, + ]); + + const [quote] = await getRelayQuotes({ + accountSupports7702: true, + messenger, + requests: [ + { + ...QUOTE_REQUEST_MOCK, + paymentOverride: PaymentOverride.MoneyAccount, + }, + ], + transaction: TRANSACTION_META_MOCK, + }); + + const overrideStep = quote.original.steps.find( + (step) => step.id === 'payment-override', + ) as RelayTransactionStep; + // QUOTE_REQUEST_MOCK.sourceChainId is '0x1' → chainId 1 + expect(overrideStep.items[0].data.chainId).toBe(1); + }); + }); + describe('gas buffer support', () => { it('applies buffer to single transaction gas estimate', async () => { const quoteMock = cloneDeep(QUOTE_MOCK); diff --git a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts index 08077cd823..2c5c9c33d8 100644 --- a/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts +++ b/packages/transaction-pay-controller/src/strategy/relay/relay-quotes.ts @@ -2,7 +2,10 @@ import { Interface } from '@ethersproject/abi'; import { toHex } from '@metamask/controller-utils'; -import type { TransactionMeta } from '@metamask/transaction-controller'; +import type { + TransactionMeta, + TransactionParams, +} from '@metamask/transaction-controller'; import type { Hex } from '@metamask/utils'; import { createModuleLogger } from '@metamask/utils'; import { BigNumber } from 'bignumber.js'; @@ -275,7 +278,11 @@ async function getSingleQuote( log('Fetched relay quote', quote); - return await normalizeQuote(quote, request, fullRequest); + const quoteWithDeposits = request.paymentOverride + ? await injectPaymentOverrideSteps(quote, request, fullRequest) + : quote; + + return await normalizeQuote(quoteWithDeposits, request, fullRequest); } catch (error) { log('Error fetching relay quote', error); throw error; @@ -1015,6 +1022,93 @@ function getSubsidizedFeeAmountUsd(quote: RelayQuote): BigNumber { return isSubsidizedStablecoin ? amountFormatted : amountUsd; } +/** + * Fetches transactions from the paymentOverride callback and injects + * them into the relay quote's steps so they are submitted alongside the relay + * transactions and included in gas estimation. + * + * For standard flows (`isPostQuote` false) the step is prepended so it + * executes before the relay bridge transaction. For post-quote flows it is + * appended so it executes after. + * + * @param quote - Relay quote to mutate in place. + * @param request - Quote request, used to determine ordering and chain ID. + * @param fullRequest - Full quotes request, provides messenger and transaction ID. + * @returns The relay quote with paymentOverride steps injected, or the original quote if the callback returns no transactions. + */ +async function injectPaymentOverrideSteps( + quote: RelayQuote, + request: QuoteRequest, + fullRequest: PayStrategyGetQuotesRequest, +): Promise { + const { messenger, transaction } = fullRequest; + + const overrideTxs = await messenger.call( + 'TransactionPayController:getPaymentOverrideData', + transaction.id, + ); + + if (!overrideTxs.length) { + return quote; + } + + const overrideStep = buildPaymentOverrideStep( + overrideTxs, + request.sourceChainId, + ); + + const steps = request.isPostQuote + ? [...quote.steps, overrideStep] + : [overrideStep, ...quote.steps]; + + log('Injected paymentOverride step', { + transactionId: transaction.id, + isPostQuote: request.isPostQuote, + txCount: overrideTxs.length, + }); + + return { ...quote, steps }; +} + +/** + * Converts an array of TransactionParams into a single RelayTransactionStep + * so they can be injected into a relay quote's steps array. + * + * Each transaction's `chainId` is used when present; `sourceChainId` is the + * fallback for transactions that do not specify one. + * + * @param txParams - Transactions from the paymentOverride callback. + * @param sourceChainId - Fallback hex chain ID used when a transaction omits its own. + * @returns A relay transaction step wrapping the paymentOverride transactions. + */ +function buildPaymentOverrideStep( + txParams: TransactionParams[], + sourceChainId: Hex, +): RelayTransactionStep { + const fallbackChainId = parseInt(sourceChainId, 16); + + return { + id: 'payment-override', + kind: 'transaction', + requestId: 'payment-override', + items: txParams.map((params) => ({ + check: { endpoint: '', method: 'GET' as const }, + status: 'incomplete' as const, + data: { + chainId: params.chainId + ? parseInt(params.chainId, 16) + : fallbackChainId, + data: (params.data as Hex) ?? '0x', + from: params.from as Hex, + maxFeePerGas: params.maxFeePerGas as string, + maxPriorityFeePerGas: params.maxPriorityFeePerGas as string, + to: params.to as Hex, + value: params.value, + }, + })), + }; +} + function isStablecoin(chainId: string, tokenAddress: string): boolean { return Boolean( STABLECOINS[chainId as Hex]?.includes(tokenAddress.toLowerCase() as Hex), diff --git a/packages/transaction-pay-controller/src/tests/messenger-mock.ts b/packages/transaction-pay-controller/src/tests/messenger-mock.ts index 1931aa202a..0a0892b0c1 100644 --- a/packages/transaction-pay-controller/src/tests/messenger-mock.ts +++ b/packages/transaction-pay-controller/src/tests/messenger-mock.ts @@ -29,6 +29,7 @@ import type { TransactionControllerUpdateTransactionAction } from '@metamask/tra import type { TransactionPayControllerMessenger } from '..'; import type { TransactionPayControllerGetDelegationTransactionAction, + TransactionPayControllerGetPaymentOverrideDataAction, TransactionPayControllerGetStrategyAction, TransactionPayControllerPolymarketGetDepositWalletAddressAction, TransactionPayControllerPolymarketSubmitDepositWalletBatchAction, @@ -120,6 +121,10 @@ export function getMessengerMock({ TransactionPayControllerGetDelegationTransactionAction['handler'] > = jest.fn(); + const getPaymentOverrideDataMock: jest.MockedFn< + TransactionPayControllerGetPaymentOverrideDataAction['handler'] + > = jest.fn().mockResolvedValue([]); + const polymarketGetDepositWalletAddressMock: jest.MockedFn< TransactionPayControllerPolymarketGetDepositWalletAddressAction['handler'] > = jest.fn(); @@ -255,6 +260,11 @@ export function getMessengerMock({ getDelegationTransactionMock, ); + messenger.registerActionHandler( + 'TransactionPayController:getPaymentOverrideData', + getPaymentOverrideDataMock, + ); + messenger.registerActionHandler( 'TransactionPayController:polymarketGetDepositWalletAddress', polymarketGetDepositWalletAddressMock, @@ -306,6 +316,7 @@ export function getMessengerMock({ getControllerStateMock, getCurrencyRateControllerStateMock, getDelegationTransactionMock, + getPaymentOverrideDataMock, getGasFeeControllerStateMock, getGasFeeTokensMock, getKeyringControllerStateMock, diff --git a/packages/transaction-pay-controller/src/types.ts b/packages/transaction-pay-controller/src/types.ts index d6795dccdf..119a005a80 100644 --- a/packages/transaction-pay-controller/src/types.ts +++ b/packages/transaction-pay-controller/src/types.ts @@ -54,6 +54,7 @@ import type { TransactionControllerStateChangeEvent, TransactionControllerUpdateTransactionAction, TransactionMeta, + TransactionParams, } from '@metamask/transaction-controller'; import type { Hex, Json } from '@metamask/utils'; import type { Draft } from 'immer'; @@ -152,6 +153,15 @@ export type TransactionConfig = { /** Callback to update transaction config. */ export type TransactionConfigCallback = (config: TransactionConfig) => void; +/** + * Callback invoked during quote execution when `paymentOverride` is defined. + * Returns additional transactions to be submitted alongside the quote, + * ordered before or after the quote batch depending on `isPostQuote`. + */ +export type GetPaymentOverrideDataCallback = ( + transactionId: string, +) => Promise; + /** Callback to update fiat payment state. */ export type TransactionFiatPaymentCallback = ( fiatPayment: TransactionFiatPayment, @@ -203,6 +213,12 @@ export type TransactionPayControllerOptions = { /** Callbacks for the Polymarket relayer; required only for the Polymarket deposit-wallet flow. */ polymarket?: PolymarketCallbacks; + /** + * Optional callback invoked during quote execution when `paymentOverride` is defined. + * Returns additional transactions to be submitted alongside the quote batch. + */ + getPaymentOverrideData?: GetPaymentOverrideDataCallback; + /** Initial state of the controller. */ state?: Partial; }; @@ -424,6 +440,9 @@ export type QuoteRequest = { /** Whether the source of funds is a Polymarket deposit wallet. */ isPolymarketDepositWallet?: boolean; + /** Overrides the payment source for the transaction. */ + paymentOverride?: PaymentOverride; + /** * Optional address to receive refunds if the quote provider transaction fails. * When set, overrides the default refund recipient (EOA) in the quote diff --git a/packages/transaction-pay-controller/src/utils/quotes.ts b/packages/transaction-pay-controller/src/utils/quotes.ts index ba2ec2afd7..e332b63285 100644 --- a/packages/transaction-pay-controller/src/utils/quotes.ts +++ b/packages/transaction-pay-controller/src/utils/quotes.ts @@ -4,7 +4,7 @@ import type { TransactionMeta } from '@metamask/transaction-controller'; import type { Hex, Json } from '@metamask/utils'; import { createModuleLogger } from '@metamask/utils'; -import { TransactionPayStrategy } from '../constants'; +import { PaymentOverride, TransactionPayStrategy } from '../constants'; import { projectLogger } from '../logger'; import type { QuoteRequest, @@ -86,6 +86,7 @@ export async function updateQuotes( isPostQuote, isHyperliquidSource, isPolymarketDepositWallet, + paymentOverride, paymentToken: originalPaymentToken, refundTo, sourceAmounts, @@ -122,6 +123,7 @@ export async function updateQuotes( isPostQuote, isHyperliquidSource, isPolymarketDepositWallet, + paymentOverride, paymentToken, refundTo, sourceAmounts, @@ -326,6 +328,7 @@ function clearControllerIfCurrent( * @param request.isHyperliquidSource - Whether the source of funds is HyperLiquid. * @param request.isPolymarketDepositWallet - Whether the source of funds is a Polymarket deposit wallet. * @param request.isPostQuote - Whether this is a post-quote flow. + * @param request.paymentOverride - Optional payment override type for the transaction. * @param request.paymentToken - Payment token (source for standard flows, destination for post-quote). * @param request.refundTo - Optional address to receive refunds if the Relay transaction fails. * @param request.sourceAmounts - Source amounts for the transaction. @@ -339,6 +342,7 @@ function buildQuoteRequests({ isPostQuote, isHyperliquidSource, isPolymarketDepositWallet, + paymentOverride, paymentToken, refundTo, sourceAmounts, @@ -350,6 +354,7 @@ function buildQuoteRequests({ isPostQuote?: boolean; isHyperliquidSource?: boolean; isPolymarketDepositWallet?: boolean; + paymentOverride?: PaymentOverride; paymentToken: TransactionPaymentToken | undefined; refundTo?: Hex; sourceAmounts: TransactionPaySourceAmount[] | undefined; @@ -366,6 +371,7 @@ function buildQuoteRequests({ isMaxAmount, isHyperliquidSource, isPolymarketDepositWallet, + paymentOverride, destinationToken: paymentToken, refundTo, sourceAmounts, @@ -382,6 +388,7 @@ function buildQuoteRequests({ return { from, isMaxAmount, + paymentOverride, sourceBalanceRaw: paymentToken.balanceRaw, sourceTokenAmount: sourceAmount.sourceAmountRaw, sourceChainId: paymentToken.chainId, @@ -409,6 +416,7 @@ function buildQuoteRequests({ * @param request.isMaxAmount - Whether the transaction is a maximum amount transaction. * @param request.isHyperliquidSource - Whether the source of funds is HyperLiquid. * @param request.isPolymarketDepositWallet - Whether the source of funds is a Polymarket deposit wallet. + * @param request.paymentOverride - Optional payment override type for the transaction. * @param request.destinationToken - Destination token (paymentToken in post-quote mode). * @param request.refundTo - Optional address to receive refunds if the Relay transaction fails. * @param request.sourceAmounts - Source amounts for the transaction (includes source token info). @@ -420,6 +428,7 @@ function buildPostQuoteRequests({ isMaxAmount, isHyperliquidSource, isPolymarketDepositWallet, + paymentOverride, destinationToken, refundTo, sourceAmounts, @@ -429,6 +438,7 @@ function buildPostQuoteRequests({ isMaxAmount: boolean; isHyperliquidSource?: boolean; isPolymarketDepositWallet?: boolean; + paymentOverride?: PaymentOverride; destinationToken: TransactionPaymentToken; refundTo?: Hex; sourceAmounts: TransactionPaySourceAmount[] | undefined; @@ -459,6 +469,7 @@ function buildPostQuoteRequests({ isPostQuote: true, isHyperliquidSource, isPolymarketDepositWallet, + paymentOverride, refundTo, sourceBalanceRaw: sourceAmount.sourceBalanceRaw, sourceTokenAmount: sourceAmount.sourceAmountRaw,