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
5 changes: 5 additions & 0 deletions packages/assets-controllers/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]

Expand Down
1 change: 1 addition & 0 deletions packages/assets-controllers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export {
SUPPORTED_CHAIN_IDS,
getNativeTokenAddress,
SPOT_PRICES_SUPPORT_INFO,
getAssetId,
} from './token-prices-service';
export {
fetchRwas,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
fetchSupportedNetworks,
getSupportedNetworks,
resetSupportedNetworksCache,
getAssetId,
} from './codefi-v2';

// We're not customizing the default max delay
Expand Down Expand Up @@ -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();
});
});
});

/**
Expand Down
85 changes: 59 additions & 26 deletions packages/assets-controllers/src/token-prices-service/codefi-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,6 @@ export const ZERO_ADDRESS: Hex =
*/
const chainIdToNativeTokenAddress: Record<Hex, Hex> = {
'0x89': '0x0000000000000000000000000000000000001010', // Polygon
'0x1e': '0x542fda317318ebf1d3deaf76e0b632741a7e677d', // Rootstock Mainnet - Native symbol: RBTC

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rootstock native address mapping removed

Medium Severity

The Rootstock (0x1e) entry was removed from chainIdToNativeTokenAddress, so getNativeTokenAddress now returns the zero address instead of RBTC’s contract address. Native detection in getAssetId and market-data keys in TokenRatesController no longer match wallets that still use the RBTC address, which can misclassify native RBTC as ERC-20 or drop prices.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 99313eb. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0x542fda317318ebf1d3deaf76e0b632741a7e677d has never been the native.

'0x64': '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d', // Gnosis
'0x3dc': '0x779ded0c9e1022225f8e0630b35a9b54be713736', // Stable - Native symbol: USDT0
'0x440': '0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000', // Metis Andromeda
'0x1388': '0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000', // Mantle
};
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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<Record<Hex, string>>
)[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.
Expand Down Expand Up @@ -744,34 +792,19 @@ 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;
}

return {
...asset,
assetId: assetId as CaipAssetType,
assetId,
};
})
.filter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('token-prices-service', () => {
"getSupportedNetworks",
"resetSupportedNetworksCache",
"SPOT_PRICES_SUPPORT_INFO",
"getAssetId",
]
`);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export {
getSupportedNetworks,
resetSupportedNetworksCache,
SPOT_PRICES_SUPPORT_INFO,
getAssetId,
} from './codefi-v2';