Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion packages/maskbook/src/plugins/Wallet/pipes/index.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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',
}
}
}
99 changes: 60 additions & 39 deletions packages/maskbook/src/plugins/Wallet/services/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { unreachable } from '@dimensiondev/kit'
import {
Asset,
ChainId,
CollectibleProvider,
createERC1155Token,
createERC20Token,
createERC721Token,
Expand All @@ -13,18 +14,24 @@ import {
getTokenConstants,
isChainIdMainnet,
NetworkType,
pow10,
CollectibleProvider,
PortfolioProvider,
pow10,
} from '@masknet/web3-shared'
import BigNumber from 'bignumber.js'
import { values } from 'lodash-es'
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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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[]
}
26 changes: 23 additions & 3 deletions packages/maskbook/src/plugins/Wallet/types/zerion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -112,6 +128,12 @@ export interface ZerionTransactionResponseBody extends SocketResponseBody {
}
}

type ZerionAssetResponseBodyPayload = {
assets: Record<string, ZerionAddressAsset>
'bsc-assets': Record<string, ZerionAddressCovalentAsset>
'polygon-assets': Record<string, ZerionAddressCovalentAsset>
}

export interface ZerionAssetResponseBody extends SocketResponseBody {
meta: {
address: string
Expand All @@ -120,7 +142,5 @@ export interface ZerionAssetResponseBody extends SocketResponseBody {
currency: string
status: string
}
payload: {
assets: Record<string, ZerionAddressAsset>
}
payload: Partial<ZerionAssetResponseBodyPayload>
}
2 changes: 1 addition & 1 deletion packages/shared/src/UI/components/TokenIcon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
7 changes: 4 additions & 3 deletions packages/web3-shared/src/hooks/useAssetsFromProvider.ts
Original file line number Diff line number Diff line change
@@ -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'

Expand All @@ -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])
}