diff --git a/packages/bridge-controller/CHANGELOG.md b/packages/bridge-controller/CHANGELOG.md index 4c11bd7bfe..8d3d0fbb0b 100644 --- a/packages/bridge-controller/CHANGELOG.md +++ b/packages/bridge-controller/CHANGELOG.md @@ -7,6 +7,14 @@ 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 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 7c2419a212..c6b9fea49b 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,7 +1,7 @@ import { create, coerce, Infer, is, intersection } from '@metamask/superstruct'; import { parseCaipAssetType } from '@metamask/utils'; -import { BigNumber } from 'bignumber.js'; +import { assetIdsMatch } from '../utils/assets.js'; import { formatAddressToAssetId } from '../utils/caip-formatters.js'; import { sumAmounts } from '../utils/number-formatters.js'; import { @@ -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: { diff --git a/packages/bridge-controller/src/index.ts b/packages/bridge-controller/src/index.ts index 943a079c05..e810382622 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 db540aa01e..d3d7f7e869 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,21 @@ 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 && + parseCaipAssetType(assetId2).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 9ce7439108..c18723df97 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 d21cdafec3..d060e7afeb 100644 --- a/packages/bridge-controller/src/utils/quote-metadata/calculators.ts +++ b/packages/bridge-controller/src/utils/quote-metadata/calculators.ts @@ -20,6 +20,7 @@ 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 type { QuoteMetadata, TokenAmountValues } from './types.js'; @@ -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,