Skip to content

Commit 0b86a8e

Browse files
fix: gas fee bridge to polygon native (#7053)
## Explanation Support gas fee bridge to Polygon native by getting target fiat rates using the original request rather than the quote. Also: - Use original quote if bridge quote fails to be refreshed during submission. - Only refresh quotes if transaction status is unapproved. ## References Related to [#6164](MetaMask/MetaMask-planning#6164) ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/contributing.md#updating-changelogs), highlighting breaking changes as necessary - [x] I've prepared draft pull requests for clients and consumer packages to resolve any breaking changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > Fixes bridge fiat rate resolution using request params, falls back to original quote if refresh fails during submit, and skips quote updates/refresh for non-unapproved transactions. > > - **Bridge strategy**: > - **Quotes**: Use request `source/target` chain/token for fiat rates in `normalizeQuote`; add distinct errors for missing source/target rates; enhance balance-limit logging. > - **Submit**: For subsequent quotes, wrap `refreshQuote` in try/catch and submit with original quote on failure. > - **Quotes utils**: > - `updateQuotes` returns `boolean` and early-returns `false` unless transaction `status` is `unapproved`; `refreshQuotes` logs "Refreshed" only when updates occur. > - **Tests**: Update expectations for new error messages, refresh fallback, and `updateQuotes` return value/behavior. > - **Changelog**: Document Polygon native bridge fix and the above behaviors. > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 4329c23. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY -->
1 parent de92da5 commit 0b86a8e

File tree

8 files changed

+106
-31
lines changed

8 files changed

+106
-31
lines changed

packages/transaction-pay-controller/CHANGELOG.md

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

1212
- Calculate totals even if no quotes received ([#7042](https://github.com/MetaMask/core/pull/7042))
1313

14+
### Fixed
15+
16+
- Fix bridging to native Polygon ([#7053](https://github.com/MetaMask/core/pull/7053))
17+
- Use original quote if bridge quote fails to refresh during submit.
18+
- Only refresh quotes if transaction status is unapproved.
19+
1420
## [3.0.0]
1521

1622
### Changed

packages/transaction-pay-controller/src/TransactionPayController.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ describe('TransactionPayController', () => {
4646

4747
messenger = getMessengerMock({ skipRegister: true }).messenger;
4848

49-
updateQuotesMock.mockResolvedValue();
49+
updateQuotesMock.mockResolvedValue(true);
5050
});
5151

5252
describe('updatePaymentToken', () => {

packages/transaction-pay-controller/src/strategy/bridge/bridge-quotes.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -797,15 +797,32 @@ describe('Bridge Quotes Utils', () => {
797797
});
798798
});
799799

800-
it('throws if missing fiat rate', async () => {
800+
it('throws if missing fiat source rate', async () => {
801801
getTokenFiatRateMock.mockReturnValue(undefined);
802802

803803
await expect(
804804
getBridgeQuotes({
805805
...request,
806806
requests: [QUOTE_REQUEST_1_MOCK],
807807
}),
808-
).rejects.toThrow(`Fiat rate not found for source or target token`);
808+
).rejects.toThrow(
809+
`Failed to fetch bridge quotes: Error: Fiat rate not found for source token - Chain ID: 0x1, Address: 0xabc`,
810+
);
811+
});
812+
813+
it('throws if missing fiat target rate', async () => {
814+
getTokenFiatRateMock
815+
.mockReturnValueOnce({ usdRate: '3', fiatRate: '2' })
816+
.mockReturnValueOnce(undefined);
817+
818+
await expect(
819+
getBridgeQuotes({
820+
...request,
821+
requests: [QUOTE_REQUEST_1_MOCK],
822+
}),
823+
).rejects.toThrow(
824+
`Failed to fetch bridge quotes: Error: Fiat rate not found for target token - Chain ID: 0x2, Address: 0xdef`,
825+
);
809826
});
810827

811828
it('uses defaults if no feature flags', async () => {

packages/transaction-pay-controller/src/strategy/bridge/bridge-quotes.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,13 @@ async function getSufficientSingleBridgeQuote(
270270
sourceBalanceRaw,
271271
)
272272
) {
273-
log('Reached balance limit', targetTokenAddress);
273+
log('Reached balance limit', {
274+
targetTokenAddress,
275+
sourceBalanceRaw,
276+
currentSourceAmount,
277+
attempt: i + 1,
278+
});
279+
274280
break;
275281
}
276282

@@ -467,20 +473,28 @@ function normalizeQuote(
467473
messenger: TransactionPayControllerMessenger,
468474
transaction: TransactionMeta,
469475
): TransactionPayQuote<TransactionPayBridgeQuote> {
470-
const targetFiatRate = getTokenFiatRate(
476+
const sourceFiatRate = getTokenFiatRate(
471477
messenger,
472-
quote.quote.destAsset.address as Hex,
473-
toHex(quote.quote.destChainId),
478+
request.sourceTokenAddress,
479+
request.sourceChainId,
474480
);
475481

476-
const sourceFiatRate = getTokenFiatRate(
482+
if (sourceFiatRate === undefined) {
483+
throw new Error(
484+
`Fiat rate not found for source token - Chain ID: ${request.sourceChainId}, Address: ${request.sourceTokenAddress}`,
485+
);
486+
}
487+
488+
const targetFiatRate = getTokenFiatRate(
477489
messenger,
478-
request.sourceTokenAddress,
479-
toHex(quote.quote.srcChainId),
490+
request.targetTokenAddress,
491+
request.targetChainId,
480492
);
481493

482-
if (sourceFiatRate === undefined || targetFiatRate === undefined) {
483-
throw new Error('Fiat rate not found for source or target token');
494+
if (targetFiatRate === undefined) {
495+
throw new Error(
496+
`Fiat rate not found for target token - Chain ID: ${request.targetChainId}, Address: ${request.targetTokenAddress}`,
497+
);
484498
}
485499

486500
const targetAmountMinimumFiat = calculateFiatValue(

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ describe('Bridge Submit Utils', () => {
242242
expect(updateTransactionMock).not.toHaveBeenCalled();
243243
});
244244

245-
it('refreshes quotes after the first one', async () => {
245+
it('refreshes quotes after the first', async () => {
246246
await submitBridgeQuotes(request);
247247

248248
expect(refreshQuoteMock).toHaveBeenCalledTimes(1);
@@ -253,6 +253,14 @@ describe('Bridge Submit Utils', () => {
253253
);
254254
});
255255

256+
it('does not throw if refresh fails', async () => {
257+
refreshQuoteMock.mockRejectedValueOnce(new Error('Refresh failed'));
258+
259+
await submitBridgeQuotes(request);
260+
261+
expect(submitTransactionMock).toHaveBeenCalledTimes(2);
262+
});
263+
256264
it('resolves immediately if bridge status already completed', async () => {
257265
getBridgeStatusControllerStateMock.mockReturnValue({
258266
txHistory: {

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,15 @@ export async function submitBridgeQuotes(
6464
for (const quote of quotes) {
6565
log('Submitting bridge', index, quote);
6666

67-
const finalQuote =
68-
index > 0
69-
? await refreshQuote(quote, messenger as never, transaction)
70-
: quote.original;
67+
let finalQuote = quote.original;
68+
69+
if (index > 0) {
70+
try {
71+
finalQuote = await refreshQuote(quote, messenger, transaction);
72+
} catch (error) {
73+
log('Failed to refresh subsequent quote before submit', error);
74+
}
75+
}
7176

7277
await submitBridgeTransaction(request, finalQuote);
7378

packages/transaction-pay-controller/src/utils/quotes.test.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { TransactionMeta } from '@metamask/transaction-controller';
1+
import {
2+
TransactionStatus,
3+
type TransactionMeta,
4+
} from '@metamask/transaction-controller';
25
import type { BatchTransaction } from '@metamask/transaction-controller';
36
import type { Hex, Json } from '@metamask/utils';
47
import { cloneDeep } from 'lodash';
@@ -48,6 +51,7 @@ const TRANSACTION_DATA_MOCK: TransactionData = {
4851

4952
const TRANSACTION_META_MOCK = {
5053
id: TRANSACTION_ID_MOCK,
54+
status: TransactionStatus.unapproved,
5155
txParams: { from: '0xabc' as Hex },
5256
} as TransactionMeta;
5357

@@ -94,9 +98,10 @@ describe('Quotes Utils', () => {
9498
* Run the updateQuotes function.
9599
*
96100
* @param params - Partial params to override the defaults.
101+
* @returns Return value from updateQuotes.
97102
*/
98103
async function run(params?: Partial<UpdateQuotesRequest>) {
99-
await updateQuotes({
104+
return await updateQuotes({
100105
messenger,
101106
transactionData: cloneDeep(TRANSACTION_DATA_MOCK),
102107
transactionId: TRANSACTION_ID_MOCK,
@@ -300,6 +305,18 @@ describe('Quotes Utils', () => {
300305
},
301306
});
302307
});
308+
309+
it('does nothing if transaction is not unapproved', async () => {
310+
getTransactionMock.mockReturnValue({
311+
...TRANSACTION_META_MOCK,
312+
status: TransactionStatus.confirmed,
313+
});
314+
315+
const result = await run();
316+
317+
expect(updateTransactionDataMock).not.toHaveBeenCalled();
318+
expect(result).toBe(false);
319+
});
303320
});
304321

305322
describe('refreshQuotes', () => {

packages/transaction-pay-controller/src/utils/quotes.ts

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import type { BatchTransaction } from '@metamask/transaction-controller';
1+
import {
2+
TransactionStatus,
3+
type BatchTransaction,
4+
} from '@metamask/transaction-controller';
25
import type { TransactionMeta } from '@metamask/transaction-controller';
36
import type { Hex, Json } from '@metamask/utils';
47
import { createModuleLogger } from '@metamask/utils';
@@ -34,19 +37,26 @@ export type UpdateQuotesRequest = {
3437
* Update the quotes for a specific transaction.
3538
*
3639
* @param request - Request parameters.
40+
* @returns Boolean indicating if the quotes were updated.
3741
*/
38-
export async function updateQuotes(request: UpdateQuotesRequest) {
42+
export async function updateQuotes(
43+
request: UpdateQuotesRequest,
44+
): Promise<boolean> {
3945
const { messenger, transactionData, transactionId, updateTransactionData } =
4046
request;
4147

4248
const transaction = getTransaction(transactionId, messenger);
4349

44-
log('Updating quotes', { transactionId });
45-
4650
if (!transaction || !transactionData) {
4751
throw new Error('Transaction not found');
4852
}
4953

54+
if (transaction?.status !== TransactionStatus.unapproved) {
55+
return false;
56+
}
57+
58+
log('Updating quotes', { transactionId });
59+
5060
const { paymentToken, sourceAmounts, tokens } = transactionData;
5161

5262
const requests = buildQuoteRequests({
@@ -95,6 +105,8 @@ export async function updateQuotes(request: UpdateQuotesRequest) {
95105
data.isLoading = false;
96106
});
97107
}
108+
109+
return true;
98110
}
99111

100112
/**
@@ -181,20 +193,16 @@ export async function refreshQuotes(
181193
continue;
182194
}
183195

184-
log('Refreshing expired quotes', {
185-
transactionId,
186-
strategy: strategyName,
187-
refreshInterval,
188-
});
189-
190-
await updateQuotes({
196+
const isUpdated = await updateQuotes({
191197
messenger,
192198
transactionData,
193199
transactionId,
194200
updateTransactionData,
195201
});
196202

197-
log('Refreshed quotes', { transactionId, strategy: strategyName });
203+
if (isUpdated) {
204+
log('Refreshed quotes', { transactionId, strategy: strategyName });
205+
}
198206
}
199207
}
200208

0 commit comments

Comments
 (0)