diff --git a/packages/assets-controllers/src/TokenRatesController.test.ts b/packages/assets-controllers/src/TokenRatesController.test.ts index d6daaabc228..4648151769a 100644 --- a/packages/assets-controllers/src/TokenRatesController.test.ts +++ b/packages/assets-controllers/src/TokenRatesController.test.ts @@ -2348,6 +2348,55 @@ describe('TokenRatesController', () => { ); }); + it('correctly calls the Price API with unqiue native token addresses (e.g. MATIC)', async () => { + const tokenPricesService = buildMockTokenPricesService({ + fetchTokenPrices: jest.fn().mockResolvedValue({ + '0x0000000000000000000000000000000000001010': { + currency: 'MATIC', + tokenAddress: '0x0000000000000000000000000000000000001010', + value: 0.001, + }, + }), + }); + + await withController( + { + options: { tokenPricesService }, + mockNetworkClientConfigurationsByNetworkClientId: { + 'AAAA-BBBB-CCCC-DDDD': buildCustomNetworkClientConfiguration({ + chainId: '0x89', + }), + }, + }, + async ({ + controller, + triggerTokensStateChange, + triggerNetworkStateChange, + }) => { + await callUpdateExchangeRatesMethod({ + allTokens: { + '0x89': { + [defaultSelectedAddress]: [], + }, + }, + chainId: '0x89', + controller, + triggerTokensStateChange, + triggerNetworkStateChange, + method, + nativeCurrency: 'MATIC', + selectedNetworkClientId: 'AAAA-BBBB-CCCC-DDDD', + }); + + expect( + controller.state.marketData['0x89'][ + '0x0000000000000000000000000000000000001010' + ], + ).toBeDefined(); + }, + ); + }); + it('only updates rates once when called twice', async () => { const tokenAddresses = [ '0x0000000000000000000000000000000000000001', diff --git a/packages/assets-controllers/src/TokenRatesController.ts b/packages/assets-controllers/src/TokenRatesController.ts index 908c9b0319b..50ffd583c95 100644 --- a/packages/assets-controllers/src/TokenRatesController.ts +++ b/packages/assets-controllers/src/TokenRatesController.ts @@ -27,7 +27,7 @@ import { isEqual } from 'lodash'; import { reduceInBatchesSerially, TOKEN_PRICES_BATCH_SIZE } from './assetsUtil'; import { fetchExchangeRate as fetchNativeCurrencyExchangeRate } from './crypto-compare-service'; import type { AbstractTokenPricesService } from './token-prices-service/abstract-token-prices-service'; -import { ZERO_ADDRESS } from './token-prices-service/codefi-v2'; +import { getNativeTokenAddress } from './token-prices-service/codefi-v2'; import type { TokensControllerGetStateAction, TokensControllerStateChangeEvent, @@ -718,9 +718,9 @@ export class TokenRatesController extends StaticIntervalPollingController { }); }); + it('calls the /spot-prices endpoint using the correct native token address', async () => { + const mockPriceAPI = nock('https://price.api.cx.metamask.io') + .get('/v2/chains/137/spot-prices') + .query({ + tokenAddresses: '0x0000000000000000000000000000000000001010', + vsCurrency: 'ETH', + includeMarketData: 'true', + }) + .reply(200, { + '0x0000000000000000000000000000000000001010': { + price: 14, + currency: 'ETH', + pricePercentChange1d: 1, + priceChange1d: 1, + marketCap: 117219.99428314982, + allTimeHigh: 0.00060467892389492, + allTimeLow: 0.00002303954000865728, + totalVolume: 5155.094053542448, + high1d: 0.00008020715848194385, + low1d: 0.00007792083564549064, + circulatingSupply: 1494269733.9526057, + dilutedMarketCap: 117669.5125951733, + marketCapPercentChange1d: 0.76671, + pricePercentChange1h: -1.0736342953259423, + pricePercentChange7d: -7.351582573655089, + pricePercentChange14d: -1.0799098946709822, + pricePercentChange30d: -25.776321124365992, + pricePercentChange200d: 46.091571238599165, + pricePercentChange1y: -2.2992517267242754, + }, + }); + + const marketData = + await new CodefiTokenPricesServiceV2().fetchTokenPrices({ + chainId: '0x89', + tokenAddresses: [], + currency: 'ETH', + }); + + expect(mockPriceAPI.isDone()).toBe(true); + expect( + marketData['0x0000000000000000000000000000000000001010'], + ).toBeDefined(); + }); + it('should not include token price object for token address when token price in not included the response data', async () => { nock('https://price.api.cx.metamask.io') .get('/v2/chains/1/spot-prices') @@ -1960,6 +2007,19 @@ describe('CodefiTokenPricesServiceV2', () => { ).toBe(false); }); }); + + describe('getNativeTokenAddress', () => { + it('should return unique native token address for MATIC', () => { + expect(getNativeTokenAddress('0x89')).toBe( + '0x0000000000000000000000000000000000001010', + ); + }); + it('should return zero address for other chains', () => { + (['0x1', '0x2', '0x1337'] as const).forEach((chainId) => { + expect(getNativeTokenAddress(chainId)).toBe(ZERO_ADDRESS); + }); + }); + }); }); /** 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 b496faa2def..4f163203e4d 100644 --- a/packages/assets-controllers/src/token-prices-service/codefi-v2.ts +++ b/packages/assets-controllers/src/token-prices-service/codefi-v2.ts @@ -156,6 +156,24 @@ export const SUPPORTED_CURRENCIES = [ export const ZERO_ADDRESS: Hex = '0x0000000000000000000000000000000000000000' as const; +/** + * A mapping from chain id to the address of the chain's native token. + * Only for chains whose native tokens have a specific address. + */ +const chainIdToNativeTokenAddress: Record = { + '0x89': '0x0000000000000000000000000000000000001010', +}; + +/** + * Returns the address that should be used to query the price api for the + * chain's native token. On most chains, this is signified by the zero address. + * But on some chains, the native token has a specific address. + * @param chainId - The hexadecimal chain id. + * @returns The address of the chain's native token. + */ +export const getNativeTokenAddress = (chainId: Hex): Hex => + chainIdToNativeTokenAddress[chainId] ?? ZERO_ADDRESS; + /** * A currency that can be supplied as the `vsCurrency` parameter to * the `/spot-prices` endpoint. Covers both uppercase and lowercase versions. @@ -435,7 +453,7 @@ export class CodefiTokenPricesServiceV2 const url = new URL(`${BASE_URL}/chains/${chainIdAsNumber}/spot-prices`); url.searchParams.append( 'tokenAddresses', - [ZERO_ADDRESS, ...tokenAddresses].join(','), + [getNativeTokenAddress(chainId), ...tokenAddresses].join(','), ); url.searchParams.append('vsCurrency', currency); url.searchParams.append('includeMarketData', 'true'); @@ -445,7 +463,7 @@ export class CodefiTokenPricesServiceV2 handleFetch(url, { headers: { 'Cache-Control': 'no-cache' } }), ); - return [ZERO_ADDRESS, ...tokenAddresses].reduce( + return [getNativeTokenAddress(chainId), ...tokenAddresses].reduce( ( obj: Partial>, tokenAddress, 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 a5a1e93f7a5..a59be2ba4de 100644 --- a/packages/assets-controllers/src/token-prices-service/index.test.ts +++ b/packages/assets-controllers/src/token-prices-service/index.test.ts @@ -6,6 +6,7 @@ describe('token-prices-service', () => { Array [ "CodefiTokenPricesServiceV2", "SUPPORTED_CHAIN_IDS", + "getNativeTokenAddress", ] `); }); diff --git a/packages/assets-controllers/src/token-prices-service/index.ts b/packages/assets-controllers/src/token-prices-service/index.ts index f6313c36e71..509fc680055 100644 --- a/packages/assets-controllers/src/token-prices-service/index.ts +++ b/packages/assets-controllers/src/token-prices-service/index.ts @@ -1,2 +1,6 @@ export type { AbstractTokenPricesService } from './abstract-token-prices-service'; -export { CodefiTokenPricesServiceV2, SUPPORTED_CHAIN_IDS } from './codefi-v2'; +export { + CodefiTokenPricesServiceV2, + SUPPORTED_CHAIN_IDS, + getNativeTokenAddress, +} from './codefi-v2';