diff --git a/packages/maskbook/src/plugins/Wallet/pipes/index.ts b/packages/maskbook/src/plugins/Wallet/pipes/index.ts index e0809ab761f2..9e46e8c444bc 100644 --- a/packages/maskbook/src/plugins/Wallet/pipes/index.ts +++ b/packages/maskbook/src/plugins/Wallet/pipes/index.ts @@ -1,5 +1,6 @@ import { safeUnreachable, unreachable } from '@dimensiondev/kit' -import { NetworkType, PortfolioProvider } from '@masknet/web3-shared' +import { ChainId, NetworkType, PortfolioProvider } from '@masknet/web3-shared' +import type { SocketRequestAssetScope } from '../types' export function resolvePortfolioDataProviderName(provider: PortfolioProvider) { switch (provider) { @@ -59,3 +60,28 @@ export function resolveZerionTransactionsScopeName(network: NetworkType) { return '' } } + +export function resolveChainByScope(scope: SocketRequestAssetScope) { + switch (scope) { + case 'assets': + return { + chain: 'eth', + chainId: ChainId.Mainnet, + } + case 'bsc-assets': + return { + chain: 'bsc', + chainId: ChainId.BSC, + } + case 'polygon-assets': + return { + chain: 'matic', + chainId: ChainId.Matic, + } + default: + safeUnreachable(scope) + return { + chain: 'unknown', + } + } +} diff --git a/packages/maskbook/src/plugins/Wallet/services/assets.ts b/packages/maskbook/src/plugins/Wallet/services/assets.ts index e58485ec65d5..f14cca52048c 100644 --- a/packages/maskbook/src/plugins/Wallet/services/assets.ts +++ b/packages/maskbook/src/plugins/Wallet/services/assets.ts @@ -2,6 +2,7 @@ import { unreachable } from '@dimensiondev/kit' import { Asset, ChainId, + CollectibleProvider, createERC1155Token, createERC20Token, createERC721Token, @@ -13,9 +14,8 @@ import { getTokenConstants, isChainIdMainnet, NetworkType, - pow10, - CollectibleProvider, PortfolioProvider, + pow10, } from '@masknet/web3-shared' import BigNumber from 'bignumber.js' import { values } from 'lodash-es' @@ -23,8 +23,15 @@ import { EthereumAddress } from 'wallet.ts' import * as DebankAPI from '../apis/debank' import * as OpenSeaAPI from '../apis/opensea' import * as ZerionAPI from '../apis/zerion' -import { resolveZerionAssetsScopeName } from '../pipes' -import type { BalanceRecord, ZerionAddressAsset } from '../types' +import { resolveChainByScope, resolveZerionAssetsScopeName } from '../pipes' +import type { + BalanceRecord, + SocketRequestAssetScope, + ZerionAddressAsset, + ZerionAddressCovalentAsset, + ZerionAsset, + ZerionCovalentAsset, +} from '../types' export async function getAssetsListNFT( address: string, @@ -89,14 +96,29 @@ export async function getAssetsList( if (!EthereumAddress.isValid(address)) return [] switch (provider) { case PortfolioProvider.ZERION: - if (network !== NetworkType.Ethereum) return [] const scope = resolveZerionAssetsScopeName(network) - if (!scope) return [] + const { meta, payload } = await ZerionAPI.getAssetsList(address, scope) if (meta.status !== 'ok') throw new Error('Fail to load assets.') - // skip NFT assets - const assetsList = values(payload.assets).filter((x) => x.asset.is_displayable && x.asset.icon_url) - return formatAssetsFromZerion(assetsList) + + const assets = Object.entries(payload).map(([key, value]) => { + if (key === 'assets') { + const assetsList = (values(value) as ZerionAddressAsset[]).filter( + ({ asset }) => + asset.is_displayable && + !filterAssetType.some((type) => type === asset.type) && + asset.icon_url, + ) + return formatAssetsFromZerion(assetsList, key) + } + + return formatAssetsFromZerion( + values(value) as ZerionAddressCovalentAsset[], + key as SocketRequestAssetScope, + ) + }) + + return assets.flat() case PortfolioProvider.DEBANK: const { data = [], error_code } = await DebankAPI.getAssetsList(address) if (error_code === 0) return formatAssetsFromDebank(data) @@ -135,35 +157,34 @@ function formatAssetsFromDebank(data: BalanceRecord[]) { const filterAssetType = ['compound', 'trash', 'uniswap', 'uniswap-v2', 'nft'] -function formatAssetsFromZerion(data: ZerionAddressAsset[]) { - return data - .filter(({ asset }) => asset.is_displayable && !filterAssetType.some((type) => type === asset.type)) - .map(({ asset, quantity }) => { - const balance = Number(new BigNumber(quantity).dividedBy(pow10(asset.decimals)).toString()) - return { - token: { - name: asset.name, - symbol: asset.symbol, - decimals: asset.decimals, - address: - asset.name === 'Ether' || asset.name === 'Ethereum' - ? getTokenConstants().NATIVE_TOKEN_ADDRESS - : asset.asset_code, - chainId: ChainId.Mainnet, - type: - asset.name === 'Ether' || asset.name === 'Ethereum' - ? EthereumTokenType.Native - : EthereumTokenType.ERC20, - }, - chain: 'eth', - balance: quantity, - price: { - usd: new BigNumber(asset.price?.value ?? 0).toString(), - }, - value: { - usd: new BigNumber(balance).multipliedBy(asset.price?.value ?? 0).toString(), - }, +function formatAssetsFromZerion( + data: ZerionAddressAsset[] | ZerionAddressCovalentAsset[], + scope: SocketRequestAssetScope, +) { + return data.map(({ asset, quantity }) => { + const balance = Number(new BigNumber(quantity).dividedBy(pow10(asset.decimals)).toString()) + const value = (asset as ZerionAsset).price?.value ?? (asset as ZerionCovalentAsset).value ?? 0 + const isNativeToken = (symbol: string) => ['ETH', 'BNB', 'MATIC'].includes(symbol) + + return { + token: { + name: asset.name, + symbol: asset.symbol, + decimals: asset.decimals, + address: isNativeToken(asset.symbol) ? getTokenConstants().NATIVE_TOKEN_ADDRESS : asset.asset_code, + chainId: resolveChainByScope(scope).chainId, + type: isNativeToken(asset.symbol) ? EthereumTokenType.Native : EthereumTokenType.ERC20, logoURI: asset.icon_url, - } - }) as Asset[] + }, + chain: resolveChainByScope(scope).chain, + balance: quantity, + price: { + usd: new BigNumber(value).toString(), + }, + value: { + usd: new BigNumber(balance).multipliedBy(value).toString(), + }, + logoURI: asset.icon_url, + } + }) as Asset[] } diff --git a/packages/maskbook/src/plugins/Wallet/types/zerion.ts b/packages/maskbook/src/plugins/Wallet/types/zerion.ts index 042e895105dc..d39248b20e6e 100644 --- a/packages/maskbook/src/plugins/Wallet/types/zerion.ts +++ b/packages/maskbook/src/plugins/Wallet/types/zerion.ts @@ -3,6 +3,8 @@ export type SocketNameSpace = { socket: SocketIOClient.Socket } +export type SocketRequestAssetScope = 'assets' | 'bsc-assets' | 'polygon-assets' + export type SocketRequestBody = { scope: string[] payload: Record @@ -60,11 +62,25 @@ export interface ZerionAsset { price?: ZerionPrice } +export interface ZerionCovalentAsset { + asset_code: string + name: string + symbol: string + decimals: number + icon_url?: string + value: number +} + export interface ZerionAddressAsset { asset: ZerionAsset quantity: string } +export interface ZerionAddressCovalentAsset { + asset: ZerionCovalentAsset + quantity: string +} + export interface ZerionTransactionChange { asset: ZerionAsset value: number @@ -112,6 +128,12 @@ export interface ZerionTransactionResponseBody extends SocketResponseBody { } } +type ZerionAssetResponseBodyPayload = { + assets: Record + 'bsc-assets': Record + 'polygon-assets': Record +} + export interface ZerionAssetResponseBody extends SocketResponseBody { meta: { address: string @@ -120,7 +142,5 @@ export interface ZerionAssetResponseBody extends SocketResponseBody { currency: string status: string } - payload: { - assets: Record - } + payload: Partial } diff --git a/packages/shared/src/UI/components/TokenIcon/index.tsx b/packages/shared/src/UI/components/TokenIcon/index.tsx index 1fd798848af3..7cd8d0bb0f21 100644 --- a/packages/shared/src/UI/components/TokenIcon/index.tsx +++ b/packages/shared/src/UI/components/TokenIcon/index.tsx @@ -56,7 +56,7 @@ export function TokenIcon(props: TokenIconProps) { _logoURI = nativeToken?.nativeCurrency.logoURI } - const { TOKEN_ASSET_BASE_URI } = useTokenAssetBaseURLConstants(chainId) + const { TOKEN_ASSET_BASE_URI } = useTokenAssetBaseURLConstants(chainId ?? _chainId) const fallbackLogos = getFallbackIcons(address, TOKEN_ASSET_BASE_URI ?? []) const tokenBlockie = useBlockie(address) diff --git a/packages/web3-shared/src/hooks/useAssetsFromProvider.ts b/packages/web3-shared/src/hooks/useAssetsFromProvider.ts index a0009446c097..76dfc9f9ddac 100644 --- a/packages/web3-shared/src/hooks/useAssetsFromProvider.ts +++ b/packages/web3-shared/src/hooks/useAssetsFromProvider.ts @@ -1,7 +1,7 @@ -import { useAccount } from './useAccount' -import { useChainDetailed } from './useChainDetailed' import { useWeb3Context, useWeb3State } from '../context' import { useAsyncRetry } from 'react-use' +import { useAccount } from './useAccount' +import { useChainDetailed } from './useChainDetailed' import { useNetworkType } from './useNetworkType' import { getNetworkTypeFromChainId } from '../utils' @@ -16,6 +16,7 @@ export function useAssetsFromProvider() { if (!account) return [] if (chainDetailed?.network !== 'mainnet') return [] if (getNetworkTypeFromChainId(chainDetailed.chainId) !== network) return [] + return getAssetList(account.toLowerCase(), network, portfolioProvider) - }, [account, network, portfolioProvider, chainDetailed]) + }, [account, portfolioProvider, network, chainDetailed]) }