diff --git a/packages/dashboard/src/web3/context.ts b/packages/dashboard/src/web3/context.ts index d37006a3e6c5..f83c422da0ee 100644 --- a/packages/dashboard/src/web3/context.ts +++ b/packages/dashboard/src/web3/context.ts @@ -13,6 +13,7 @@ import { } from '@masknet/web3-shared-evm' import { getProxyWebsocketInstance } from '@masknet/web3-shared-base' import { Services, Messages, PluginServices, PluginMessages } from '../API' +import { TokenListApi } from '@masknet/web3-providers' export const Web3Context: Web3ProviderType = { allowTestnet: createSubscriptionFromAsync(Services.Settings.getWalletAllowTestChain, false, () => { @@ -94,7 +95,7 @@ export const Web3Context: Web3ProviderType = { getCollectionsNFT: PluginServices.Wallet.getCollectionsNFT, getAddressNamesList: PluginServices.Wallet.getAddressNames, getTransactionList: PluginServices.Wallet.getTransactionList, - fetchERC20TokensFromTokenLists: Services.Ethereum.fetchERC20TokensFromTokenLists, + fetchERC20TokensFromTokenLists: TokenListApi.fetchERC20TokensFromTokenLists, providerSocket: getProxyWebsocketInstance((info) => PluginMessages.Wallet.events.socketMessageUpdated.sendToAll(info), ), diff --git a/packages/mask/src/extension/background-script/EthereumService.ts b/packages/mask/src/extension/background-script/EthereumService.ts index b8c932081a79..583984838923 100644 --- a/packages/mask/src/extension/background-script/EthereumService.ts +++ b/packages/mask/src/extension/background-script/EthereumService.ts @@ -1,7 +1,5 @@ export * from './EthereumServices/request' export * from './EthereumServices/provider' -export * from './EthereumServices/tokenList' export * from './EthereumServices/nonce' export * from './EthereumServices/network' export * from './EthereumServices/rpc' -export * from './EthereumServices/tokenPrice' diff --git a/packages/mask/src/extension/background-script/EthereumServices/tokenPrice.ts b/packages/mask/src/extension/background-script/EthereumServices/tokenPrice.ts deleted file mode 100644 index 0f2091241a77..000000000000 --- a/packages/mask/src/extension/background-script/EthereumServices/tokenPrice.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { CurrencyType, CryptoPrice } from '@masknet/web3-shared-evm' - -const URL_BASE = 'https://api.coingecko.com/api/v3' - -export async function getTokenPrices(platform: string, contractAddresses: string[], currency: CurrencyType) { - const addressList = contractAddresses.join(',') - const requestPath = `${URL_BASE}/simple/token_price/${platform}?contract_addresses=${addressList}&vs_currencies=${currency}` - const prices = await fetch(requestPath).then((r) => r.json() as Promise) - return prices -} - -export async function getNativeTokenPrice(tokenIds: string[], currency: CurrencyType) { - const requestPath = `${URL_BASE}/simple/price?ids=${tokenIds.join(',')}&vs_currencies=${currency}` - const prices = await fetch(requestPath).then((r) => r.json() as Promise) - return prices -} diff --git a/packages/mask/src/plugins/Wallet/services/tokenPrice.ts b/packages/mask/src/plugins/Wallet/services/tokenPrice.ts index 45dd820c0502..f4815d7e6cad 100644 --- a/packages/mask/src/plugins/Wallet/services/tokenPrice.ts +++ b/packages/mask/src/plugins/Wallet/services/tokenPrice.ts @@ -1,10 +1,10 @@ import { CryptoPrice, CurrencyType } from '@masknet/web3-shared-evm' import { pollingTask } from '@masknet/shared-base' -import { getTokenPrices, getNativeTokenPrice } from '../../../extension/background-script/EthereumService' import { startEffects } from '../../../../utils-pure' import { UPDATE_CHAIN_STATE_DELAY } from '../constants' import { currentTokenPricesSettings } from '../settings' import { uniq } from 'lodash-unified' +import { TokenPriceApi } from '@masknet/web3-providers' const { run } = startEffects(import.meta.webpackHot) @@ -46,7 +46,11 @@ export async function updateTokenPrices() { const platforms = Object.keys(trackingContracts) await Promise.allSettled( platforms.map(async (platform) => { - const prices = await getTokenPrices(platform, trackingContracts[platform], CurrencyType.USD) + const prices = await TokenPriceApi.getTokenPrices( + platform, + trackingContracts[platform], + CurrencyType.USD, + ) updateCurrentPrices(prices) }), ) @@ -72,7 +76,7 @@ export async function updateNativeTokenPrices() { // update chain state try { - const prices = await getNativeTokenPrice(trackingNativeTokenIds, CurrencyType.USD) + const prices = await TokenPriceApi.getNativeTokenPrice(trackingNativeTokenIds, CurrencyType.USD) updateCurrentPrices(prices) } catch { // do nothing diff --git a/packages/mask/src/web3/context.ts b/packages/mask/src/web3/context.ts index 9d570a16479d..226645e72157 100644 --- a/packages/mask/src/web3/context.ts +++ b/packages/mask/src/web3/context.ts @@ -33,7 +33,7 @@ import type { InternalSettings } from '../settings/createSettings' import { Flags } from '../../shared' import Services from '../extension/service' import { getProxyWebsocketInstance } from '@masknet/web3-shared-base' -import { UserNFTContainerAtTwitter } from '@masknet/web3-providers' +import { TokenListApi, UserNFTContainerAtTwitter } from '@masknet/web3-providers' import type { ERC721 } from '@masknet/web3-contracts/types/ERC721' import type { AbiItem } from 'web3-utils' import ERC721ABI from '@masknet/web3-contracts/abis/ERC721.json' @@ -164,7 +164,7 @@ function createWeb3Context(disablePopup = false, isMask = false): Web3ProviderTy return addressNames }, getTransactionList: WalletRPC.getTransactionList, - fetchERC20TokensFromTokenLists: Services.Ethereum.fetchERC20TokensFromTokenLists, + fetchERC20TokensFromTokenLists: TokenListApi.fetchERC20TokensFromTokenLists, providerSocket: getProxyWebsocketInstance((info) => WalletMessages.events.socketMessageUpdated.sendToAll(info)), } } diff --git a/packages/mask/src/extension/background-script/EthereumServices/tokenList.ts b/packages/web3-providers/src/TokenList/index.ts similarity index 62% rename from packages/mask/src/extension/background-script/EthereumServices/tokenList.ts rename to packages/web3-providers/src/TokenList/index.ts index eb68e4e95190..6e23a93b14f4 100644 --- a/packages/mask/src/extension/background-script/EthereumServices/tokenList.ts +++ b/packages/web3-providers/src/TokenList/index.ts @@ -7,39 +7,14 @@ import { getChainDetailed, } from '@masknet/web3-shared-evm' import { groupBy } from 'lodash-unified' +import type { TokenListAPI } from '../types' const NATIVE_TOKEN_ADDRESS_IN_1INCH = '0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' -interface Token { - address: string - chainId: number - name: string - symbol: string - decimals: number - logoURI?: string -} - -interface TokenList { - keywords: string[] - logoURI: string - name: string - timestamp: string - tokens: Token[] - version: { - major: number - minor: number - patch: number - } -} - -interface TokenObject { - tokens: Record -} - const fetchTokenList = memoizePromise( async (url: string) => { const response = await fetch(url, { cache: 'force-cache' }) - return response.json() as Promise + return response.json() as Promise }, (url) => url, ) @@ -53,7 +28,7 @@ async function fetch1inchERC20TokensFromTokenList( url: string, chainId = ChainId.Mainnet, ): Promise { - const tokens = ((await fetchTokenList(url)) as TokenObject).tokens + const tokens = ((await fetchTokenList(url)) as TokenListAPI.TokenObject).tokens const _tokens = Object.values(tokens) return _tokens .filter((x) => x.address.toLowerCase() !== NATIVE_TOKEN_ADDRESS_IN_1INCH) @@ -74,7 +49,7 @@ async function fetchCommonERC20TokensFromTokenList( url: string, chainId = ChainId.Mainnet, ): Promise { - return ((await fetchTokenList(url)) as TokenList).tokens + return ((await fetchTokenList(url)) as TokenListAPI.TokenList).tokens .filter( (x) => x.chainId === chainId && @@ -114,24 +89,31 @@ async function fetchERC20TokensFromTokenList(urls: string[], chainId = ChainId.M * @param urls * @param chainId */ -export const fetchERC20TokensFromTokenLists = memoizePromise( - async (urls: string[], chainId = ChainId.Mainnet): Promise => { - const tokens = (await fetchERC20TokensFromTokenList(urls, chainId)) - .sort((a, b) => b.weight - a.weight) - .flatMap((x) => x.tokens) - const groupedToken = groupBy(tokens, (x) => x.address.toLowerCase()) - return Object.values(groupedToken).map((tokenList) => { - const logoURIs = tokenList - .map((token) => token.logoURI) - .flat() - .filter((token) => !!token) as string[] - return { - ...tokenList[0], - ...{ address: formatEthereumAddress(tokenList[0].address) }, - ...{ logoURI: logoURIs }, - } - }) - }, - (urls, chainId) => `${chainId}-${urls.join()}`, -) +export class TokenList implements TokenListAPI.Provider { + async fetchERC20TokensFromTokenLists(url: string[], chainId: ChainId) { + const result = memoizePromise( + async (urls: string[], chainId = ChainId.Mainnet): Promise => { + const tokens = (await fetchERC20TokensFromTokenList(urls, chainId)) + .sort((a, b) => b.weight - a.weight) + .flatMap((x) => x.tokens) + const groupedToken = groupBy(tokens, (x) => x.address.toLowerCase()) + + return Object.values(groupedToken).map((tokenList) => { + const logoURIs = tokenList + .map((token) => token.logoURI) + .flat() + .filter((token) => !!token) as string[] + return { + ...tokenList[0], + ...{ address: formatEthereumAddress(tokenList[0].address) }, + ...{ logoURI: logoURIs }, + } + }) + }, + (urls, chainId) => `${chainId}-${urls.join()}`, + ) + + return result(url, chainId) + } +} diff --git a/packages/web3-providers/src/TokenPrice/index.ts b/packages/web3-providers/src/TokenPrice/index.ts new file mode 100644 index 000000000000..0151066d1f5b --- /dev/null +++ b/packages/web3-providers/src/TokenPrice/index.ts @@ -0,0 +1,18 @@ +import type { CurrencyType } from '@masknet/plugin-infra' +import type { TokenPriceAPI } from '../types' + +const URL_BASE = 'https://api.coingecko.com/api/v3' + +export class TokenPrice implements TokenPriceAPI.Provider { + async getTokenPrices(platform: string, contractAddresses: string[], currency: CurrencyType) { + const addressList = contractAddresses.join(',') + const requestPath = `${URL_BASE}/simple/token_price/${platform}?contract_addresses=${addressList}&vs_currencies=${currency}` + const prices = await fetch(requestPath).then((r) => r.json() as Promise) + return prices + } + async getNativeTokenPrice(tokenIds: string[], currency: CurrencyType) { + const requestPath = `${URL_BASE}/simple/price?ids=${tokenIds.join(',')}&vs_currencies=${currency}` + const prices = await fetch(requestPath).then((r) => r.json() as Promise) + return prices + } +} diff --git a/packages/web3-providers/src/index.ts b/packages/web3-providers/src/index.ts index 1100dde2898f..388f66b6add3 100644 --- a/packages/web3-providers/src/index.ts +++ b/packages/web3-providers/src/index.ts @@ -6,6 +6,8 @@ import { NativeExplorerAPI } from './explorer' import { RSS3API } from './rss3' import { KeyValueAPI } from './kv' import { UserNFTContainerAtTwitterAPI } from './UserNFTContainer' +import { TokenList } from './TokenList' +import { TokenPrice } from './TokenPrice' export * from './types' export * from './opensea/utils' @@ -19,6 +21,9 @@ export const RSS3 = new RSS3API() export const KeyValue = new KeyValueAPI() export const UserNFTContainerAtTwitter = new UserNFTContainerAtTwitterAPI() +export const TokenListApi = new TokenList() +export const TokenPriceApi = new TokenPrice() + // Method for provider proxy export { getOpenSeaNFTList, getOpenSeaCollectionList } from './opensea' export { getAssetListFromDebank } from './debank' diff --git a/packages/web3-providers/src/types.ts b/packages/web3-providers/src/types.ts index 21f0f78c7770..071bf45f4405 100644 --- a/packages/web3-providers/src/types.ts +++ b/packages/web3-providers/src/types.ts @@ -1,6 +1,5 @@ import type { Transaction as Web3Transaction } from 'web3-core' import type RSS3 from 'rss3-next' -import type { CurrencyType } from '@masknet/plugin-infra' import type { ChainId, ERC20TokenDetailed, @@ -8,6 +7,7 @@ import type { ERC721TokenDetailed, NativeTokenDetailed, } from '@masknet/web3-shared-evm' +import type { CurrencyType } from '@masknet/plugin-infra' export namespace ExplorerAPI { export type Transaction = Web3Transaction & { @@ -393,3 +393,51 @@ export namespace UserNFTContainerAPI { > } } + +export namespace TokenListAPI { + export interface Token { + address: string + chainId: number + name: string + symbol: string + decimals: number + logoURI?: string + } + + export interface TokenList { + keywords: string[] + logoURI: string + name: string + timestamp: string + tokens: Token[] + version: { + major: number + minor: number + patch: number + } + } + + export interface TokenObject { + tokens: Record + } + + export interface Provider { + fetchERC20TokensFromTokenLists: (urls: string[], chainId: ChainId) => Promise + } +} + +export namespace TokenPriceAPI { + export interface PriceRecord { + [currency: string]: number + } + + /** Base on response of coingecko's token price API */ + export interface CryptoPrice { + [token: string]: PriceRecord + } + + export interface Provider { + getTokenPrices: (platform: string, contractAddresses: string[], currency: CurrencyType) => Promise + getNativeTokenPrice: (tokenIds: string[], currency: CurrencyType) => Promise + } +}