From 7dc5ba8bcc16e3d40969bed68fa7d6cca469fba5 Mon Sep 17 00:00:00 2001 From: micaelae Date: Tue, 11 Aug 2026 17:10:46 -0700 Subject: [PATCH 1/6] feat: assetIdsMatch --- packages/bridge-controller/CHANGELOG.md | 1 + packages/bridge-controller/src/index.ts | 1 + packages/bridge-controller/src/utils/assets.ts | 17 +++++++++++++++++ .../src/utils/number-formatters.ts | 8 ++++++-- .../src/utils/quote-metadata/calculators.ts | 16 +++++++++------- 5 files changed, 34 insertions(+), 9 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 4c11bd7bfec..663ea21d1b6 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Export `assetIdsMatch` util to compare assetIds. EVM assetIds are case insensitive - Point Arc's native USDC to the registered `slip44:5042` asset ID instead of the `erc20:0x0000...` placeholder ([#9796](https://github.com/MetaMask/core/pull/9796)) ### Changed diff --git a/packages/bridge-controller/src/index.ts b/packages/bridge-controller/src/index.ts index 943a079c055..e8103826229 100644 --- a/packages/bridge-controller/src/index.ts +++ b/packages/bridge-controller/src/index.ts @@ -128,6 +128,7 @@ export { toQuoteMetadataV1 } from './utils/quote-metadata/to-quote-metadata-v1.j export { toQuoteMetadataV2 } from './utils/quote-metadata/to-quote-metadata-v2.js'; export { sumAmounts } from './utils/number-formatters.js'; +export { assetIdsMatch } from './utils/assets.js'; export { validateQuoteStreamComplete, diff --git a/packages/bridge-controller/src/utils/assets.ts b/packages/bridge-controller/src/utils/assets.ts index db540aa01eb..ee921e6a930 100644 --- a/packages/bridge-controller/src/utils/assets.ts +++ b/packages/bridge-controller/src/utils/assets.ts @@ -1,3 +1,4 @@ +import { KnownCaipNamespace, parseCaipAssetType } from '@metamask/utils'; import type { CaipAssetType } from '@metamask/utils'; import type { ExchangeRate, GenericQuoteRequest } from '../types.js'; @@ -39,3 +40,19 @@ export const toExchangeRates = ( }, {}); return exchangeRates; }; + +export const assetIdsMatch = ( + assetId1?: CaipAssetType, + assetId2?: CaipAssetType, +): boolean => { + if (!assetId2 || !assetId1) { + return false; + } + + return ( + assetId1 === assetId2 || + (parseCaipAssetType(assetId1).chain.namespace === + KnownCaipNamespace.Eip155 && + assetId1.toLowerCase() === assetId2.toLowerCase()) + ); +}; diff --git a/packages/bridge-controller/src/utils/number-formatters.ts b/packages/bridge-controller/src/utils/number-formatters.ts index 9ce74391085..c18723df97f 100644 --- a/packages/bridge-controller/src/utils/number-formatters.ts +++ b/packages/bridge-controller/src/utils/number-formatters.ts @@ -3,6 +3,7 @@ import { BigNumber } from 'bignumber.js'; import type { DeepPartial } from '../types.js'; import type { QuoteResponse } from '../validators/quote-response.js'; +import { assetIdsMatch } from './assets.js'; /** * 1500000 -> 1.5 @@ -82,8 +83,11 @@ export const sumAmounts = ( /** * Fees and prices can be denominated in different assets, so we need to check if all fees have the same units */ - const isSameAssetForAllFees = - new Set(fees.map((fee) => fee.asset?.assetId?.toLowerCase())).size === 1; + const isSameAssetForAllFees = fees.reduce( + (acc, fee) => + acc && assetIdsMatch(fee.asset?.assetId, fees[0]?.asset?.assetId), + true, + ); /** * Keys that require the asset to be the same for all fees diff --git a/packages/bridge-controller/src/utils/quote-metadata/calculators.ts b/packages/bridge-controller/src/utils/quote-metadata/calculators.ts index d21cdafec30..e95b6452a2d 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/calculators.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/calculators.ts @@ -22,6 +22,7 @@ import type { QuoteResponse } from '../../validators/quote-response.js'; import type { TxData } from '../../validators/trade.js'; import { isEvmQuoteResponse, isNativeAddress } from '../bridge.js'; import { calcNormalizedTokenAmount } from '../number-formatters.js'; +import { assetIdsMatch } from '../assets.js'; import type { QuoteMetadata, TokenAmountValues } from './types.js'; export const calcNonEvmTotalNetworkFee = ( @@ -76,8 +77,7 @@ export const calcSentAmount = ( .filter( (fee) => fee?.amount && - fee.asset?.assetId?.toLowerCase() === - srcAsset.assetId?.toLowerCase(), + assetIdsMatch(fee.asset?.assetId, srcAsset.assetId), ) .reduce( (acc, { amount }) => acc.plus(amount), @@ -271,10 +271,12 @@ export const calcIncludedTxFees = ( return undefined; } // Use exchange rate of the token that is being used to pay for the transaction - const { exchangeRate, usdExchangeRate } = - txFee?.asset.assetId === srcAsset.assetId - ? srcTokenExchangeRate - : destTokenExchangeRate; + const { exchangeRate, usdExchangeRate } = assetIdsMatch( + txFee?.asset?.assetId, + srcAsset.assetId, + ) + ? srcTokenExchangeRate + : destTokenExchangeRate; const normalizedTxFeeAmount = calcNormalizedTokenAmount( txFee?.amount, txFee?.asset.decimals, @@ -299,7 +301,7 @@ export const calcAdjustedReturn = ( }: QuoteResponseV1['quote'], ) => { // If gas is included and is taken from the dest token, don't subtract network fee from return - if (txFee?.asset?.assetId?.toLowerCase() === destAssetId.toLowerCase()) { + if (assetIdsMatch(txFee?.asset?.assetId, destAssetId)) { return { valueInCurrency: toTokenAmount.valueInCurrency, usd: toTokenAmount.usd, From d4e1258b044fa81db20b7fcb8ab6f650e2cbea6a Mon Sep 17 00:00:00 2001 From: micaelae Date: Tue, 11 Aug 2026 18:48:46 -0700 Subject: [PATCH 2/6] fix: filter src-side fees by assetId in toQuoteResponseV2 --- packages/bridge-controller/CHANGELOG.md | 1 + .../src/coercers/quote-response-v1-to-v2.ts | 24 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 663ea21d1b6..f754969a81a 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -24,6 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Filter fees by `assetId` when coercing V1 quotes to V2 - Populate QuotesReceived event's `usd_amount_source` property ([#9828](https://github.com/MetaMask/core/pull/9828)) ## [79.0.1] diff --git a/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts b/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts index 7c2419a212b..dd095f06b4b 100644 --- a/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts +++ b/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts @@ -1,6 +1,5 @@ import { create, coerce, Infer, is, intersection } from '@metamask/superstruct'; import { parseCaipAssetType } from '@metamask/utils'; -import { BigNumber } from 'bignumber.js'; import { formatAddressToAssetId } from '../utils/caip-formatters.js'; import { sumAmounts } from '../utils/number-formatters.js'; @@ -18,6 +17,7 @@ import { import type { QuoteResponse } from '../validators/quote-response.js'; import { QuoteSchemaV2, FeeType, QuoteSchema } from '../validators/quote.js'; import { StepSchemaV2, StepSchema } from '../validators/step.js'; +import { assetIdsMatch } from '../utils/assets.js'; const BridgeAssetV2FromV1 = coerce( BridgeAssetV2Schema, @@ -78,19 +78,19 @@ const QuoteV2FromV1 = coerce(QuoteSchemaV2, QuoteSchema, (value) => { ...restQuote } = value; + const srcAssetV2 = toBridgeAssetV2(srcAsset); + return { src: { - amount: new BigNumber(srcTokenAmount) - .plus( - intent - ? 0 - : (sumAmounts([ - feeData[FeeType.TX_FEE], - feeData[FeeType.METABRIDGE], - ])?.amount ?? 0), - ) - .toFixed(), - asset: toBridgeAssetV2(srcAsset), + amount: sumAmounts([ + { amount: srcTokenAmount, asset: srcAssetV2 }, + ...(intent + ? [] + : [feeData[FeeType.TX_FEE], feeData[FeeType.METABRIDGE]].filter( + (fee) => assetIdsMatch(fee?.asset?.assetId, srcAssetV2.assetId), + )), + ])?.amount, + asset: srcAssetV2, ...(walletAddress && { walletAddress }), }, dest: { From c7bddde16fbe8ab8e85beda4ad266ed2877ea097 Mon Sep 17 00:00:00 2001 From: micaelae Date: Tue, 11 Aug 2026 18:53:17 -0700 Subject: [PATCH 3/6] changelog --- packages/bridge-controller/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index f754969a81a..fc8bf7cc92e 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Export `assetIdsMatch` util to compare assetIds. EVM assetIds are case insensitive +- Export `assetIdsMatch` util to compare assetIds. EVM assetIds are case insensitive ([#9831](https://github.com/MetaMask/core/pull/9831)) - Point Arc's native USDC to the registered `slip44:5042` asset ID instead of the `erc20:0x0000...` placeholder ([#9796](https://github.com/MetaMask/core/pull/9796)) ### Changed @@ -24,7 +24,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Filter fees by `assetId` when coercing V1 quotes to V2 +- Filter fees by `assetId` when coercing V1 quotes to V2 ([#9831](https://github.com/MetaMask/core/pull/9831)) - Populate QuotesReceived event's `usd_amount_source` property ([#9828](https://github.com/MetaMask/core/pull/9828)) ## [79.0.1] From 6b838d7cc9847de13413f1c15672edcf8658a7ae Mon Sep 17 00:00:00 2001 From: micaelae Date: Tue, 11 Aug 2026 18:54:49 -0700 Subject: [PATCH 4/6] fix: evm check --- packages/bridge-controller/src/utils/assets.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/bridge-controller/src/utils/assets.ts b/packages/bridge-controller/src/utils/assets.ts index ee921e6a930..d3d7f7e869f 100644 --- a/packages/bridge-controller/src/utils/assets.ts +++ b/packages/bridge-controller/src/utils/assets.ts @@ -53,6 +53,8 @@ export const assetIdsMatch = ( assetId1 === assetId2 || (parseCaipAssetType(assetId1).chain.namespace === KnownCaipNamespace.Eip155 && + parseCaipAssetType(assetId2).chain.namespace === + KnownCaipNamespace.Eip155 && assetId1.toLowerCase() === assetId2.toLowerCase()) ); }; From 73fb9a08e392b019b99d5fe7cd6650ed1f83cb1e Mon Sep 17 00:00:00 2001 From: micaelae Date: Tue, 11 Aug 2026 19:15:40 -0700 Subject: [PATCH 5/6] lint --- .../bridge-controller/src/coercers/quote-response-v1-to-v2.ts | 2 +- .../bridge-controller/src/utils/quote-metadata/calculators.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts b/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts index dd095f06b4b..c6b9fea49b6 100644 --- a/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts +++ b/packages/bridge-controller/src/coercers/quote-response-v1-to-v2.ts @@ -1,6 +1,7 @@ import { create, coerce, Infer, is, intersection } from '@metamask/superstruct'; import { parseCaipAssetType } from '@metamask/utils'; +import { assetIdsMatch } from '../utils/assets.js'; import { formatAddressToAssetId } from '../utils/caip-formatters.js'; import { sumAmounts } from '../utils/number-formatters.js'; import { @@ -17,7 +18,6 @@ import { import type { QuoteResponse } from '../validators/quote-response.js'; import { QuoteSchemaV2, FeeType, QuoteSchema } from '../validators/quote.js'; import { StepSchemaV2, StepSchema } from '../validators/step.js'; -import { assetIdsMatch } from '../utils/assets.js'; const BridgeAssetV2FromV1 = coerce( BridgeAssetV2Schema, diff --git a/packages/bridge-controller/src/utils/quote-metadata/calculators.ts b/packages/bridge-controller/src/utils/quote-metadata/calculators.ts index e95b6452a2d..d060e7afeb4 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/calculators.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/calculators.ts @@ -20,9 +20,9 @@ import type { QuoteResponseV1 } from '../../validators/quote-response-v1.js'; import { QuoteResponseSchemaV2 } from '../../validators/quote-response.js'; import type { QuoteResponse } from '../../validators/quote-response.js'; import type { TxData } from '../../validators/trade.js'; +import { assetIdsMatch } from '../assets.js'; import { isEvmQuoteResponse, isNativeAddress } from '../bridge.js'; import { calcNormalizedTokenAmount } from '../number-formatters.js'; -import { assetIdsMatch } from '../assets.js'; import type { QuoteMetadata, TokenAmountValues } from './types.js'; export const calcNonEvmTotalNetworkFee = ( From 977e4495a48e607bcfc1ccac80d86f89305f1156 Mon Sep 17 00:00:00 2001 From: micaelae Date: Tue, 11 Aug 2026 19:21:41 -0700 Subject: [PATCH 6/6] changelog --- packages/bridge-controller/CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index fc8bf7cc92e..8d3d0fbb0ba 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,11 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Export `assetIdsMatch` util to compare assetIds. EVM assetIds are case insensitive ([#9831](https://github.com/MetaMask/core/pull/9831)) + +### Fixed + +- Filter fees by `assetId` when coercing V1 quotes to V2 ([#9831](https://github.com/MetaMask/core/pull/9831)) + ## [79.1.0] ### Added -- Export `assetIdsMatch` util to compare assetIds. EVM assetIds are case insensitive ([#9831](https://github.com/MetaMask/core/pull/9831)) - Point Arc's native USDC to the registered `slip44:5042` asset ID instead of the `erc20:0x0000...` placeholder ([#9796](https://github.com/MetaMask/core/pull/9796)) ### Changed @@ -24,7 +31,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed -- Filter fees by `assetId` when coercing V1 quotes to V2 ([#9831](https://github.com/MetaMask/core/pull/9831)) - Populate QuotesReceived event's `usd_amount_source` property ([#9828](https://github.com/MetaMask/core/pull/9828)) ## [79.0.1]