From fe98acc0d0c6f921d845758c59457ec0b8325346 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Thu, 16 Apr 2026 14:09:55 +0100 Subject: [PATCH] fix(transaction-pay-controller): poll Across status by deposit txn ref Fix Across deposit status polling to query /deposit/status with depositTxnRef= instead of misusing the quote id as depositId. Also handle fillTxnRef in the response so the status poller can return the fill transaction reference when Across provides it. This resolves the repeated 400s seen after the source-chain tx finalized by aligning the request and response handling with Across' current status API shape. --- .../transaction-pay-controller/CHANGELOG.md | 4 +++ .../src/strategy/across/across-submit.test.ts | 25 ++++++++++++++++--- .../src/strategy/across/across-submit.ts | 15 ++++------- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/packages/transaction-pay-controller/CHANGELOG.md b/packages/transaction-pay-controller/CHANGELOG.md index 2589b78feaf..fff8b923e39 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] +### Fixed + +- Fix Across deposit status polling to query `/deposit/status` with `depositTxnRef=` and return `fillTxnRef` when Across omits the destination hash ([#8489](https://github.com/MetaMask/core/pull/8489)) + ## [19.2.0] ### Changed diff --git a/packages/transaction-pay-controller/src/strategy/across/across-submit.test.ts b/packages/transaction-pay-controller/src/strategy/across/across-submit.test.ts index 962302cf373..8bd82fab907 100644 --- a/packages/transaction-pay-controller/src/strategy/across/across-submit.test.ts +++ b/packages/transaction-pay-controller/src/strategy/across/across-submit.test.ts @@ -139,7 +139,7 @@ describe('Across Submit', () => { transactionMeta: TRANSACTION_META_MOCK, }); successfulFetchMock.mockResolvedValue({ - json: async () => ({ status: 'pending' }), + json: async () => ({ status: 'success' }), } as Response); }); @@ -576,7 +576,7 @@ describe('Across Submit', () => { ); }); - it('polls Across status endpoint when quote includes a deposit id', async () => { + it('polls Across status endpoint with depositTxnRef from the source tx hash', async () => { const confirmedTransaction = { id: 'new-tx', chainId: '0x1', @@ -633,7 +633,7 @@ describe('Across Submit', () => { }); expect(successfulFetchMock).toHaveBeenCalledWith( - expect.stringContaining('/deposit/status?'), + expect.stringContaining('/deposit/status?depositTxnRef=0xconfirmed'), expect.objectContaining({ method: 'GET' }), ); expect(result.transactionHash).toBe('0xtarget'); @@ -728,6 +728,25 @@ describe('Across Submit', () => { } }); + it('returns fill txn ref when destination hash is missing', async () => { + setupConfirmedSubmission(); + successfulFetchMock.mockResolvedValueOnce({ + json: async () => ({ + fillTxnRef: '0xfillref', + status: 'filled', + }), + } as Response); + + const result = await submitAcrossQuotes({ + messenger, + quotes: [buildDepositQuote()], + transaction: TRANSACTION_META_MOCK, + isSmartTransaction: jest.fn(), + }); + + expect(result.transactionHash).toBe('0xfillref'); + }); + it('returns fill tx hash when destination hash is missing', async () => { setupConfirmedSubmission(); successfulFetchMock.mockResolvedValueOnce({ diff --git a/packages/transaction-pay-controller/src/strategy/across/across-submit.ts b/packages/transaction-pay-controller/src/strategy/across/across-submit.ts index 4a50e9b832b..d199bd2566c 100644 --- a/packages/transaction-pay-controller/src/strategy/across/across-submit.ts +++ b/packages/transaction-pay-controller/src/strategy/across/across-submit.ts @@ -242,34 +242,28 @@ async function submitTransactions( ? getTransaction(transactionIds.slice(-1)[0], messenger)?.hash : undefined; - return await waitForAcrossCompletion( - quote.original, - hash as Hex | undefined, - messenger, - ); + return await waitForAcrossCompletion(hash as Hex | undefined, messenger); } type AcrossStatusResponse = { status?: string; destinationTxHash?: Hex; + fillTxnRef?: Hex; fillTxHash?: Hex; txHash?: Hex; }; async function waitForAcrossCompletion( - quote: AcrossQuote, transactionHash: Hex | undefined, messenger: TransactionPayControllerMessenger, ): Promise { - if (!transactionHash || !quote.quote.id) { + if (!transactionHash) { return transactionHash; } const config = getPayStrategiesConfig(messenger); const params = new URLSearchParams({ - depositId: quote.quote.id, - originChainId: String(quote.quote.swapTx.chainId), - txHash: transactionHash, + depositTxnRef: transactionHash, }); const url = `${config.across.apiBase}/deposit/status?${params.toString()}`; @@ -313,6 +307,7 @@ async function waitForAcrossCompletion( ['completed', 'filled', 'success'].includes(normalizedStatus) ) { return ( + status.fillTxnRef ?? status.destinationTxHash ?? status.fillTxHash ?? status.txHash ??