diff --git a/packages/assets-controllers/src/TokenDetectionController.ts b/packages/assets-controllers/src/TokenDetectionController.ts index f16b34f7007..2e43b98b1d5 100644 --- a/packages/assets-controllers/src/TokenDetectionController.ts +++ b/packages/assets-controllers/src/TokenDetectionController.ts @@ -752,7 +752,7 @@ export class TokenDetectionController extends StaticIntervalPollingController; @@ -189,7 +201,9 @@ export class TokenListController extends StaticIntervalPollingController { const selectedNetworkClient = this.messenger.call( 'NetworkController:getNetworkClientById', networkControllerState.selectedNetworkClientId, @@ -214,7 +228,7 @@ export class TokenListController extends StaticIntervalPollingController { if (!isTokenListSupportedForNetwork(this.chainId)) { return; } @@ -227,7 +241,7 @@ export class TokenListController extends StaticIntervalPollingController { this.stopPolling(); await this.#startDeprecatedPolling(); } @@ -238,7 +252,7 @@ export class TokenListController extends StaticIntervalPollingController token.address.toLowerCase() === address.toLowerCase(), @@ -525,7 +526,7 @@ export class TokensController extends BaseController< }, {}); try { tokensToImport.forEach((tokenToAdd) => { - const { address, symbol, decimals, image, aggregators, name } = + const { address, symbol, decimals, image, aggregators, name, rwaData } = tokenToAdd; const checksumAddress = toChecksumHexAddress(address); const formattedToken: Token = { @@ -535,6 +536,7 @@ export class TokensController extends BaseController< image, aggregators, name, + rwaData, }; newTokensMap[checksumAddress] = formattedToken; importedTokensMap[address.toLowerCase()] = true; @@ -664,6 +666,7 @@ export class TokensController extends BaseController< aggregators, isERC721, name, + rwaData, } = tokenToAdd; const checksumAddress = toChecksumHexAddress(address); const newEntry: Token = { @@ -674,6 +677,7 @@ export class TokensController extends BaseController< isERC721, aggregators, name, + rwaData, }; const previousImportedIndex = newTokens.findIndex( diff --git a/packages/assets-controllers/src/selectors/token-selectors.ts b/packages/assets-controllers/src/selectors/token-selectors.ts index a1772dfa6cd..9dada974087 100644 --- a/packages/assets-controllers/src/selectors/token-selectors.ts +++ b/packages/assets-controllers/src/selectors/token-selectors.ts @@ -8,6 +8,7 @@ import type { NetworkState } from '@metamask/network-controller'; import { hexToBigInt, parseCaipAssetType } from '@metamask/utils'; import type { Hex } from '@metamask/utils'; import { createSelector, weakMapMemoize } from 'reselect'; +import { TokenRwaData } from 'src/token-service'; import { parseBalanceWithDecimals, @@ -84,6 +85,7 @@ export type Asset = ( conversionRate: number; } | undefined; + rwaData?: TokenRwaData; }; export type AssetListState = { diff --git a/packages/assets-controllers/src/token-service.ts b/packages/assets-controllers/src/token-service.ts index 3048b9bbcbb..4e7a69c361f 100644 --- a/packages/assets-controllers/src/token-service.ts +++ b/packages/assets-controllers/src/token-service.ts @@ -8,7 +8,9 @@ import type { CaipAssetType, CaipChainId, Hex } from '@metamask/utils'; import { isTokenListSupportedForNetwork } from './assetsUtil'; -export const TOKEN_END_POINT_API = 'https://token.api.cx.metamask.io'; +// export const TOKEN_END_POINT_API = 'https://token.api.cx.metamask.io'; +// TODO change it back after development is done +export const TOKEN_END_POINT_API = 'https://token.dev-api.cx.metamask.io'; export const TOKEN_METADATA_NO_SUPPORT_ERROR = 'TokenService Error: Network does not support fetchTokenMetadata'; @@ -23,7 +25,7 @@ function getTokensURL(chainId: Hex): string { return `${TOKEN_END_POINT_API}/tokens/${convertHexToDecimal( chainId, - )}?occurrenceFloor=${occurrenceFloor}&includeNativeAssets=false&includeTokenFees=false&includeAssetType=false&includeERC20Permit=false&includeStorage=false`; + )}?occurrenceFloor=${occurrenceFloor}&includeNativeAssets=false&includeTokenFees=false&includeAssetType=false&includeERC20Permit=false&includeStorage=false&includeRwaData=true`; } /** @@ -36,7 +38,7 @@ function getTokensURL(chainId: Hex): string { function getTokenMetadataURL(chainId: Hex, tokenAddress: string): string { return `${TOKEN_END_POINT_API}/token/${convertHexToDecimal( chainId, - )}?address=${tokenAddress}`; + )}?address=${tokenAddress}&includeRwaData=true`; } /** @@ -64,7 +66,7 @@ function getTokenSearchURL(options: { query: string; limit?: number; includeMarketData?: boolean; - includeRwaData?: boolean; + includeRwaData?: true; }): string { const { chainIds, query, ...optionalParams } = options; const encodedQuery = encodeURIComponent(query); @@ -118,6 +120,8 @@ function getTrendingTokensURL(options: { } }); + queryParams.append('includeRwaData', 'true'); + // Handle excludeLabels separately to avoid encoding the commas // The API expects: excludeLabels=stable_coin,blue_chip (not %2C) const excludeLabelsParam = @@ -154,17 +158,74 @@ export async function fetchTokenListByChainId( if (response) { const result = await parseJsonResponse(response); if (Array.isArray(result) && chainId === ChainId['linea-mainnet']) { - return result.filter( + const filteredResult = result.filter( (elm) => - Boolean(elm.aggregators.includes('lineaTeam')) || - elm.aggregators.length >= 3, + elm.aggregators.includes('lineaTeam') ?? elm.aggregators.length >= 3, ); + // TODO: remove this after development is done + // if the filteredResult has an aggregator that includes 'Ondo' then append rwaData as metadata. + const filteredResultWithRwaData = filteredResult.map((elm) => { + const metadata = { + rwaData: { + instrumentType: 'stock', + ticker: elm.name?.split(' ')[0] ?? '', + market: { + nextOpen: new Date(new Date().setHours(9, 0, 0, 0)).toISOString(), + nextClose: new Date( + new Date().setHours(16, 0, 0, 0), + ).toISOString(), + }, + nextPause: { + start: new Date(new Date().setHours(16, 0, 0, 0)).toISOString(), + end: new Date(new Date().setHours(17, 0, 0, 0)).toISOString(), + }, + }, + }; + // if (elm.aggregators.includes('Ondo')) { + return { ...elm, ...metadata }; + // } + // return elm; + }); + + console.log('filteredResult', filteredResult); + return filteredResultWithRwaData; + } + + if (Array.isArray(result)) { + // TODO: remove this after development is done + // if the filteredResult has an aggregator that includes 'Ondo' then append rwaData as metadata. + const filteredResultWithRwaData = result.map((elm) => { + const metadata = { + rwaData: { + instrumentType: 'stock', + ticker: elm.name?.split(' ')[0] ?? '', + market: { + nextOpen: new Date(new Date().setHours(9, 0, 0, 0)).toISOString(), + nextClose: new Date( + new Date().setHours(16, 0, 0, 0), + ).toISOString(), + }, + nextPause: { + start: new Date(new Date().setHours(16, 0, 0, 0)).toISOString(), + end: new Date(new Date().setHours(17, 0, 0, 0)).toISOString(), + }, + }, + }; + // if (elm.aggregators.includes('Ondo')) { + return { ...elm, ...metadata }; + // } + // return elm; + }); + + console.log('filteredResultWithRwaData', filteredResultWithRwaData); + return filteredResultWithRwaData; } return result; } return undefined; } +// TODO This end point already contain RwaData, so we don't need to append it here. export type TokenRwaData = { market?: { nextOpen?: string; @@ -218,7 +279,7 @@ export async function searchTokens( query, limit, includeMarketData, - includeRwaData, + includeRwaData: true, }); try { @@ -228,7 +289,7 @@ export async function searchTokens( // The API returns an object with structure: { count: number, data: array, pageInfo: object } if (result && typeof result === 'object' && Array.isArray(result.data)) { return { - count: result.count || result.data.length, + count: result.count ?? result.data.length, data: result.data, }; } @@ -325,7 +386,27 @@ export async function getTrendingTokens({ // Validate that the API returned an array if (Array.isArray(result)) { - return result; + // TODO hack the results to include RwaData + const filteredResultWithRwaData = result.map((elm) => { + const metadata = { + rwaData: { + instrumentType: 'stock', + ticker: elm.name?.split(' ')[0] ?? '', + market: { + nextOpen: new Date(new Date().setHours(9, 0, 0, 0)).toISOString(), + nextClose: new Date( + new Date().setHours(16, 0, 0, 0), + ).toISOString(), + }, + nextPause: { + start: new Date(new Date().setHours(16, 0, 0, 0)).toISOString(), + end: new Date(new Date().setHours(17, 0, 0, 0)).toISOString(), + }, + }, + }; + return { ...elm, ...metadata }; + }); + return filteredResultWithRwaData; } // Handle non-expected responses @@ -360,6 +441,32 @@ export async function fetchTokenMetadata( const tokenMetadataURL = getTokenMetadataURL(chainId, tokenAddress); const response = await queryApi(tokenMetadataURL, abortSignal, timeout); if (response) { + const result = await parseJsonResponse(response); + if (Array.isArray(result)) { + const filteredResultWithRwaData = result.map((elm) => { + const metadata = { + rwaData: { + instrumentType: 'stock', + ticker: elm.name?.split(' ')[0] ?? '', + market: { + nextOpen: new Date(new Date().setHours(9, 0, 0, 0)).toISOString(), + nextClose: new Date( + new Date().setHours(16, 0, 0, 0), + ).toISOString(), + }, + nextPause: { + start: new Date(new Date().setHours(16, 0, 0, 0)).toISOString(), + end: new Date(new Date().setHours(17, 0, 0, 0)).toISOString(), + }, + }, + }; + if (elm.aggregators.includes('Ondo')) { + return { ...elm, ...metadata }; + } + return elm; + }); + return filteredResultWithRwaData as unknown as TReturn; + } return parseJsonResponse(response) as Promise; } return undefined; diff --git a/packages/bridge-controller/src/utils/fetch.ts b/packages/bridge-controller/src/utils/fetch.ts index 5a391c777f3..bc8bb0753e1 100644 --- a/packages/bridge-controller/src/utils/fetch.ts +++ b/packages/bridge-controller/src/utils/fetch.ts @@ -53,7 +53,22 @@ export async function fetchBridgeTokens( const transformedTokens: Record = {}; tokens.forEach((token: unknown) => { if (validateSwapsTokenObject(token)) { - transformedTokens[token.address] = token; + // TODO hack the results to include RwaData + const metadata = { + rwaData: { + instrumentType: 'stock', + ticker: token.name?.split(' ')[0] ?? '', + market: { + nextOpen: new Date(new Date().setHours(9, 0, 0, 0)).toISOString(), + nextClose: new Date(new Date().setHours(16, 0, 0, 0)).toISOString(), + }, + nextPause: { + start: new Date(new Date().setHours(16, 0, 0, 0)).toISOString(), + end: new Date(new Date().setHours(17, 0, 0, 0)).toISOString(), + }, + }, + }; + transformedTokens[token.address] = { ...token, ...metadata }; } }); return transformedTokens; diff --git a/packages/bridge-controller/src/utils/validators.ts b/packages/bridge-controller/src/utils/validators.ts index 327a3f5eee1..bbad4569f77 100644 --- a/packages/bridge-controller/src/utils/validators.ts +++ b/packages/bridge-controller/src/utils/validators.ts @@ -86,6 +86,21 @@ export const BridgeAssetSchema = type({ * URL for token icon */ iconUrl: optional(nullable(string())), + + rwaData: optional( + type({ + instrumentType: string(), + ticker: string(), + market: type({ + nextOpen: string(), + nextClose: string(), + }), + nextPause: type({ + start: string(), + end: string(), + }), + }), + ), }); const DefaultPairSchema = type({