去中心化金融这几年发展迅速,其中借贷协议是最成熟的应用方向之一,而Aave又是借贷赛道里市场份额最大的协议之一。如果你的团队已经有一个运行良好的React应用,想给它加上去中心化借贷能力,比如让用户存入资产赚取利息,或者抵押资产借出其他代币,那么这篇文章就是为你准备的。整个迁移过程并不需要推翻原有架构,核心工作是引入Web3交互层,并在UI层面新增借贷相关的页面与状态管理。

迁移前的准备:理解Aave的核心概念
在动手改代码之前,先花点时间弄清楚Aave的运作模型。Aave本质上是一组部署在区块链上的智能合约,用户通过它完成存款(Supply,也叫Deposit)和借款(Borrow)。存款人会获得aToken,这是一种生息代币,数量会随着利息累积而增长;借款人则需要抵押资产,抵押率和清算阈值由每个资产的市场参数决定。
Aave目前有V2和V3两个主要版本,新项目建议直接对接V3。V3引入了Portal跨链、eMode高效模式等特性,合约接口也更加清晰。前端与Aave交互有两条路径:一条是直接调用合约方法,另一条是使用Aave官方提供的UI Provider(@aave/protocol-ui和aave-ui-kit)。如果你的应用界面风格已经很成熟,推荐走第一条路,用ethers.js直接读写合约,侵入性最小。
还需要确认你的应用要支持哪些网络。Aave V3部署在Arbitrum、Optimism、Polygon、Base等多条链上,主网Ethereum也已支持。不同网络的合约地址完全不同,这部分配置后面会讲到。
引入Web3交互层:钱包连接与Provider注入
迁移的第一步是让React应用具备与区块链对话的能力。目前主流的连接方案是WalletConnect和MetaMask注入的window.ethereum对象。社区里最常用的库是wagmi加RainbowKit,它们封装了钱包检测、链切换、签名等细节,与React的Hooks体系契合度很高。
import { WagmiProvider, createConfig, http } from 'wagmi';
import { mainnet, arbitrum, base } from 'wagmi/chains';
import { injected } from 'wagmi/connectors';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const config = createConfig({
chains: [mainnet, arbitrum, base],
connectors: [injected()],
transports: {
[mainnet.id]: http(),
[arbitrum.id]: http(),
[base.id]: http(),
},
});
const queryClient = new QueryClient();
export function Web3Provider({ children }) {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
);
}这里有个容易被忽视的点:wagmi依赖react-query来管理异步状态,必须同时包裹QueryClientProvider,否则所有链上数据请求都无法工作。把Web3Provider放在应用根组件的最外层,原有的Context和状态管理不受影响。
关于ethers.js和web3.js的选择,现在业内基本倾向ethers.js(v6),wagmi内部也是基于它构建的。ethers.js的Contract抽象更简洁,类型提示也更友好,对TypeScript项目尤其友好。
调用Aave合约实现存款与借款
Aave V3的核心入口是Pool合约。存款调用supply方法,借款调用borrow方法,还款则是repay。在调用这些写方法之前,用户必须先对aToken对应的底层资产执行approve授权,这是ERC20的标准流程,很多新手在这里踩坑:approve的金额设多少、是否需要无限授权,都需要在产品层面想清楚。
下面是存款操作的完整示例,包含授权与supply两步:
import { ethers } from 'ethers';
import POOL_ABI from './abi/pool.json';
import ERC20_ABI from './abi/erc20.json';
// Aave V3 Pool地址以Arbitrum为例
const POOL_ADDRESS = '0x794a61358D6845594F94dc1DB02A252b5b4814aD';
export async function supplyUSDC(signer, amountInWei) {
const usdc = new ethers.Contract(
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
ERC20_ABI,
signer
);
// 第一步:授权Pool合约花费用户的USDC
const approveTx = await usdc.approve(POOL_ADDRESS, amountInWei);
await approveTx.wait();
// 第二步:执行存款
const pool = new ethers.Contract(POOL_ADDRESS, POOL_ABI, signer);
const supplyTx = await pool.supply(
'0xaf88d065e77c8cC2239327C5EDb3A432268e5831', // 资产地址
amountInWei,
await signer.getAddress(),
0 // referralCode,已废弃,传0即可
);
await supplyTx.wait();
return supplyTx.hash;
}借款流程类似,调用pool.borrow时需要指定利率模式(稳定或浮动)、金额和资产。借款前建议先通过PoolDataProvider合约查询用户的健康系数,也就是healthFactor,这个值低于1时仓位会被清算。在UI上实时展示健康系数并给出预警,是DeFi前端的基本素养。
金额计算也有讲究。Aave的资产精度不一致,USDC是6位小数,而ETH是18位,直接用ethers.parseUnits时要传入正确的位数,否则数额会差出好几个数量级。建议在项目里维护一份资产配置表,统一管理地址、精度、图标和展示名称。
数据展示与状态管理的整合
借贷前端需要展示的数据主要有三类:市场数据(各资产的存款利率、借款利率、总存款量)、用户数据(存款余额、借款余额、健康系数、可用借款额度)、aToken余额。这些数据变化频繁,不适合塞进Redux这类全局Store,更合理的做法是用wagmi的useContractRead按需拉取,组件卸载时自动清理。
import { useAccount, useContractRead } from 'wagmi';
export function useUserReserveData(assetAddress) {
const { address } = useAccount();
return useContractRead({
address: POOL_ADDRESS,
abi: POOL_ABI,
functionName: 'getUserAccountData',
args: [address],
watch: true, // 每个区块自动刷新
select: (data) => ({
totalCollateral: data[0],
totalDebt: data[1],
availableBorrows: data[2],
healthFactor: Number(data[5]) / 1e18,
}),
});
}如果觉得逐个合约调用太繁琐,也可以走Aave的GraphQL数据接口,它聚合了市场与用户的全量数据,一次请求就能拿到渲染整个仪表盘所需的字段,对首屏性能更友好。GraphQL适合读,写操作仍然必须走合约交易,两者结合是常见的架构。
上线前的注意事项
合约地址配置是重中之重。建议按链维护一份JSON配置文件,切换网络时动态加载对应地址,绝对不要把测试网地址带到生产环境。历史上不止一个项目因为配错地址造成用户资金损失。所有写操作上线前必须在测试网(如Sepolia或Arbitrum Sepolia)完整跑通,包括授权、存款、借款、还款和提款的全流程。
其次是错误处理。钱包交易随时可能被用户拒绝、因滑点或Gas不足而失败,前端必须捕获这些异常并给出可读的提示,而不是抛出一串十六进制错误码。ethers.js的报错对象里通常带有reason字段,值得做一层统一解析。
最后是安全审计与依赖管理。Web3生态的npm包更新极快,锁定版本、定期检查依赖是否有已知漏洞,并考虑接入Sentry之类的监控来追踪合约交互失败率,这些工程化工作能让你的DeFi前端在真实资金环境下站得更稳。迁移本身不难,难的是把每一个边界情况都处理到位。