Skip to content

Commit

Permalink
feat: get assets support trusted tokens (#8706)
Browse files Browse the repository at this point in the history
* feat: get assets support trusted tokens

* feat: add get trust assets for fungible token api

* refactor: add getTrustedFungibleAssets api

* fix: typo

* fix: remove unused code

---------

Co-authored-by: lelenei <zwpsky@gmail.com>
  • Loading branch information
Lanttcat and lelenei committed Feb 21, 2023
1 parent b0a2789 commit e61cb74
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 4 deletions.
2 changes: 1 addition & 1 deletion packages/plugins/Solana/src/state/Hub/hub.ts
Expand Up @@ -27,7 +27,7 @@ class HubFungibleClient extends HubStateFungibleClient<ChainId, SchemaType> {
const options = this.getOptions(initial)

// only the first page is available
if ((options.indicator ?? 0) > 0) return []
if ((options.indicator?.index ?? 0) > 0) return []

return this.getPredicateProviders<FungibleTokenAPI.Provider<ChainId, SchemaType> | PriceAPI.Provider<ChainId>>(
{
Expand Down
15 changes: 14 additions & 1 deletion packages/web3-hooks/base/src/useFungibleAssets.ts
Expand Up @@ -16,6 +16,7 @@ import { useWeb3Hub } from './useWeb3Hub.js'
import { useWeb3State } from './useWeb3State.js'
import { useTrustedFungibleTokens } from './useTrustedFungibleTokens.js'
import { useBlockedFungibleTokens } from './useBlockedFungibleTokens.js'
import { unionWith } from 'lodash-es'

export function useFungibleAssets<S extends 'all' | void = void, T extends NetworkPluginID = NetworkPluginID>(
pluginID?: T,
Expand All @@ -40,9 +41,21 @@ export function useFungibleAssets<S extends 'all' | void = void, T extends Netwo
size: 50,
})
})

const trustedAssetsIterator = pageableToIterator(async (indicator?: HubIndicator) => {
if (!hub.getTrustedFungibleAssets) return
return hub.getTrustedFungibleAssets(account, trustedTokens, { indicator, size: 50 })
})
const assets = await asyncIteratorToArray(iterator)
const trustedAssets = await asyncIteratorToArray(trustedAssetsIterator)

const _assets = unionWith(
assets,
trustedAssets,
(a, z) => isSameAddress(a.address, z.address) && a.chainId === z.chainId,
)

const filteredAssets = assets.length && schemaType ? assets.filter((x) => x.schema === schemaType) : assets
const filteredAssets = _assets.length && schemaType ? _assets.filter((x) => x.schema === schemaType) : _assets

return filteredAssets
.filter((x) => !isBlockedToken(x))
Expand Down
89 changes: 89 additions & 0 deletions packages/web3-providers/src/Contracts/apis/FungibleTokenAPI.ts
@@ -0,0 +1,89 @@
import {
createIndicator,
createPageable,
CurrencyType,
FungibleAsset,
FungibleToken,
HubOptions,
multipliedBy,
toFixed,
formatBalance,
} from '@masknet/web3-shared-base'
import type { AbiItem } from 'web3-utils'
import BalanceCheckerABI from '@masknet/web3-contracts/abis/BalanceChecker.json'
import type { BalanceChecker } from '@masknet/web3-contracts/types/BalanceChecker.js'
import { ChainId, createContract, getEthereumConstant, SchemaType } from '@masknet/web3-shared-evm'
import type { FungibleTokenAPI } from '../../entry-types.js'
import { Web3API } from '../../EVM/index.js'
import { EMPTY_LIST } from '@masknet/shared-base'
import { CoinGeckoPriceEVM } from '../../entry.js'
import { uniq } from 'lodash-es'

export class ContractFungibleTokenAPI implements FungibleTokenAPI.Provider<ChainId, SchemaType> {
private web3 = new Web3API()

private createWeb3(chainId: ChainId) {
return this.web3.getWeb3(chainId)
}

private createContract(chainId: ChainId) {
const address = getEthereumConstant(chainId, 'BALANCE_CHECKER_ADDRESS')
if (!address) throw new Error('Failed to create multicall contract.')

const web3 = this.createWeb3(chainId)
const contract = createContract<BalanceChecker>(web3, address, BalanceCheckerABI as unknown as AbiItem[])
if (!contract) throw new Error('Failed to create multicall contract.')

return contract
}

async createAssets(fungibleToken: FungibleToken<ChainId, SchemaType>, chainId: ChainId, balance: number) {
const price = await CoinGeckoPriceEVM.getFungibleTokenPrice(chainId, fungibleToken.address)

return {
...fungibleToken,
balance: balance.toFixed(),
price: {
[CurrencyType.USD]: toFixed(price),
},
value: {
[CurrencyType.USD]: multipliedBy(price ?? 0, formatBalance(balance, fungibleToken.decimals)).toFixed(),
},
}
}

async getTrustedAssets(
address: string,
trustedFungibleTokens?: Array<FungibleToken<ChainId, SchemaType>>,
options?: HubOptions<ChainId>,
) {
if (!trustedFungibleTokens) createPageable(EMPTY_LIST, createIndicator(options?.indicator))

const chains = uniq(trustedFungibleTokens?.map((x) => x.chainId)) ?? [ChainId.Mainnet]
let result: Array<FungibleAsset<ChainId, SchemaType>> = EMPTY_LIST

for (const chainId of chains) {
const contract = this.createContract(chainId)
if (!contract) return createPageable(EMPTY_LIST, createIndicator(options?.indicator))

const balances = await contract.methods
.balances([address], trustedFungibleTokens?.map((x) => x.address) ?? [])
.call()

const requests = balances
.map((x, i) => {
if (!trustedFungibleTokens?.[i]) return
return this.createAssets(trustedFungibleTokens[i], chainId, Number.parseInt(x, 10))
})
.filter(Boolean)

const assets = (await Promise.allSettled(requests))
.map((x) => (x.status === 'fulfilled' ? x.value : undefined))
.filter(Boolean) as Array<FungibleAsset<ChainId, SchemaType>>

result = [...result, ...assets]
}

return createPageable(result, createIndicator(options?.indicator))
}
}
1 change: 1 addition & 0 deletions packages/web3-providers/src/Contracts/index.ts
@@ -0,0 +1 @@
export * from './apis/FungibleTokenAPI.js'
33 changes: 32 additions & 1 deletion packages/web3-providers/src/DeBank/apis/FungibleTokenAPI.ts
@@ -1,22 +1,33 @@
import urlcat from 'urlcat'
import { unionWith } from 'lodash-es'
import { createPageable, HubOptions, createIndicator, isSameAddress } from '@masknet/web3-shared-base'
import {
createPageable,
HubOptions,
createIndicator,
isSameAddress,
FungibleToken,
HubIndicator,
} from '@masknet/web3-shared-base'
import type { ChainId, SchemaType } from '@masknet/web3-shared-evm'
import { formatAssets, resolveDeBankAssetId } from '../helpers.js'
import type { WalletTokenRecord } from '../types.js'
import { fetchJSON, getNativeAssets } from '../../entry-helpers.js'
import type { FungibleTokenAPI } from '../../entry-types.js'
import { ContractFungibleTokenAPI } from '../../Contracts/index.js'

const DEBANK_OPEN_API = 'https://debank-proxy.r2d2.to'

export class DeBankFungibleTokenAPI implements FungibleTokenAPI.Provider<ChainId, SchemaType> {
private contractFungibleToken = new ContractFungibleTokenAPI()

async getAssets(address: string, options?: HubOptions<ChainId>) {
const result = await fetchJSON<WalletTokenRecord[] | undefined>(
urlcat(DEBANK_OPEN_API, '/v1/user/all_token_list', {
id: address,
is_all: false,
}),
)

return createPageable(
unionWith(
formatAssets(
Expand All @@ -40,4 +51,24 @@ export class DeBankFungibleTokenAPI implements FungibleTokenAPI.Provider<ChainId
createIndicator(options?.indicator),
)
}

async getTrustedAssets(
address: string,
trustedFungibleTokens?: Array<FungibleToken<ChainId, SchemaType>>,
options?: HubOptions<ChainId, HubIndicator>,
) {
const trustTokenAssets = await this.contractFungibleToken.getTrustedAssets(
address,
trustedFungibleTokens,
options,
)
return createPageable(
unionWith(
trustTokenAssets.data,
getNativeAssets(),
(a, z) => isSameAddress(a.address, z.address) && a.chainId === z.chainId,
),
createIndicator(options?.indicator),
)
}
}
15 changes: 14 additions & 1 deletion packages/web3-providers/src/types/FungibleToken.ts
@@ -1,4 +1,11 @@
import type { FungibleAsset, Pageable, HubOptions, HubIndicator, FungibleTokenStats } from '@masknet/web3-shared-base'
import type {
FungibleAsset,
Pageable,
HubOptions,
HubIndicator,
FungibleTokenStats,
FungibleToken,
} from '@masknet/web3-shared-base'

export namespace FungibleTokenAPI {
export interface Provider<ChainId, SchemaType, Indicator = HubIndicator> {
Expand All @@ -14,6 +21,12 @@ export namespace FungibleTokenAPI {
address: string,
options?: HubOptions<ChainId, Indicator>,
) => Promise<Pageable<FungibleAsset<ChainId, SchemaType>, Indicator>>
/** Get trusted fungible assets. */
getTrustedAssets?: (
address: string,
trustedFungibleTokens?: Array<FungibleToken<ChainId, SchemaType>>,
options?: HubOptions<ChainId, Indicator>,
) => Promise<Pageable<FungibleAsset<ChainId, SchemaType>, Indicator>>
/** Get fungible token stats. */
getStats?: (
address: string,
Expand Down
6 changes: 6 additions & 0 deletions packages/web3-shared/base/src/specs/index.ts
Expand Up @@ -1232,6 +1232,12 @@ export interface HubFungible<ChainId, SchemaType, GasOption, Web3HubOptions = Hu
account: string,
initial?: Web3HubOptions,
) => Promise<Pageable<FungibleAsset<ChainId, SchemaType>>>
/** Get fungible assets owned by the give trusted fungible token. */
getTrustedFungibleAssets?: (
account: string,
trustedFungibleTokens?: Array<FungibleToken<ChainId, SchemaType>>,
initial?: Web3HubOptions,
) => Promise<Pageable<FungibleAsset<ChainId, SchemaType>>>
/** Get balance of a fungible token owned by the given account. */
getFungibleTokenBalance?: (address: string, initial?: Web3HubOptions) => Promise<number>
/** Get stats data of a fungible token */
Expand Down
13 changes: 13 additions & 0 deletions packages/web3-state/src/Hub/FungibleClient.ts
Expand Up @@ -129,6 +129,19 @@ export class HubStateFungibleClient<ChainId, SchemaType> extends HubStateBaseCli
)
}

async getTrustedFungibleAssets(
account: string,
trustedFungibleTokens?: Array<FungibleToken<ChainId, SchemaType>>,
initial?: HubOptions<ChainId>,
): Promise<Pageable<FungibleAsset<ChainId, SchemaType>>> {
const options = this.getOptions(initial, { account })
const providers = this.getProviders(initial)
return attemptUntil(
providers.map((x) => () => x.getTrustedAssets?.(options.account, trustedFungibleTokens, options)),
createPageable(EMPTY_LIST, createIndicator(options.indicator)),
)
}

async getFungibleToken(
address: string,
initial?: HubOptions<ChainId, HubIndicator> | undefined,
Expand Down

0 comments on commit e61cb74

Please sign in to comment.