Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: price fetching for native tokens with non zero addresses #4952

Merged
merged 8 commits into from
Nov 22, 2024
49 changes: 49 additions & 0 deletions packages/assets-controllers/src/TokenRatesController.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
6 changes: 3 additions & 3 deletions packages/assets-controllers/src/TokenRatesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -718,9 +718,9 @@ export class TokenRatesController extends StaticIntervalPollingController<TokenR
});

contractNativeInformations = {
[ZERO_ADDRESS]: {
[getNativeTokenAddress(chainId)]: {
currency: nativeCurrency,
...contractNativeInformationsNative[ZERO_ADDRESS],
...contractNativeInformationsNative[getNativeTokenAddress(chainId)],
},
};
}
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 @@ -134,6 +134,7 @@ export {
export {
CodefiTokenPricesServiceV2,
SUPPORTED_CHAIN_IDS,
getNativeTokenAddress,
} from './token-prices-service';
export { RatesController, Cryptocurrency } from './RatesController';
export type {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
CodefiTokenPricesServiceV2,
SUPPORTED_CHAIN_IDS,
SUPPORTED_CURRENCIES,
ZERO_ADDRESS,
getNativeTokenAddress,
} from './codefi-v2';

// We're not customizing the default max delay
Expand Down Expand Up @@ -208,6 +210,51 @@ describe('CodefiTokenPricesServiceV2', () => {
});
});

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')
Expand Down Expand Up @@ -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);
});
});
});
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Hex, Hex> = {
Prithpal-Sooriya marked this conversation as resolved.
Show resolved Hide resolved
'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.
Expand Down Expand Up @@ -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');
Expand All @@ -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<TokenPricesByTokenAddress<Hex, SupportedCurrency>>,
tokenAddress,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ describe('token-prices-service', () => {
Array [
"CodefiTokenPricesServiceV2",
"SUPPORTED_CHAIN_IDS",
"getNativeTokenAddress",
]
`);
});
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Loading