Deposit в Aave V3
Как внести WETH в Aave V3 через Pool.supply, получить aWETH и проверить позицию в ethers v6.
Deposit в Aave V3 называется supply
В интерфейсе Aave пользователь делает deposit. В контракте Aave V3 это метод supply.
Flow похож на другие DeFi-протоколы: сначала пользователь разрешает Pool списать ERC-20 токены через approve, затем вызывает supply и получает aToken. Для WETH это aWETH.
Вносим WETH в Aave V3
Пример запускается на локальном fork Ethereum mainnet. Он использует настоящие mainnet-контракты Aave V3 и WETH, но не отправляет транзакции в реальную сеть.
Создайте проект:
mkdir aave-v3-deposit-example
cd aave-v3-deposit-example
npm init -y
npm pkg set type="module"
npm install ethers tsx
npm install --save-dev hardhat typescript @types/node
mkdir srcСоздайте файл hardhat.config.js:
export default {
solidity: "0.8.20",
};В первом терминале запустите mainnet fork:
npx hardhat node --fork https://ethereum-rpc.publicnode.comСоздайте файл src/deposit-weth.ts:
import { ethers } from "ethers";
const LOCAL_FORK_RPC_URL = "http://127.0.0.1:8545";
const HARDHAT_ACCOUNT_PRIVATE_KEY =
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
const AAVE_V3_POOL_ADDRESS = "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2";
const WETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2";
const AWETH_ADDRESS = "0x4d5F47FA6A74757f35C14fD3a6Ef8E3C9BC514E8";
const WETH_DECIMALS = 18;
const REFERRAL_CODE = 0;
const WETH_ABI = [
"function deposit() external payable",
"function approve(address spender,uint256 amount) external returns (bool)",
"function allowance(address owner,address spender) external view returns (uint256)",
"function balanceOf(address account) external view returns (uint256)",
];
const ATOKEN_ABI = [
"function balanceOf(address account) external view returns (uint256)",
];
const AAVE_POOL_ABI = [
"function supply(address asset,uint256 amount,address onBehalfOf,uint16 referralCode) external",
];
const provider = new ethers.JsonRpcProvider(LOCAL_FORK_RPC_URL);
const wallet = new ethers.Wallet(HARDHAT_ACCOUNT_PRIVATE_KEY, provider);
const weth = new ethers.Contract(WETH_ADDRESS, WETH_ABI, wallet);
const aWeth = new ethers.Contract(AWETH_ADDRESS, ATOKEN_ABI, provider);
const pool = new ethers.Contract(AAVE_V3_POOL_ADDRESS, AAVE_POOL_ABI, wallet);
const amount = ethers.parseUnits("1", WETH_DECIMALS);
console.log(`User: ${wallet.address}`);
const wrapTx = await weth.deposit({ value: amount });
await wrapTx.wait();
const wethBefore = await weth.balanceOf(wallet.address);
const aWethBefore = await aWeth.balanceOf(wallet.address);
console.log(`WETH before: ${ethers.formatUnits(wethBefore, WETH_DECIMALS)}`);
console.log(`aWETH before: ${ethers.formatUnits(aWethBefore, WETH_DECIMALS)}`);
const approveTx = await weth.approve(AAVE_V3_POOL_ADDRESS, amount);
await approveTx.wait();
const allowance = await weth.allowance(wallet.address, AAVE_V3_POOL_ADDRESS);
console.log(`Pool allowance: ${ethers.formatUnits(allowance, WETH_DECIMALS)}`);
const supplyTx = await pool.supply(
WETH_ADDRESS,
amount,
wallet.address,
REFERRAL_CODE,
);
const receipt = await supplyTx.wait();
const wethAfter = await weth.balanceOf(wallet.address);
const aWethAfter = await aWeth.balanceOf(wallet.address);
console.log(`Supply tx: ${receipt?.hash}`);
console.log(`WETH after: ${ethers.formatUnits(wethAfter, WETH_DECIMALS)}`);
console.log(`aWETH after: ${ethers.formatUnits(aWethAfter, WETH_DECIMALS)}`);Во втором терминале запустите deposit:
npx tsx src/deposit-weth.tsСкрипт выведет примерно такой результат:
User: 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
WETH before: 1.0
aWETH before: 0.0
Pool allowance: 1.0
Supply tx: 0x...
WETH after: 0.0
aWETH after: 1.0На реальном mainnet aWETH баланс со временем растёт за счёт процентов. На локальном fork вы увидите состояние на момент запуска.
Как это работает
AAVE_V3_POOL_ADDRESS — адрес Aave V3 Pool в Ethereum mainnet.
WETH_ADDRESS — underlying asset, который пользователь вносит в протокол.
AWETH_ADDRESS — aToken для WETH. Aave выпускает его пользователю после supply.
weth.deposit() — превращает ETH локального Hardhat-аккаунта в WETH. Aave Pool принимает ERC-20 токен, а не нативный ETH.
weth.approve(AAVE_V3_POOL_ADDRESS, amount) — разрешает Aave Pool списать WETH с адреса пользователя.
pool.supply(WETH_ADDRESS, amount, wallet.address, 0) — вносит WETH в Aave V3 и минтит aWETH на onBehalfOf.
referralCode: 0 — стандартное значение, если у приложения нет referral-интеграции с Aave.
Что такое aToken
aToken — это токен позиции поставщика ликвидности в Aave.
Когда пользователь вносит WETH, он получает aWETH. Баланс aWETH отражает долю пользователя в пуле и увеличивается вместе с начисленными процентами. При withdraw Aave сжигает aWETH и возвращает underlying asset.
Для разработчика важное следствие: позиция в Aave читается не по балансу WETH, а по балансу соответствующего aToken.
Частые ошибки
Вызвать deposit, а не supply → в Aave V3 контрактный метод называется supply. deposit — это пользовательский термин из интерфейса.
Забыть approve → Pool не сможет списать WETH и транзакция откатится.
Передать нативный ETH вместо WETH → supply принимает ERC-20 asset. Для ETH сначала используйте WETH или отдельный gateway-flow.
Проверять только WETH баланс после supply → после deposit WETH уходит в Pool, а позиция появляется как aWETH.
Что дальше
- Wrapped ETH: WETH deposit и withdraw — понять, почему DeFi-протоколы часто работают с WETH
- ERC-20: approve и allowance — разобраться, зачем Aave Pool нужен allowance
- Swap через Uniswap V3 Router — получить нужный asset перед deposit в lending-протокол