diff --git a/packages/assets-controllers/CHANGELOG.md b/packages/assets-controllers/CHANGELOG.md index 07de57bfce..d640e9acc0 100644 --- a/packages/assets-controllers/CHANGELOG.md +++ b/packages/assets-controllers/CHANGELOG.md @@ -7,8 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Export new type `TrendingTokensQueryParams` for extensible trending token query parameters ([#8729](https://github.com/MetaMask/core/pull/8729)) + ### Changed +- **BREAKING:** `getTrendingTokens` now accepts `sort` instead of `sortBy` to match the API parameter name ([#8729](https://github.com/MetaMask/core/pull/8729)) +- `getTrendingTokens` and `getTrendingTokensURL` now accept arbitrary query parameters via an index signature on `TrendingTokensQueryParams`, allowing new API parameters to pass through without a core release ([#8729](https://github.com/MetaMask/core/pull/8729)) + - Bump `@metamask/account-tree-controller` from `^7.2.0` to `^7.3.0` ([#8722](https://github.com/MetaMask/core/pull/8722)) - Bump `@metamask/keyring-controller` from `^25.4.0` to `^25.5.0` ([#8722](https://github.com/MetaMask/core/pull/8722)) - Bump `@metamask/multichain-account-service` from `^8.0.1` to `^9.0.0` ([#8722](https://github.com/MetaMask/core/pull/8722)) diff --git a/packages/assets-controllers/src/index.ts b/packages/assets-controllers/src/index.ts index 8bc2c17247..dda1702b93 100644 --- a/packages/assets-controllers/src/index.ts +++ b/packages/assets-controllers/src/index.ts @@ -292,6 +292,7 @@ export { createFormatters } from './utils/formatters'; export type { SortTrendingBy, TrendingAsset, + TrendingTokensQueryParams, TokenSearchItem, TokenAsset, TokenRwaData, diff --git a/packages/assets-controllers/src/token-service.test.ts b/packages/assets-controllers/src/token-service.test.ts index 7ae819b9a8..e6362f154a 100644 --- a/packages/assets-controllers/src/token-service.test.ts +++ b/packages/assets-controllers/src/token-service.test.ts @@ -1139,7 +1139,7 @@ describe('Token service', () => { it('returns empty array if api returns non-array response', async () => { nock(TOKEN_END_POINT_API) .get( - `/v3/tokens/trending?chainIds=${encodeURIComponent(sampleCaipChainId)}`, + `/v3/tokens/trending?chainIds=${encodeURIComponent(sampleCaipChainId)}&includeRwaData=true&usePriceApiData=true`, ) .reply(200, { error: 'Invalid response' }) .persist(); @@ -1151,7 +1151,7 @@ describe('Token service', () => { it('returns empty array if the fetch fails', async () => { nock(TOKEN_END_POINT_API) .get( - `/v3/tokens/trending?chainIds=${encodeURIComponent(sampleCaipChainId)}`, + `/v3/tokens/trending?chainIds=${encodeURIComponent(sampleCaipChainId)}&includeRwaData=true&usePriceApiData=true`, ) .reply(500) .persist(); @@ -1162,7 +1162,7 @@ describe('Token service', () => { it('returns the list of trending tokens if the fetch succeeds', async () => { const testChainId = 'eip155:1'; - const sortBy: SortTrendingBy = 'm5_trending'; + const sort: SortTrendingBy = 'm5_trending'; const testMinLiquidity = 1000000; const testMinVolume24hUsd = 1000000; const testMaxVolume24hUsd = 1000000; @@ -1170,14 +1170,14 @@ describe('Token service', () => { const testMaxMarketCap = 1000000; nock(TOKEN_END_POINT_API) .get( - `/v3/tokens/trending?chainIds=${encodeURIComponent(testChainId)}&sort=${sortBy}&minLiquidity=${testMinLiquidity}&minVolume24hUsd=${testMinVolume24hUsd}&maxVolume24hUsd=${testMaxVolume24hUsd}&minMarketCap=${testMinMarketCap}&maxMarketCap=${testMaxMarketCap}&includeRwaData=true&usePriceApiData=true`, + `/v3/tokens/trending?chainIds=${encodeURIComponent(testChainId)}&sort=${sort}&minLiquidity=${testMinLiquidity}&minVolume24hUsd=${testMinVolume24hUsd}&maxVolume24hUsd=${testMaxVolume24hUsd}&minMarketCap=${testMinMarketCap}&maxMarketCap=${testMaxMarketCap}&includeRwaData=true&usePriceApiData=true`, ) .reply(200, sampleTrendingTokens) .persist(); const result = await getTrendingTokens({ chainIds: [testChainId], - sortBy, + sort, minLiquidity: testMinLiquidity, minVolume24hUsd: testMinVolume24hUsd, maxVolume24hUsd: testMaxVolume24hUsd, @@ -1292,7 +1292,7 @@ describe('Token service', () => { const result = await getTrendingTokens({ chainIds: [testChainId], - sortBy: 'h6_trending', + sort: 'h6_trending', minLiquidity: testMinLiquidity, minVolume24hUsd: testMinVolume, includeRwaData: false, @@ -1300,6 +1300,23 @@ describe('Token service', () => { }); expect(result).toStrictEqual(sampleTrendingTokensWithSecurityData); }); + + it('passes unknown query params through to the URL', async () => { + const testChainId = 'eip155:1'; + + nock(TOKEN_END_POINT_API) + .get( + `/v3/tokens/trending?chainIds=${encodeURIComponent(testChainId)}&includeRwaData=true&usePriceApiData=true&vsCurrency=eur`, + ) + .reply(200, sampleTrendingTokens) + .persist(); + + const result = await getTrendingTokens({ + chainIds: [testChainId], + vsCurrency: 'eur', + }); + expect(result).toStrictEqual(sampleTrendingTokens); + }); }); describe('searchTokens with includeTokenSecurityData', () => { diff --git a/packages/assets-controllers/src/token-service.ts b/packages/assets-controllers/src/token-service.ts index 02464f1556..1b49ccd656 100644 --- a/packages/assets-controllers/src/token-service.ts +++ b/packages/assets-controllers/src/token-service.ts @@ -138,24 +138,14 @@ function getTokenAssetsURL(options: { } /** - * Get the trending tokens URL for the given networks and search query. + * Shared query-parameter type for the v3 trending tokens endpoint. * - * @param options - Options for getting trending tokens. - * @param options.chainIds - Array of CAIP format chain IDs (e.g., ['eip155:1', 'eip155:137', 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp']). - * @param options.sort - The sort field. - * @param options.minLiquidity - The minimum liquidity. - * @param options.minVolume24hUsd - The minimum volume 24h in USD. - * @param options.maxVolume24hUsd - The maximum volume 24h in USD. - * @param options.minMarketCap - The minimum market cap. - * @param options.maxMarketCap - The maximum market cap. - * @param options.excludeLabels - Array of labels to exclude (e.g., ['stable_coin', 'blue_chip']). - * @param options.includeRwaData - Optional flag to include RWA data in the results (defaults to false). - * @param options.usePriceApiData - Optional flag to use price API data in the results (defaults to false). - * @param options.includeTokenSecurityData - Optional flag to include token security data in the results (defaults to false). - * @returns The trending tokens URL. + * Known parameters are explicitly typed for autocomplete and documentation. + * The index signature allows new API parameters to pass through without + * requiring a core release — callers can add any additional key/value and + * it will be forwarded as a query parameter. */ -function getTrendingTokensURL(options: { - chainIds: CaipChainId[]; +export type TrendingTokensQueryParams = { sort?: SortTrendingBy; minLiquidity?: number; minVolume24hUsd?: number; @@ -166,11 +156,21 @@ function getTrendingTokensURL(options: { includeRwaData?: boolean; usePriceApiData?: boolean; includeTokenSecurityData?: boolean; -}): string { + [key: string]: string | number | boolean | string[] | undefined; +}; + +/** + * Get the trending tokens URL for the given networks and search query. + * + * @param options - Options bag: `chainIds` (required) plus any query params. + * @returns The trending tokens URL. + */ +function getTrendingTokensURL( + options: { chainIds: CaipChainId[] } & TrendingTokensQueryParams, +): string { const encodedChainIds = options.chainIds .map((id) => encodeURIComponent(id)) .join(','); - // Add the rest of query params if they are defined const queryParams = new URLSearchParams(); const { chainIds, excludeLabels, ...rest } = options; Object.entries(rest).forEach(([key, value]) => { @@ -391,46 +391,20 @@ export type TrendingAsset = { /** * Get the trending tokens for the given chains. * - * @param options - Options for getting trending tokens. - * @param options.chainIds - The chains to get the trending tokens for. - * @param options.sortBy - The sort by field. - * @param options.minLiquidity - The minimum liquidity. - * @param options.minVolume24hUsd - The minimum volume 24h in USD. - * @param options.maxVolume24hUsd - The maximum volume 24h in USD. - * @param options.minMarketCap - The minimum market cap. - * @param options.maxMarketCap - The maximum market cap. - * @param options.excludeLabels - Array of labels to exclude (e.g., ['stable_coin', 'blue_chip']). - * @param options.includeRwaData - Optional flag to include RWA data in the results (defaults to true). - * @param options.usePriceApiData - Optional flag to use price API data in the results (defaults to true). - * @param options.includeTokenSecurityData - Optional flag to include token security data in the results (defaults to false). + * Accepts all known query parameters plus any additional ones via the + * index signature on {@link TrendingTokensQueryParams}. New API parameters + * can be passed without updating this function. + * + * @param options - Options bag: `chainIds` (required) plus any query params + * supported by the v3 trending endpoint. * @returns The trending tokens. * @throws Will throw if the request fails. */ -export async function getTrendingTokens({ - chainIds, - sortBy, - minLiquidity, - minVolume24hUsd, - maxVolume24hUsd, - minMarketCap, - maxMarketCap, - excludeLabels, - includeRwaData = true, - usePriceApiData = true, - includeTokenSecurityData, -}: { - chainIds: CaipChainId[]; - sortBy?: SortTrendingBy; - minLiquidity?: number; - minVolume24hUsd?: number; - maxVolume24hUsd?: number; - minMarketCap?: number; - maxMarketCap?: number; - excludeLabels?: string[]; - includeRwaData?: boolean; - usePriceApiData?: boolean; - includeTokenSecurityData?: boolean; -}): Promise { +export async function getTrendingTokens( + options: { chainIds: CaipChainId[] } & TrendingTokensQueryParams, +): Promise { + const { chainIds, ...rest } = options; + if (chainIds.length === 0) { console.error('No chains provided'); return []; @@ -438,16 +412,9 @@ export async function getTrendingTokens({ const trendingTokensURL = getTrendingTokensURL({ chainIds, - sort: sortBy, - minLiquidity, - minVolume24hUsd, - maxVolume24hUsd, - minMarketCap, - maxMarketCap, - excludeLabels, - includeRwaData, - usePriceApiData, - includeTokenSecurityData, + ...rest, + includeRwaData: rest.includeRwaData ?? true, + usePriceApiData: rest.usePriceApiData ?? true, }); try {