diff --git a/packages/assets-controller/CHANGELOG.md b/packages/assets-controller/CHANGELOG.md index 45acf6cd067..0868c3b977f 100644 --- a/packages/assets-controller/CHANGELOG.md +++ b/packages/assets-controller/CHANGELOG.md @@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/transaction-controller` from `^69.6.1` to `^69.7.0` ([#10046](https://github.com/MetaMask/core/pull/10046)) +### Fixed + +- Preserve EVM bridge exchange-rate data and omit non-EVM conversion rates when the selected currency has no CAIP mapping, instead of discarding the whole result ([#10064](https://github.com/MetaMask/core/pull/10064)) + ## [14.0.3] ### Changed diff --git a/packages/assets-controller/src/utils/formatExchangeRatesForBridge.test.ts b/packages/assets-controller/src/utils/formatExchangeRatesForBridge.test.ts index 6af339fd958..56845b5073b 100644 --- a/packages/assets-controller/src/utils/formatExchangeRatesForBridge.test.ts +++ b/packages/assets-controller/src/utils/formatExchangeRatesForBridge.test.ts @@ -394,6 +394,129 @@ describe('formatExchangeRatesForBridge', () => { ); }); + describe('currencies absent from MAP_CAIP_CURRENCIES', () => { + // A deliberately fictional currency code: every real SupportedCurrency is + // now present in MAP_CAIP_CURRENCIES (see the 'gel' tests below), so this + // exercises the defensive path for a genuinely unmapped/invalid value. + const UNMAPPED_CURRENCY = 'xyz'; + + it('still returns EVM native marketData and currencyRates when selectedCurrency has no CAIP mapping', () => { + const ethNativeId = 'eip155:1/slip44:60'; + const result = formatExchangeRatesForBridge({ + assetsInfo: { [ethNativeId]: NATIVE_METADATA }, + assetsPrice: { + [ethNativeId]: price({ + price: 2000, + id: 'ethereum', + marketCap: 240_000_000_000, + lastUpdated: 1_700_000_000_000, + }), + }, + selectedCurrency: UNMAPPED_CURRENCY, + nativeAssetIdentifiers: { 'eip155:1': ethNativeId }, + networkConfigurationsByChainId: EVM_NETWORK_CONFIGS, + }); + + expect(result.currencyRates.ETH).toStrictEqual({ + conversionDate: 1_700_000_000, + conversionRate: 2000, + usdConversionRate: 2000, + }); + + const chainData = result.marketData['0x1']; + expect(chainData).toBeDefined(); + const nativeAddress = '0x0000000000000000000000000000000000000000'; + expect(chainData[nativeAddress]).toBeDefined(); + expect(chainData[nativeAddress].currency).toBe('ETH'); + expect(result.currentCurrency).toBe(UNMAPPED_CURRENCY); + }); + + it('still returns EVM ERC20 marketData when selectedCurrency has no CAIP mapping', () => { + const ethNativeId = 'eip155:1/slip44:60'; + const usdcId = + 'eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; + const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; + const result = formatExchangeRatesForBridge({ + assetsInfo: { + [ethNativeId]: NATIVE_METADATA, + [usdcId]: { + type: 'erc20', + decimals: 6, + symbol: 'USDC', + } as AssetMetadata, + }, + assetsPrice: { + [ethNativeId]: price({ price: 2000 }), + [usdcId]: price({ price: 1, id: 'usd-coin' }), + }, + selectedCurrency: UNMAPPED_CURRENCY, + nativeAssetIdentifiers: { 'eip155:1': ethNativeId }, + networkConfigurationsByChainId: EVM_NETWORK_CONFIGS, + }); + + expect(result.marketData['0x1']?.[usdcAddress]).toBeDefined(); + }); + + it('omits (rather than mislabels) the non-EVM conversionRates entry when selectedCurrency has no CAIP mapping', () => { + // price (unlike usdPrice, which is always USD) for a non-EVM asset is + // already denominated in selectedCurrency, so a genuinely unmapped + // currency must not be silently relabeled as some other currency + // (e.g. USD) — that would be wrong data, not a safe fallback. It's + // correctly omitted instead. + const bitcoinAssetId = 'bip122:000000000019d6689c085ae165831e93/slip44:0'; + const result = formatExchangeRatesForBridge({ + assetsInfo: {}, + assetsPrice: { + [bitcoinAssetId]: price({ price: 50000 }), + }, + selectedCurrency: UNMAPPED_CURRENCY, + nativeAssetIdentifiers: {}, + }); + + expect(result.conversionRates[bitcoinAssetId]).toBeUndefined(); + expect(result.currentCurrency).toBe(UNMAPPED_CURRENCY); + }); + }); + + describe('gel (SupportedCurrency previously missing from MAP_CAIP_CURRENCIES)', () => { + it('resolves EVM marketData/currencyRates for gel', () => { + const ethNativeId = 'eip155:1/slip44:60'; + const result = formatExchangeRatesForBridge({ + assetsInfo: { [ethNativeId]: NATIVE_METADATA }, + assetsPrice: { + [ethNativeId]: price({ price: 5400, lastUpdated: 1_700_000_000_000 }), + }, + selectedCurrency: 'gel', + nativeAssetIdentifiers: { 'eip155:1': ethNativeId }, + networkConfigurationsByChainId: EVM_NETWORK_CONFIGS, + }); + + expect(result.currencyRates.ETH).toStrictEqual({ + conversionDate: 1_700_000_000, + conversionRate: 5400, + usdConversionRate: 5400, + }); + }); + + it('resolves the non-EVM conversionRates entry under the correct GEL CAIP currency, not USD', () => { + const bitcoinAssetId = 'bip122:000000000019d6689c085ae165831e93/slip44:0'; + const result = formatExchangeRatesForBridge({ + assetsInfo: {}, + assetsPrice: { + [bitcoinAssetId]: price({ price: 135000 }), + }, + selectedCurrency: 'gel', + nativeAssetIdentifiers: {}, + }); + + expect(result.conversionRates[bitcoinAssetId]).toBeDefined(); + expect(result.conversionRates[bitcoinAssetId].rate).toBe('135000'); + expect(result.conversionRates[bitcoinAssetId].currency).toBe( + 'swift:0/iso4217:GEL', + ); + }); + }); + describe('memoization', () => { afterEach(() => { clearFormatExchangeRatesForBridgeCacheForTesting(); diff --git a/packages/assets-controller/src/utils/formatExchangeRatesForBridge.ts b/packages/assets-controller/src/utils/formatExchangeRatesForBridge.ts index c452f271853..7b5b49cd686 100644 --- a/packages/assets-controller/src/utils/formatExchangeRatesForBridge.ts +++ b/packages/assets-controller/src/utils/formatExchangeRatesForBridge.ts @@ -109,15 +109,15 @@ function computeExchangeRatesForBridge( const currencyRates: CurrencyRateState['currencyRates'] = {}; const marketData: TokenRatesControllerState['marketData'] = {}; + // Only consumed by the non-EVM (else) branch below to key conversionRates. + // An unmapped selectedCurrency must not discard EVM marketData/currencyRates, + // which don't depend on this lookup at all — so this bails out only the + // non-EVM assets (individually, below) rather than the whole function. + // Left undefined (not defaulted to USD) because `price` for a non-EVM + // asset is already denominated in `selectedCurrency` (unlike `usdPrice`, + // which is always USD); labeling that `price` value's currency as USD + // when it isn't would be wrong data, not a safe fallback. const currencyCaip = MAP_CAIP_CURRENCIES[selectedCurrency.toLowerCase()]; - if (!currencyCaip) { - return { - conversionRates: {}, - currencyRates: {}, - marketData: {}, - currentCurrency: selectedCurrency, - }; - } const fungibleAssetsPrice = Object.entries(assetsPrice).reduce< Record @@ -195,7 +195,7 @@ function computeExchangeRatesForBridge( usdConversionRate: usdPrice, }; } - } else { + } else if (currencyCaip) { conversionRates[assetId as Caip19AssetId] = { rate: String(price), currency: currencyCaip, diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index d739247a26f..e6ba70f459b 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -12,6 +12,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/remote-feature-flag-controller` from `^6.0.0` to `^6.1.0` ([#9980](https://github.com/MetaMask/core/pull/9980)) - Bump `@metamask/transaction-controller` from `^69.6.1` to `^69.7.0` ([#10046](https://github.com/MetaMask/core/pull/10046)) +### Fixed + +- Add the missing Georgian lari (`gel`) entry to `MAP_CAIP_CURRENCIES` ([#10064](https://github.com/MetaMask/core/pull/10064)) + ## [111.1.3] ### Changed diff --git a/packages/assets-controllers/src/MultichainAssetsRatesController/constant.ts b/packages/assets-controllers/src/MultichainAssetsRatesController/constant.ts index 2fef0e8155d..609d395f07d 100644 --- a/packages/assets-controllers/src/MultichainAssetsRatesController/constant.ts +++ b/packages/assets-controllers/src/MultichainAssetsRatesController/constant.ts @@ -57,6 +57,7 @@ export const MAP_CAIP_CURRENCIES: { dkk: 'swift:0/iso4217:DKK', eur: 'swift:0/iso4217:EUR', gbp: 'swift:0/iso4217:GBP', + gel: 'swift:0/iso4217:GEL', hkd: 'swift:0/iso4217:HKD', huf: 'swift:0/iso4217:HUF', idr: 'swift:0/iso4217:IDR',