Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/assets-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Caip19AssetId, FungibleAssetPrice>
Expand Down Expand Up @@ -195,7 +195,7 @@ function computeExchangeRatesForBridge(
usdConversionRate: usdPrice,
};
}
} else {
} else if (currencyCaip) {
conversionRates[assetId as Caip19AssetId] = {
rate: String(price),
currency: currencyCaip,
Expand Down
4 changes: 4 additions & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down