Files
assetx/webapp/hooks/useTokenList.ts
default 2ee4553b71 init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、
antdesign(管理后台)、landingpage(营销落地页)、
数据库 SQL 和配置文件。
2026-03-27 11:26:43 +00:00

97 lines
3.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { useReadContracts } from 'wagmi'
import { fetchProducts, Product } from '@/lib/api/fundmarket'
import { Token } from '@/lib/api/tokens'
import { getDynamicOverride, getDynamicOverrideByName } from '@/lib/contracts/registry'
const ERC20_DECIMALS_ABI = [
{ name: 'decimals', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ type: 'uint8' }] },
] as const
function productToToken(p: Product): Token {
return {
symbol: p.tokenSymbol,
name: p.name,
decimals: p.decimals ?? 18,
iconUrl: p.iconUrl,
contractAddress: p.contractAddress ?? '',
chainId: p.chainId ?? 0,
tokenType: p.token_role === 'stablecoin' ? 'stablecoin' : 'yield-token',
}
}
/**
* Global token list hook — fetches once, cached 5 min.
* All components share the same ['token-list'] React Query cache.
* Each token has onChainDecimals (live from chain) with decimals as fallback.
*/
export function useTokenList() {
const { data: products = [], isLoading: isProductsLoading } = useQuery({
queryKey: ['token-list'],
queryFn: () => fetchProducts(true),
staleTime: 5 * 60 * 1000,
})
const baseTokens: Token[] = useMemo(
() => products
.filter(p => p.token_role === 'stablecoin' || p.token_role === 'yt_token')
.map(productToToken),
[products]
)
// 批量读取每个 token 的链上 decimals按 token 配置的 chainId 发起请求
// 仅包含有合约地址的 token记录原始索引以便回写
const tokensWithAddress = useMemo(
() => baseTokens
.map((t, i) => ({ t, i }))
.filter(({ t }) => !!t.contractAddress),
[baseTokens]
)
const decimalsContracts = useMemo(
() => tokensWithAddress.map(({ t }) => ({
address: t.contractAddress as `0x${string}`,
abi: ERC20_DECIMALS_ABI,
functionName: 'decimals' as const,
chainId: t.chainId,
})),
[tokensWithAddress]
)
const { data: onChainData } = useReadContracts({
contracts: decimalsContracts,
query: { enabled: decimalsContracts.length > 0 },
})
// 将链上精度回写到对应 token按原始索引匹配避免过滤后索引错位
const onChainDecimalsMap = new Map<number, number>()
tokensWithAddress.forEach(({ i }, j) => {
if (onChainData?.[j]?.status === 'success') {
onChainDecimalsMap.set(i, Number(onChainData[j].result))
}
})
// 合并链上精度 + 合约地址contractAddress 为空时回退到注册表)
const tokens: Token[] = baseTokens.map((t, i) => {
// DB 未配置地址/chainId 时,先按精确 chainId 查注册表,再按 name 跨链搜索兜底
let contractAddress = t.contractAddress || getDynamicOverride(t.symbol, t.chainId) || ''
let chainId = t.chainId
if (!contractAddress) {
const found = getDynamicOverrideByName(t.symbol)
if (found) {
contractAddress = found.address
chainId = found.chainId // 同时修正 chainId
}
}
const onChainDecimals = onChainDecimalsMap.has(i) ? onChainDecimalsMap.get(i) : undefined
return { ...t, contractAddress, chainId, onChainDecimals }
})
const stablecoins = tokens.filter(t => t.tokenType === 'stablecoin')
const yieldTokens = tokens.filter(t => t.tokenType === 'yield-token')
const bySymbol: Record<string, Token> = Object.fromEntries(tokens.map(t => [t.symbol, t]))
return { tokens, stablecoins, yieldTokens, bySymbol, isLoading: isProductsLoading }
}