1、前端如何做多链支持(Ethereum、Polygon、BSC 等)?
答:前端要支持多链,就要解决:
- 如何连接不同链(
Ethereum / Polygon / BSC
...) - 如何获取链上数据(余额、交易、合约交互)
- 如何在链之间切换(用户体验 + 钱包交互)
常见做法
- 多链
RPC Provider
- 前端可以为每条链准备不同的
RPC URL
- 根据用户选择的链,切换使用的
provider
。
- 前端可以为每条链准备不同的
js
import { JsonRpcProvider } from 'ethers';
const providers = {
ethereum: new JsonRpcProvider('https://mainnet.infura.io/v3/xxx'),
polygon: new JsonRpcProvider('https://polygon-rpc.com'),
bsc: new JsonRpcProvider('https://bsc-dataseed.binance.org/')
}
- 检测 & 切换链(MetaMask 等钱包)
- 使用
window.ethereum.request({ method: "eth_chainId" })
获取当前钱包所连链。 - 如果用户在
Polygon
页签,但钱包在Ethereum
→ 提示切换。 - 使用
wallet_switchEthereumChain
或wallet_addEthereumChain
让用户在钱包里一键切换:
- 使用
js
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId: '0x89' }] // Polygon chainId
})
- 多链合约配置
- 同一个
DApp
的合约地址可能在不同链上不同。 - 前端需要维护一个
chainId → contractAddress
的映射表:
- 同一个
js
const contractAddresses = {
1: '0xEthereumContract',
137: '0xPolygonContract',
56: '0xBscContract'
}
- 多链
Token
显示:对于钱包类应用,需要动态获取账户在不同链上的 Token:- 方式一:调用链上标准接口(如
ERC-20
的balanceOf
)。 - 方式二:集成 第三方 API(如 Alchemy、Covalent、Moralis),一次返回某地址在多条链的资产。
- 方式一:调用链上标准接口(如
用户体验优化
- 自动识别链:进入
DApp
时,自动检测当前链是否匹配,如果不对 → 引导切换。 - 统一状态管理:通过
zustand / redux
存储chainId
和provider
,保证全局一致。 - 多链兼容组件:写通用
useContract(chainId, abi) hook
,动态返回不同链的合约实例。
坑点
- 不同链的
gas
机制、手续费单位 不一样(Polygon/BSC 用 gwei,但实际消耗很低)。 - 有些链 RPC 不稳定,需要备用节点。
- 浏览器钱包可能不支持某些侧链 (要用
wallet_addEthereumChain
)。
2、如何处理链上数据缓存,避免重复请求?
答:链上数据(余额、价格、交易状态)往往变化频率不高,但请求成本高、延迟大,如果不做缓存,很容易造成重复请求、页面卡顿、节点限流。
为什么要缓存链上数据?
- RPC 调用慢:一次
eth_call
或getBalance
通常要几十~几百毫秒。 - 节点限流:像
Infura、Alchemy
免费套餐有QPS
限制,过多请求会被block
。 - 数据变化频率低:地址余额不会每秒变,重复请求没有意义。
常见缓存手段
- 内存缓存(短时缓存):前端直接在内存里缓存请求结果,避免同一页面内重复调用。
- 持久化缓存(localStorage / IndexedDB):适合 代币列表、合约 ABI 等不常更新的数据。
- 请求合并(Deduplication):用
SWR / React Query
这类库,能自动避免重复请求:- 相同 key 的请求只会发一次;
- 自动缓存结果,支持
stale-while-revalidate
; - 内置请求重试、失效策略。
jsx
import useSWR from 'swr';
import { ethers } from 'ethers';
const fetchBalance = async (address: string) => {
const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
return await provider.getBalance(address);
};
function Balance({ address }) {
const { data: balance } = useSWR(
['balance', address],
() => fetchBalance(address),
{ refreshInterval: 30_000 } // 30s 自动刷新
);
return <div>{balance && ethers.utils.formatEther(balance)}</div>;
}
- 区块级缓存(block-level cache):链上数据只会在新区块出现时才会变化。因此缓存策略可以按区块号来做:
- 获取当前
blockNumber
; - 只在新区块出现时才重新请求。
- 获取当前
jsx
provider.on('block', async blockNumber => {
const balance = await provider.getBalance(address);
console.log(`区块 ${blockNumber} 余额更新:`, balance.toString());
})
- 使用第三方 API 的聚合缓存:像
Alchemy、Covalent、Moralis
提供的余额、代币价格API
,本身就做了缓存和索引,速度远快于自己扫链。- 适合需要 批量地址、批量 token 查询;
- 避免前端对
RPC
节点的高频调用。
3、如何在前端处理代币价格或余额的异步刷新?
异步刷新面临的问题:
- 数据延迟:区块链节点返回的余额是实时的,但频繁请求会导致卡顿/性能差。
- 价格获取:代币价格不是链上数据,需要从**预言机(如 Chainlink)或第三方 API(CoinGecko、Covalent、Alchemy)**获取。
- 频繁更新:移动端/弱网环境下,过多请求很影响体验。
常见处理方案
-
余额刷新
- 定时轮询:比如每 15~30 秒调用一次
provider.getBalance(address)
或合约balanceOf
:
jsximport { ethers } from 'ethers'; async function fetchBalance(provider, address, tokenAddress) { if (!tokenAddress) { // 原生代币(ETH) return await provider.getBalance(address); }else { // ERC20 const abi = ['function balanceOf(address) view returns (uint256)']; const token = new ethers.Contract(tokenAddress, abi, provider); return await token.balanceOf(address); } }
- 区块事件监听:
provider.on("block", callback)
每出一个新区块时去更新余额,更加精准:
jsxprovider.on('block', async blockNumber => { const balance = await provider.getBalance(address); console.log('最新余额', ethers.utils.formatEther(balance)); })
- 链下服务:使用
Alchemy、Moralis
等提供的 余额聚合API
,能一次拿到所有代币余额,而不是自己逐个调用balanceOf
jsximport Moralis from 'moralis'; const getBalance = async () => { try { await Moralis.start({ apiKey: apiKey}) const response = await Moralis.EvmApi.wallets.getWalletTokenBalancesPrice({ chain: toBeHex(8453), // base 主链 address: '0x...B7' // 要查询的地址 }) console.log('raw', response.result); }catch (e) { console.error(e); } }
- 定时轮询:比如每 15~30 秒调用一次
-
价格刷新
- 第三方 API:使用
CoinGecko、Binance API、Covalent API
获取实时价格,定时轮询(10~60s 一次即可)。 - 链上预言机:对于关键交易(如 swap),用
Chainlink Aggregator
合约读价格,避免依赖第三方 API。
jsx// 通过 链上预言机合约读取价格 ETH/USD import { Contract, JsonRpcProvider, formatUnits } from 'ethers'; import AggregatorV3ABI from '@chainlink/contracts/abi/v0.8/AggregatorV3Interface.json'; const ETH_USD_PRICE_FEED = '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419'; const getEthUsdPrice = async () => { // 连接 RPC 节点 const provider = new JsonRpcProvider(ethRpc?.rpcUrl); // 创建合约对象 const priceFeed = new Contract(ETH_USD_PRICE_FEED, AggregatorV3ABI, provider); // 调用最新价格 const roundData = await priceFeed.latestRoundData(); // 获取小数位 const decimals = await priceFeed.decimals(); console.log('decimals', decimals); // 格式化价格 const price: any = formatUnits(roundData.answer, decimals); console.log('ETH/USD 价格:', price); }
- 第三方 API:使用
性能优化
- 请求合并:如果需要多个代币余额,可以用
Multicall
合约 一次性请求,减少RPC
请求数。 - 节流/防抖:避免用户频繁切换页面时触发过多请求。
- 状态管理:用
React Query / SWR / Zustand
来缓存请求结果,避免相同数据重复请求。 - 条件刷新:前端可以在 页面可见时(
Page Visibility API
) 才轮询,后台挂起时暂停请求。