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.
How 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`.
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 |
npx claudepluginhub bootnodedev/agentic-defi --plugin agentic-defiCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.