diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index ec9ec161c8..056e6e42c3 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -7,10 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Add exported `getAssetId` in `codefi-v2.ts` for CAIP-19 asset ID derivation - extracted from existing `fetchTokenPrices` ([#9386](https://github.com/MetaMask/core/pull/9386)) + ### Changed - Bump `@metamask/messenger` from `^1.2.0` to `^2.0.0` ([#9392](https://github.com/MetaMask/core/pull/9392)) - Bump `@metamask/transaction-controller` from `^68.2.2` to `^68.3.0` ([#9421](https://github.com/MetaMask/core/pull/9421)) +- Modified native asset addresses for Stable (`988`), Rootstock (`30`) and Gnosis (`100`) in `codefi-v2.ts` ([#9386](https://github.com/MetaMask/core/pull/9386)) ## [109.3.0] diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 70cadfb015..a005d08481 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -176,6 +176,7 @@ export { SUPPORTED_CHAIN_IDS, getNativeTokenAddress, SPOT_PRICES_SUPPORT_INFO, + getAssetId, } from './token-prices-service'; export { fetchRwas, diff --git a/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts b/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts index ad25d83a78..47aec3556f 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.test.ts @@ -15,6 +15,7 @@ import { fetchSupportedNetworks, getSupportedNetworks, resetSupportedNetworksCache, + getAssetId, } from './codefi-v2'; // We're not customizing the default max delay @@ -2183,6 +2184,74 @@ describe('CodefiTokenPricesServiceV2', () => { expect(service.validateChainIdSupported('0xdeadbeef')).toBe(false); }); }); + + describe('getAssetId', () => { + it('returns a CAIP-19 erc20 id with a lowercased address for ERC20 tokens', () => { + expect(getAssetId({ chainId: '0x1', tokenAddress: '0xABCDEF' })).toBe( + 'eip155:1/erc20:0xabcdef', + ); + }); + + it('returns the hardcoded id for native tokens on supported chains', () => { + expect(getAssetId({ chainId: '0x1', tokenAddress: ZERO_ADDRESS })).toBe( + 'eip155:1/slip44:60', + ); + }); + + it('detects native tokens on chains with a custom native address', () => { + expect( + getAssetId({ + chainId: '0x89', + tokenAddress: '0x0000000000000000000000000000000000001010', + }), + ).toBe('eip155:137/slip44:966'); + }); + + it('matches native token addresses case-insensitively', () => { + expect( + getAssetId({ + chainId: '0x89', + tokenAddress: '0x0000000000000000000000000000000000001010' + .toUpperCase() + .replace('0X', '0x'), + }), + ).toBe('eip155:137/slip44:966'); + }); + + it('falls back to nativeAssetIdentifiers when the chain has no hardcoded entry', () => { + // 0x42 (OKXChain) is not in SPOT_PRICES_SUPPORT_INFO + expect( + getAssetId({ + chainId: '0x42', + tokenAddress: ZERO_ADDRESS, + nativeAssetIdentifiers: { 'eip155:66': 'eip155:66/slip44:996' }, + }), + ).toBe('eip155:66/slip44:996'); + }); + + it('prefers the hardcoded entry over nativeAssetIdentifiers', () => { + expect( + getAssetId({ + chainId: '0x1', + tokenAddress: ZERO_ADDRESS, + nativeAssetIdentifiers: { 'eip155:1': 'eip155:1/erc20:0xwrong' }, + }), + ).toBe('eip155:1/slip44:60'); + }); + + it('returns undefined for a native token with no hardcoded entry and no identifier', () => { + expect( + getAssetId({ chainId: '0x42', tokenAddress: ZERO_ADDRESS }), + ).toBeUndefined(); + }); + + it('returns undefined instead of throwing when given a malformed chain ID', () => { + expect( + // @ts-expect-error Testing runtime behavior with invalid input + getAssetId({ chainId: 'not-a-hex-string', tokenAddress: '0xAAA' }), + ).toBeUndefined(); + }); + }); }); /** diff --git a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts index 712f92011c..b2a1c2d0f1 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts @@ -228,9 +228,6 @@ export const ZERO_ADDRESS: Hex = */ const chainIdToNativeTokenAddress: Record = { '0x89': '0x0000000000000000000000000000000000001010', // Polygon - '0x1e': '0x542fda317318ebf1d3deaf76e0b632741a7e677d', // Rootstock Mainnet - Native symbol: RBTC - '0x64': '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d', // Gnosis - '0x3dc': '0x779ded0c9e1022225f8e0630b35a9b54be713736', // Stable - Native symbol: USDT0 '0x440': '0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000', // Metis Andromeda '0x1388': '0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000', // Mantle }; @@ -262,7 +259,7 @@ export const SPOT_PRICES_SUPPORT_INFO = { '0x39': 'eip155:57/slip44:57', // Syscoin Mainnet - Native symbol: SYS '0x52': 'eip155:82/slip44:18000', // Meter Mainnet - Native symbol: MTR '0x58': 'eip155:88/slip44:889', // TomoChain - Native symbol: TOMO - '0x64': 'eip155:100/slip44:700', // Gnosis (formerly xDAI Chain) - Native symbol: xDAI + '0x64': 'eip155:100/erc20:0x0000000000000000000000000000000000000000', // Gnosis (formerly xDAI Chain) - Native symbol: xDAI '0x6a': 'eip155:106/slip44:5655640', // Velas EVM Mainnet - Native symbol: VLX '0x7a': 'eip155:122/erc20:0x0000000000000000000000000000000000000000', // Fuse Mainnet - Native symbol: FUSE '0x80': 'eip155:128/slip44:1010', // Huobi ECO Chain Mainnet - Native symbol: HT @@ -279,7 +276,7 @@ export const SPOT_PRICES_SUPPORT_INFO = { '0x150': 'eip155:336/slip44:809', // Shiden - Native symbol: SDN '0x169': 'eip155:361/slip44:589', // Theta Mainnet - Native symbol: TFUEL '0x2eb': 'eip155:747/slip44:539', // Flow evm - Native symbol: Flow - '0x3dc': 'eip155:988/erc20:0x779ded0c9e1022225f8e0630b35a9b54be713736', // Stable - Native symbol: USDT0 + '0x3dc': 'eip155:988/erc20:0x0000000000000000000000000000000000000000', // Stable - Native symbol: USDT0 '0x3e7': 'eip155:999/slip44:2457', // HyperEVM - Native symbol: HYPE '0x440': 'eip155:1088/erc20:0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000', // Metis Andromeda Mainnet (Ethereum L2) - Native symbol: METIS '0x44d': 'eip155:1101/slip44:60', // Polygon zkEVM mainnet - Native symbol: ETH @@ -571,6 +568,57 @@ export function resetSupportedCurrenciesCache(): void { lastFetchedCurrencies = null; } +/** + * Derives the CAIP-19 asset ID used to query the Price API for a token on a + * given chain. + * + * For native tokens, uses the hardcoded {@link SPOT_PRICES_SUPPORT_INFO} entry + * when defined, otherwise falls back to the provided native asset identifiers + * (sourced from NetworkEnablementController). For ERC20 tokens, constructs the + * CAIP-19 ID dynamically. + * + * @param args - The arguments to this function. + * @param args.chainId - The hexadecimal chain ID the token lives on. + * @param args.tokenAddress - The token's address. + * @param args.nativeAssetIdentifiers - Map of CAIP-2 chain IDs to native asset + * identifiers, used as a fallback for native tokens. + * @returns The CAIP-19 asset ID, or undefined if it cannot be determined. + */ +export function getAssetId({ + chainId, + tokenAddress, + nativeAssetIdentifiers = {}, +}: { + chainId: Hex; + tokenAddress: string; + nativeAssetIdentifiers?: NativeAssetIdentifiersMap; +}): CaipAssetType | undefined { + try { + const caipChainId = toCaipChainId( + KnownCaipNamespace.Eip155, + hexToNumber(chainId).toString(), + ); + + const nativeAddress = getNativeTokenAddress(chainId); + const isNativeToken = + nativeAddress.toLowerCase() === tokenAddress.toLowerCase(); + + if (isNativeToken) { + const hardcodedId = ( + SPOT_PRICES_SUPPORT_INFO as Partial> + )[chainId]; + return (hardcodedId ?? nativeAssetIdentifiers[caipChainId]) as + | CaipAssetType + | undefined; + } + + return `${caipChainId}/erc20:${tokenAddress.toLowerCase()}` as CaipAssetType; + } catch { + // This block should never be reached as long as using Typescript, but added for safety. + return undefined; + } +} + /** * This version of the token prices service uses V2 of the Codefi Price API to * fetch token prices. @@ -744,26 +792,11 @@ export class CodefiTokenPricesServiceV2 implements AbstractTokenPricesService< // Filter out assets that are not supported by V3 of the Price API. .filter((asset) => supportedChainIdsV3.includes(asset.chainId)) .map((asset) => { - const caipChainId = toCaipChainId( - KnownCaipNamespace.Eip155, - hexToNumber(asset.chainId).toString(), - ); - - const nativeAddress = getNativeTokenAddress(asset.chainId); - const isNativeToken = - nativeAddress.toLowerCase() === asset.tokenAddress.toLowerCase(); - - let assetId: string | undefined; - if (isNativeToken) { - // For native tokens, use hardcoded SPOT_PRICES_SUPPORT_INFO when defined, - // otherwise use nativeAssetIdentifiers from NetworkEnablementController by default. - assetId = - SPOT_PRICES_SUPPORT_INFO[asset.chainId] ?? - this.#nativeAssetIdentifiers[caipChainId]; - } else { - // For ERC20 tokens, construct the CAIP-19 ID dynamically - assetId = `${caipChainId}/erc20:${asset.tokenAddress.toLowerCase()}`; - } + const assetId = getAssetId({ + chainId: asset.chainId, + tokenAddress: asset.tokenAddress, + nativeAssetIdentifiers: this.#nativeAssetIdentifiers, + }); if (!assetId) { return undefined; @@ -771,7 +804,7 @@ export class CodefiTokenPricesServiceV2 implements AbstractTokenPricesService< return { ...asset, - assetId: assetId as CaipAssetType, + assetId, }; }) .filter( diff --git a/packages/assets-controllers/src/token-prices-service/index.test.ts b/packages/assets-controllers/src/token-prices-service/index.test.ts index d67c48e53f..687fe8b673 100644 --- a/packages/assets-controllers/src/token-prices-service/index.test.ts +++ b/packages/assets-controllers/src/token-prices-service/index.test.ts @@ -11,6 +11,7 @@ describe('token-prices-service', () => { "getSupportedNetworks", "resetSupportedNetworksCache", "SPOT_PRICES_SUPPORT_INFO", + "getAssetId", ] `); }); diff --git a/packages/assets-controllers/src/token-prices-service/index.ts b/packages/assets-controllers/src/token-prices-service/index.ts index 3d32fe3618..da2ee6adff 100644 --- a/packages/assets-controllers/src/token-prices-service/index.ts +++ b/packages/assets-controllers/src/token-prices-service/index.ts @@ -10,4 +10,5 @@ export { getSupportedNetworks, resetSupportedNetworksCache, SPOT_PRICES_SUPPORT_INFO, + getAssetId, } from './codefi-v2';