Help us improve
Share bugs, ideas, or general feedback.
From agentic-defi
Use when interacting with Aave V3 protocol (supply, borrow, repay, withdraw, check positions, fetch investment opportunities) via the agentic-wallet MCP. Covers reserve APY fetching, calldata encoding, permission setup, approve+action batching, and health factor monitoring.
npx claudepluginhub bootnodedev/agentic-defi --plugin agentic-defiHow this skill is triggered — by the user, by Claude, or both
Slash command
/agentic-defi:aave-agentic-walletThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Aave V3 interactions require ABI-encoded calldata passed to the agentic-wallet MCP tools. The wallet handles signing and submission — you encode the call and specify the Pool address as `to`.
Creates p5.js generative art with seeded randomness, noise fields, and interactive parameter exploration. Use for algorithmic art, flow fields, or particle systems.
Share bugs, ideas, or general feedback.
Aave V3 interactions require ABI-encoded calldata passed to the agentic-wallet MCP tools. The wallet handles signing and submission — you encode the call and specify the Pool address as to.
Transaction tools:
execute_tx / execute_batch_tx — Autonomous execution using session permissions (requires grant_permissions first)send_tx — One-time user-approved transaction (user signs via URL, no session needed)Key constraint: ERC-20 operations (supply, repay) require a prior approve to the Pool. Always batch these atomically with execute_batch_tx.
All scripts run via:
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js <subcommand> [options]
Standalone usage (not as a plugin): run from the repository root with
node dist/skills/aave-agentic-wallet/aave.js <subcommand> [options]
Fetch and rank supply/borrow APYs across one or all Aave V3 networks.
# All networks
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js opportunities --network all
# Single network
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js opportunities --network arbitrum
Show health factor, collateral, and debt for a wallet on a given network.
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js position \
--wallet 0xYourWalletAddress \
--network arbitrum
Print the parameters to pass to the grant_permissions MCP tool. Covers approve, supply, borrow, repay, and withdraw for a given token.
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js grant-permissions \
--wallet 0xYourWalletAddress \
--network arbitrum \
--token 0xaf88d065e77c8cc2239327c5edb3a432268e5831 \
--limit 1000000000 \
--expiry-hours 24
Print the parameters to pass to execute_batch_tx (approve + supply).
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js supply \
--wallet 0xYourWalletAddress \
--network arbitrum \
--asset 0xaf88d065e77c8cc2239327c5edb3a432268e5831 \
--amount 100
Print the parameters to pass to execute_tx (borrow).
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js borrow \
--wallet 0xYourWalletAddress \
--network arbitrum \
--asset 0xaf88d065e77c8cc2239327c5edb3a432268e5831 \
--amount 50
Print the parameters to pass to execute_batch_tx (approve + repay). Use --amount max to repay full debt.
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js repay \
--wallet 0xYourWalletAddress \
--network arbitrum \
--asset 0xaf88d065e77c8cc2239327c5edb3a432268e5831 \
--amount max
Print the parameters to pass to execute_tx (withdraw). Use --amount max to withdraw full balance.
node ${CLAUDE_PLUGIN_DATA}/dist/skills/aave-agentic-wallet/aave.js withdraw \
--wallet 0xYourWalletAddress \
--network arbitrum \
--asset 0xaf88d065e77c8cc2239327c5edb3a432268e5831 \
--amount max
| Network | Pool Address |
|---|---|
| Ethereum | 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2 |
| Arbitrum | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
| Optimism | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
| Polygon | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
| Base | 0xA238Dd80C259a72e81d7e4664a9801593F98d1c5 |
| Avalanche | 0x794a61358D6845594F94dc1DB02A252b5b4814aD |
Canonical source: https://aave.com/docs/resources/addresses and the Aave Address Book
Interest rate mode: Always 2 (variable). Stable rate (1) is deprecated.
All calldata uses encodeFunctionData from viem:
import { encodeFunctionData, parseUnits, maxUint256 } from "viem";
const POOL = "0x794a61358D6845594F94dc1DB02A252b5b4814aD"; // Arbitrum
const USDC = "0xaf88d065e77c8cc2239327c5edb3a432268e5831";
const userAddress = "0xYourWalletAddress";
const amount = parseUnits("100", 6); // 100 USDC (6 decimals)
Requires: approve(Pool, amount) + supply(asset, amount, onBehalfOf, 0)
// Step 1: approve
const approveTx = {
to: USDC,
data: encodeFunctionData({
abi: [{ name: "approve", type: "function", inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" }
], outputs: [{ type: "bool" }] }],
functionName: "approve",
args: [POOL, amount],
}),
value: "0x0",
};
// Step 2: supply
const supplyTx = {
to: POOL,
data: encodeFunctionData({
abi: [{ name: "supply", type: "function", inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "onBehalfOf", type: "address" },
{ name: "referralCode", type: "uint16" }
], outputs: [] }],
functionName: "supply",
args: [USDC, amount, userAddress, 0],
}),
value: "0x0",
};
// Send atomically — get sessionId from list_wallets
await execute_batch_tx({ sessionId: "<from list_wallets>", calls: [approveTx, supplyTx], feeToken: USDC });
No prior approval needed. Pool pulls nothing — it sends tokens to you.
const borrowTx = {
to: POOL,
data: encodeFunctionData({
abi: [{ name: "borrow", type: "function", inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "interestRateMode", type: "uint256" },
{ name: "referralCode", type: "uint16" },
{ name: "onBehalfOf", type: "address" }
], outputs: [] }],
functionName: "borrow",
args: [USDC, amount, 2n, 0, userAddress],
}),
value: "0x0",
};
// Get sessionId from list_wallets
await execute_tx({ sessionId: "<from list_wallets>", to: POOL, data: borrowTx.data, value: "0x0", feeToken: USDC });
Requires: approve(Pool, amount) + repay(asset, amount, 2, onBehalfOf)
Use maxUint256 as amount to repay the full debt:
const repayAmount = maxUint256; // or exact amount in wei
const approveTx = {
to: USDC,
data: encodeFunctionData({
abi: [{ name: "approve", type: "function", inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" }
], outputs: [{ type: "bool" }] }],
functionName: "approve",
args: [POOL, repayAmount],
}),
value: "0x0",
};
const repayTx = {
to: POOL,
data: encodeFunctionData({
abi: [{ name: "repay", type: "function", inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "interestRateMode", type: "uint256" },
{ name: "onBehalfOf", type: "address" }
], outputs: [{ name: "", type: "uint256" }] }],
functionName: "repay",
args: [USDC, repayAmount, 2n, userAddress],
}),
value: "0x0",
};
// Get sessionId from list_wallets
await execute_batch_tx({ sessionId: "<from list_wallets>", calls: [approveTx, repayTx], feeToken: USDC });
No approval needed. Use maxUint256 to withdraw entire balance.
const withdrawTx = {
to: POOL,
data: encodeFunctionData({
abi: [{ name: "withdraw", type: "function", inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "to", type: "address" }
], outputs: [{ name: "", type: "uint256" }] }],
functionName: "withdraw",
args: [USDC, maxUint256, userAddress],
}),
value: "0x0",
};
// Get sessionId from list_wallets
await execute_tx({ sessionId: "<from list_wallets>", to: POOL, data: withdrawTx.data, value: "0x0", feeToken: USDC });
Use UiPoolDataProvider.getReservesData() — a single read call returning all reserves with live APYs, liquidity, and collateral parameters. No wallet or transaction needed.
| Network | UiPoolDataProvider | PoolAddressesProvider |
|---|---|---|
| Ethereum | 0x56b7A1012765C285afAC8b8F25C69Bf10ccfE978 | 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e |
| Arbitrum | 0x145dE30c929a065582da84Cf96F88460dB9C4b9C | 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb |
| Optimism | 0xbd83DdBE37fc91923d59C8c1E0bDe0CccCa332d5 | 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb |
| Base | 0x5d4D4007A4c6336550DdAa2a7c0d5e7972eebd16 | 0xe20fCBdBfFC4Dd138cE8b2E6FBb6CB49777ad64B |
| Polygon | 0xC69728f11E9E6127733751c8410432913123acf1 | 0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb |
Canonical source: Aave Address Book
import { createPublicClient, http, formatUnits } from "viem";
import { arbitrum } from "viem/chains";
const UI_POOL_DATA_PROVIDER = "0x145dE30c929a065582da84Cf96F88460dB9C4b9C";
const POOL_ADDRESSES_PROVIDER = "0xa97684ead0e402dC232d5A977953DF7ECBaB3CDb";
const RAY = 1e27;
const SECONDS_PER_YEAR = 31536000;
const client = createPublicClient({ chain: arbitrum, transport: http() });
const [reserves] = await client.readContract({
address: UI_POOL_DATA_PROVIDER,
abi: [{
name: "getReservesData",
type: "function",
stateMutability: "view",
inputs: [{ name: "provider", type: "address" }],
outputs: [
{
name: "",
type: "tuple[]",
components: [
{ name: "underlyingAsset", type: "address" },
{ name: "name", type: "string" },
{ name: "symbol", type: "string" },
{ name: "decimals", type: "uint256" },
{ name: "baseLTVasCollateral", type: "uint256" },
{ name: "reserveLiquidationThreshold", type: "uint256" },
{ name: "reserveLiquidationBonus", type: "uint256" },
{ name: "reserveFactor", type: "uint256" },
{ name: "usageAsCollateralEnabled", type: "bool" },
{ name: "borrowingEnabled", type: "bool" },
{ name: "isActive", type: "bool" },
{ name: "isFrozen", type: "bool" },
{ name: "liquidityIndex", type: "uint128" },
{ name: "variableBorrowIndex", type: "uint128" },
{ name: "liquidityRate", type: "uint128" }, // supply APY (ray)
{ name: "variableBorrowRate", type: "uint128" }, // borrow APY (ray)
{ name: "availableLiquidity", type: "uint256" },
{ name: "totalScaledVariableDebt", type: "uint256" },
{ name: "priceInMarketReferenceCurrency", type: "uint256" },
{ name: "priceOracle", type: "address" },
{ name: "variableRateSlope1", type: "uint256" },
{ name: "variableRateSlope2", type: "uint256" },
{ name: "baseVariableBorrowRate", type: "uint256" },
{ name: "optimalUsageRatio", type: "uint256" },
{ name: "isPaused", type: "bool" },
{ name: "isSiloedBorrowing", type: "bool" },
{ name: "accruedToTreasury", type: "uint256" },
{ name: "unbacked", type: "uint256" },
{ name: "isolationModeTotalDebt", type: "uint256" },
{ name: "debtCeilingDecimals", type: "uint256" },
{ name: "debtCeiling", type: "uint256" },
{ name: "borrowCap", type: "uint256" },
{ name: "supplyCap", type: "uint256" },
{ name: "eModeCategoryId", type: "uint8" },
{ name: "borrowableInIsolation", type: "bool" },
{ name: "flashLoanEnabled", type: "bool" },
],
},
{ name: "", type: "tuple", components: [] }, // base currency info (ignore)
],
}],
functionName: "getReservesData",
args: [POOL_ADDRESSES_PROVIDER],
});
// Convert ray rate to APY %
const rayToApy = (rate: bigint) => (Number(rate) / RAY) * 100;
// Filter active, non-frozen, and rank by supply APY
const opportunities = reserves
.filter(r => r.isActive && !r.isFrozen && !r.isPaused)
.map(r => ({
symbol: r.symbol,
asset: r.underlyingAsset,
supplyApy: rayToApy(r.liquidityRate).toFixed(2) + "%",
borrowApy: rayToApy(r.variableBorrowRate).toFixed(2) + "%",
availableLiquidity: formatUnits(r.availableLiquidity, Number(r.decimals)),
canBeCollateral: r.usageAsCollateralEnabled,
ltv: (Number(r.baseLTVasCollateral) / 100).toFixed(0) + "%",
liquidationThreshold: (Number(r.reserveLiquidationThreshold) / 100).toFixed(0) + "%",
}))
.sort((a, b) => parseFloat(b.supplyApy) - parseFloat(a.supplyApy));
// Present to user
console.table(opportunities);
After fetching, summarize as a ranked list:
Top Supply Opportunities on Arbitrum:
1. USDC — Supply APY: 8.2% | Borrow APY: 10.1% | Collateral: Yes (LTV 77%)
2. USDT — Supply APY: 7.9% | Borrow APY: 9.8% | Collateral: No
3. WETH — Supply APY: 2.1% | Borrow APY: 2.9% | Collateral: Yes (LTV 80%)
...
Then ask the user which asset they want to supply and how much.
liquidityRate is in ray units (1e27). A simple linear approximation (rate / 1e27 * 100) is accurate enough for display. For compound APY: (1 + rate/RAY/SECONDS_PER_YEAR)^SECONDS_PER_YEAR - 1.
Use getUserAccountData to read position before/after operations:
import { createPublicClient, http } from "viem";
import { arbitrum } from "viem/chains";
const client = createPublicClient({ chain: arbitrum, transport: http() });
const [
totalCollateralBase,
totalDebtBase,
availableBorrowsBase,
currentLiquidationThreshold,
ltv,
healthFactor,
] = await client.readContract({
address: POOL,
abi: [{ name: "getUserAccountData", type: "function", stateMutability: "view",
inputs: [{ name: "user", type: "address" }],
outputs: [
{ name: "totalCollateralBase", type: "uint256" },
{ name: "totalDebtBase", type: "uint256" },
{ name: "availableBorrowsBase", type: "uint256" },
{ name: "currentLiquidationThreshold", type: "uint256" },
{ name: "ltv", type: "uint256" },
{ name: "healthFactor", type: "uint256" },
]
}],
functionName: "getUserAccountData",
args: [userAddress],
});
// healthFactor is in 1e18 units — safe when > 1.5e18, liquidation risk when < 1.1e18
const hfFormatted = Number(healthFactor) / 1e18;
Request permissions that cover the operations you need. Always include the fee token in spend permissions.
// Example: grant permissions for supply + borrow on Arbitrum
await grant_permissions({
chainId: 42161,
description: "Aave V3 operations on Arbitrum",
calls: [
{ to: USDC, signature: "approve(address,uint256)", label: "Approve USDC" },
{ to: POOL, signature: "supply(address,uint256,address,uint16)", label: "Supply to Aave" },
{ to: POOL, signature: "borrow(address,uint256,uint256,uint16,address)", label: "Borrow from Aave" },
{ to: POOL, signature: "repay(address,uint256,uint256,address)", label: "Repay Aave debt" },
{ to: POOL, signature: "withdraw(address,uint256,address)", label: "Withdraw from Aave" },
],
spend: [
{ token: USDC, limit: "1000000000", period: "day", label: "USDC spend" }, // 1000 USDC/day
],
expiry: 86400, // 24 hours (duration in seconds from now)
});
list_wallets (no params) — returns wallet address and all sessions with sessionId, description, chainId, permissionscreate_wallet() — chainId and description are required when granting permissions at creationsupported_gas_tokens({ chainId }) — chainId is required (get it from supported_chains)grant_permissions({ chainId, description, calls, spend, expiry }) — creates a new session. Include Pool calls + token spend limits (include fee token in spend). Suggest a description and confirm with the user before calling.list_wallets to find the right sessionId based on description/permissions for the operationcheck_permissions({ sessionId, to, feeToken }) before sending — data and value are optionalencodeFunctionDataexecute_batch_tx({ sessionId, calls, feeToken }) for approve+action, execute_tx({ sessionId, to, feeToken }) for borrow/withdrawgetUserAccountData on-chainrevoke_session({ sessionId }) — revokes a specific session, others remain active| Mistake | Fix |
|---|---|
Forgetting approve before supply/repay | Always batch approve+action with execute_batch_tx |
Using interest rate mode 1 (stable) | Always use 2 (variable); stable is deprecated |
healthFactor < 1e18 after borrow | Check availableBorrowsBase before borrowing |
| Wrong token decimals | USDC/USDT = 6, WETH/WBTC = 18/8 — use parseUnits |
| Insufficient spend permission | Include both the asset AND the fee token in spend permissions |
Not scoping Pool address in calls | Specify to: POOL to keep permissions minimal |