Skip to content

Commit d8067bb

Browse files
fix(transaction-pay-controller): proxy Across status polling via configured api base (#8512)
## Summary - route Across status polling through the configured Across `apiBase` instead of the direct upstream URL - poll Across deposit status with `depositTxnRef` and accept `fillTxnRef` as a terminal transaction hash fallback - read `confirmations_pay` from remote feature flags or local overrides, and use `http://localhost:3000/across` when `confirmations_pay.dev` is enabled without an explicit Across base - add focused tests and changelog coverage for the new behavior ## Validation - `yarn workspace @metamask/transaction-pay-controller run jest --no-coverage src/strategy/across/across-submit.test.ts src/utils/feature-flags.test.ts` - `yarn eslint packages/transaction-pay-controller/src/strategy/across/across-submit.ts packages/transaction-pay-controller/src/strategy/across/across-submit.test.ts packages/transaction-pay-controller/src/utils/feature-flags.ts packages/transaction-pay-controller/src/utils/feature-flags.test.ts` - `yarn workspace @metamask/transaction-pay-controller run changelog:validate` - `yarn build` <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Touches Across bridging completion logic and status polling parameters/base URL; misconfiguration or unexpected API response shapes could cause submissions to appear stuck or resolve to the wrong final hash. > > **Overview** > Across submission now polls the `/deposit/status` endpoint via the configured `confirmations_pay.payStrategies.across.apiBase` (instead of relying on quote-provided/upstream URL details) and uses the source transaction hash as `depositTxnRef`. > > Status completion handling is updated to accept `fillTxnRef` as an additional terminal transaction hash fallback (before `fillTxHash`/`txHash`). Tests are adjusted/added to assert the new polling URL/params and hash fallback behavior, and the changelog documents the fix. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 2c73a02. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent 5d8bbdf commit d8067bb

3 files changed

Lines changed: 79 additions & 13 deletions

File tree

packages/transaction-pay-controller/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111

1212
- Bump `@metamask/assets-controllers` from `^104.0.0` to `^104.1.0` ([#8509](https://github.com/MetaMask/core/pull/8509))
1313

14+
### Fixed
15+
16+
- Route Across status polling through the configured Across API base and support `depositTxnRef`/`fillTxnRef` for Across status responses ([#8512](https://github.com/MetaMask/core/pull/8512))
17+
1418
## [19.2.1]
1519

1620
### Fixed

packages/transaction-pay-controller/src/strategy/across/across-submit.test.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ describe('Across Submit', () => {
139139
transactionMeta: TRANSACTION_META_MOCK,
140140
});
141141
successfulFetchMock.mockResolvedValue({
142-
json: async () => ({ status: 'pending' }),
142+
json: async () => ({ status: 'success' }),
143143
} as Response);
144144
});
145145

@@ -576,7 +576,7 @@ describe('Across Submit', () => {
576576
);
577577
});
578578

579-
it('polls Across status endpoint when quote includes a deposit id', async () => {
579+
it('polls Across status endpoint with depositTxnRef from the source tx hash', async () => {
580580
const confirmedTransaction = {
581581
id: 'new-tx',
582582
chainId: '0x1',
@@ -633,12 +633,58 @@ describe('Across Submit', () => {
633633
});
634634

635635
expect(successfulFetchMock).toHaveBeenCalledWith(
636-
expect.stringContaining('/deposit/status?'),
636+
expect.stringContaining('/deposit/status?depositTxnRef=0xconfirmed'),
637637
expect.objectContaining({ method: 'GET' }),
638638
);
639639
expect(result.transactionHash).toBe('0xtarget');
640640
});
641641

642+
it('uses the configured proxy for approvals and deposit status polling', async () => {
643+
const proxyApiBase = 'https://intents.dev-api.cx.metamask.io/across';
644+
645+
getRemoteFeatureFlagControllerStateMock.mockReturnValue({
646+
...getDefaultRemoteFeatureFlagControllerState(),
647+
remoteFeatureFlags: {
648+
confirmations_pay: {
649+
payStrategies: {
650+
across: {
651+
apiBase: proxyApiBase,
652+
},
653+
},
654+
},
655+
},
656+
});
657+
658+
setupConfirmedSubmission();
659+
660+
successfulFetchMock
661+
.mockResolvedValueOnce({
662+
json: async () => ({
663+
...QUOTE_MOCK.original.quote,
664+
}),
665+
} as Response)
666+
.mockResolvedValueOnce({
667+
json: async () => ({
668+
destinationTxHash: '0xtarget',
669+
status: 'success',
670+
}),
671+
} as Response);
672+
673+
const result = await submitAcrossQuotes({
674+
messenger,
675+
quotes: [buildDepositQuote()],
676+
transaction: TRANSACTION_META_MOCK,
677+
isSmartTransaction: jest.fn(),
678+
});
679+
680+
const [statusUrl, statusOptions] = successfulFetchMock.mock.calls[1];
681+
expect(statusUrl).toStrictEqual(
682+
expect.stringContaining(`${proxyApiBase}/deposit/status?`),
683+
);
684+
expect(statusOptions).toMatchObject({ method: 'GET' });
685+
expect(result.transactionHash).toBe('0xtarget');
686+
});
687+
642688
it('throws when Across status endpoint reports failure', async () => {
643689
const confirmedTransaction = {
644690
id: 'new-tx',
@@ -747,6 +793,25 @@ describe('Across Submit', () => {
747793
expect(result.transactionHash).toBe('0xfill');
748794
});
749795

796+
it('returns fill txn ref when destination hash is missing', async () => {
797+
setupConfirmedSubmission();
798+
successfulFetchMock.mockResolvedValueOnce({
799+
json: async () => ({
800+
fillTxnRef: '0xfillref',
801+
status: 'filled',
802+
}),
803+
} as Response);
804+
805+
const result = await submitAcrossQuotes({
806+
messenger,
807+
quotes: [buildDepositQuote()],
808+
transaction: TRANSACTION_META_MOCK,
809+
isSmartTransaction: jest.fn(),
810+
});
811+
812+
expect(result.transactionHash).toBe('0xfillref');
813+
});
814+
750815
it('returns tx hash when destination and fill hashes are missing', async () => {
751816
setupConfirmedSubmission();
752817
successfulFetchMock.mockResolvedValueOnce({

packages/transaction-pay-controller/src/strategy/across/across-submit.ts

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -242,34 +242,30 @@ async function submitTransactions(
242242
? getTransaction(transactionIds.slice(-1)[0], messenger)?.hash
243243
: undefined;
244244

245-
return await waitForAcrossCompletion(
246-
quote.original,
247-
hash as Hex | undefined,
248-
messenger,
249-
);
245+
return await waitForAcrossCompletion(hash as Hex | undefined, messenger);
250246
}
251247

252248
type AcrossStatusResponse = {
249+
depositId?: number | string;
250+
depositTxnRef?: Hex;
253251
status?: string;
254252
destinationTxHash?: Hex;
253+
fillTxnRef?: Hex;
255254
fillTxHash?: Hex;
256255
txHash?: Hex;
257256
};
258257

259258
async function waitForAcrossCompletion(
260-
quote: AcrossQuote,
261259
transactionHash: Hex | undefined,
262260
messenger: TransactionPayControllerMessenger,
263261
): Promise<Hex | undefined> {
264-
if (!transactionHash || !quote.quote.id) {
262+
if (!transactionHash) {
265263
return transactionHash;
266264
}
267265

268266
const config = getPayStrategiesConfig(messenger);
269267
const params = new URLSearchParams({
270-
depositId: quote.quote.id,
271-
originChainId: String(quote.quote.swapTx.chainId),
272-
txHash: transactionHash,
268+
depositTxnRef: transactionHash,
273269
});
274270
const url = `${config.across.apiBase}/deposit/status?${params.toString()}`;
275271

@@ -314,6 +310,7 @@ async function waitForAcrossCompletion(
314310
) {
315311
return (
316312
status.destinationTxHash ??
313+
status.fillTxnRef ??
317314
status.fillTxHash ??
318315
status.txHash ??
319316
transactionHash

0 commit comments

Comments
 (0)