diff --git a/packages/assets-controller/CHANGELOG.md b/packages/assets-controller/CHANGELOG.md index 811be3964c..4c84c7a7cb 100644 --- a/packages/assets-controller/CHANGELOG.md +++ b/packages/assets-controller/CHANGELOG.md @@ -11,6 +11,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Bump `@metamask/transaction-controller` from `^65.3.0` to `^65.4.0` ([#8796](https://github.com/MetaMask/core/pull/8796)) +### Fixed + +- Non-EVM assets with a `slip44` asset namespace (e.g. Bitcoin, Solana native, TRON) are now correctly typed as `native` instead of `erc20` in `assetsInfo` ([#8811](https://github.com/MetaMask/core/pull/8811)) +- Solana SPL tokens (CAIP-19 `solana:.../token:
`) are now correctly typed as `spl` instead of `erc20` in `assetsInfo` ([#8811](https://github.com/MetaMask/core/pull/8811)) + ## [7.1.2] ### Changed diff --git a/packages/assets-controller/src/AssetsController.ts b/packages/assets-controller/src/AssetsController.ts index 0024243cf6..997e62e13b 100644 --- a/packages/assets-controller/src/AssetsController.ts +++ b/packages/assets-controller/src/AssetsController.ts @@ -57,6 +57,7 @@ import type { Hex } from '@metamask/utils'; import { isCaipChainId, isStrictHexString, + KnownCaipNamespace, parseCaipAssetType, parseCaipChainId, } from '@metamask/utils'; @@ -82,7 +83,10 @@ import type { AccountsControllerAccountBalancesUpdatedEvent } from './data-sourc import { SnapDataSource } from './data-sources/SnapDataSource'; import type { StakedBalanceDataSourceConfig } from './data-sources/StakedBalanceDataSource'; import { StakedBalanceDataSource } from './data-sources/StakedBalanceDataSource'; -import { TokenDataSource } from './data-sources/TokenDataSource'; +import { + CaipAssetNamespace, + TokenDataSource, +} from './data-sources/TokenDataSource'; import { CHAINS_WITH_DEFAULT_TRACKED_ASSETS, DEFAULT_TRACKED_ASSETS_BY_CHAIN, @@ -1663,7 +1667,10 @@ export class AssetsController extends BaseController< let tokenType: FungibleAssetMetadata['type'] = 'erc20'; if (this.#isNativeAsset(normalizedAssetId)) { tokenType = 'native'; - } else if (parsed.assetNamespace === 'spl') { + } else if ( + parsed.chain.namespace === KnownCaipNamespace.Solana && + parsed.assetNamespace === CaipAssetNamespace.Token + ) { tokenType = 'spl'; } diff --git a/packages/assets-controller/src/README.md b/packages/assets-controller/src/README.md index 892cfc1623..baebf8606f 100644 --- a/packages/assets-controller/src/README.md +++ b/packages/assets-controller/src/README.md @@ -653,7 +653,7 @@ type Caip19AssetId = string; // - Native ETH: "eip155:1/slip44:60" // - USDC on Ethereum: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // - SOL: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501" -// - SPL Token: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5..." +// - SPL Token: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5..." // CAIP-2 chain identifier type ChainId = string; diff --git a/packages/assets-controller/src/data-sources/TokenDataSource.test.ts b/packages/assets-controller/src/data-sources/TokenDataSource.test.ts index 15932f3936..8b8cc63df4 100644 --- a/packages/assets-controller/src/data-sources/TokenDataSource.test.ts +++ b/packages/assets-controller/src/data-sources/TokenDataSource.test.ts @@ -20,8 +20,13 @@ const MOCK_ADDRESS = '0x1234567890123456789012345678901234567890'; const MOCK_TOKEN_ASSET = 'eip155:1/erc20:0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48' as Caip19AssetId; const MOCK_NATIVE_ASSET = 'eip155:1/slip44:60' as Caip19AssetId; +const MOCK_BTC_ASSET = + 'bip122:000000000019d6689c085ae165831e93/slip44:0' as Caip19AssetId; +const MOCK_SOL_NATIVE_ASSET = + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/slip44:501' as Caip19AssetId; +const MOCK_TRX_ASSET = 'tron:728126428/slip44:195' as Caip19AssetId; const MOCK_SPL_ASSET = - 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' as Caip19AssetId; + 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' as Caip19AssetId; type MockApiClient = { tokens: { @@ -516,6 +521,57 @@ describe('TokenDataSource', () => { expect(context.response.assetsInfo?.[MOCK_SPL_ASSET]?.type).toBe('spl'); }); + it.each([ + { + label: 'Bitcoin (bip122/slip44)', + assetId: MOCK_BTC_ASSET, + chainId: 'bip122:000000000019d6689c085ae165831e93', + name: 'Bitcoin', + symbol: 'BTC', + decimals: 8, + }, + { + label: 'SOL native (solana/slip44)', + assetId: MOCK_SOL_NATIVE_ASSET, + chainId: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp', + name: 'Solana', + symbol: 'SOL', + decimals: 9, + }, + { + label: 'TRX native (tron/slip44)', + assetId: MOCK_TRX_ASSET, + chainId: 'tron:728126428', + name: 'TRON', + symbol: 'TRX', + decimals: 6, + }, + ])( + 'middleware types non-EVM slip44 asset as native: $label', + async ({ assetId, chainId, name, symbol, decimals }) => { + const { controller } = setupController({ + messenger: createTestMessenger(), + supportedNetworks: [chainId], + assetsResponse: [ + createMockAssetResponse(assetId, { name, symbol, decimals }), + ], + }); + + const next = jest.fn().mockResolvedValue(undefined); + const context = createMiddlewareContext({ + response: { + detectedAssets: { + 'mock-account-id': [assetId], + }, + }, + }); + + await controller.assetsMiddleware(context, next); + + expect(context.response.assetsInfo?.[assetId]?.type).toBe('native'); + }, + ); + it('middleware merges metadata into existing response', async () => { const anotherAsset = 'eip155:1/erc20:0x6b175474e89094c44da98b954eedeac495271d0f' as Caip19AssetId; diff --git a/packages/assets-controller/src/data-sources/TokenDataSource.ts b/packages/assets-controller/src/data-sources/TokenDataSource.ts index 110831664f..c94198e73d 100644 --- a/packages/assets-controller/src/data-sources/TokenDataSource.ts +++ b/packages/assets-controller/src/data-sources/TokenDataSource.ts @@ -45,7 +45,7 @@ const BULK_SCAN_BATCH_SIZE = 100; const MIN_TOKEN_OCCURRENCES = 3; /** CAIP-19 `assetNamespace` segments used across filtering logic. */ -enum CaipAssetNamespace { +export enum CaipAssetNamespace { Slip44 = 'slip44', Erc20 = 'erc20', Token = 'token', @@ -102,11 +102,18 @@ function transformV3AssetResponseToMetadata( const parsed = parseCaipAssetType(assetId); let tokenType: 'native' | 'erc20' | 'spl' = 'erc20'; - if (nativeAssetIds.has(assetId.toLowerCase())) { + if ( + nativeAssetIds.has(assetId.toLowerCase()) || + parsed.assetNamespace === CaipAssetNamespace.Slip44 + ) { tokenType = 'native'; - } else if (parsed.assetNamespace === 'spl') { + } else if ( + parsed.chain.namespace === KnownCaipNamespace.Solana && + parsed.assetNamespace === CaipAssetNamespace.Token + ) { tokenType = 'spl'; } + // TODO: Add support for Tron trc20 standard const metadata: FungibleAssetMetadata = { // Type derived from assetId diff --git a/packages/assets-controller/src/types.ts b/packages/assets-controller/src/types.ts index d0bf7fe7bc..038fe74df9 100644 --- a/packages/assets-controller/src/types.ts +++ b/packages/assets-controller/src/types.ts @@ -9,7 +9,7 @@ import type { CaipAssetType, CaipChainId, Json } from '@metamask/utils'; * - Native: "eip155:1/slip44:60" (ETH) * - ERC20: "eip155:1/erc20:0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" (USDC) * - ERC721: "eip155:1/erc721:0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D/1234" (BAYC #1234) - * - SPL: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/spl:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" + * - SPL: "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp/token:EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" */ export type Caip19AssetId = CaipAssetType; diff --git a/packages/bridge-controller/src/selectors.test.ts b/packages/bridge-controller/src/selectors.test.ts index bd5162f310..2d73863857 100644 --- a/packages/bridge-controller/src/selectors.test.ts +++ b/packages/bridge-controller/src/selectors.test.ts @@ -39,7 +39,7 @@ describe('Bridge Selectors', () => { exchangeRate: '2.5', usdExchangeRate: '1.5', }, - 'solana:101/spl:456': { + 'solana:101/token:456': { exchangeRate: '3.0', }, },