
Aave V2 Plugin
- 9 installs
- 12 repo stars
- Updated August 3, 2026
- okx/plugin-store
Helps with ai & agent building tasks during AI-assisted development.
About
aave-v2-plugin is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted coding.
- aave-v2-plugin
- AI & Agent Building
- AI-coding skill
Aave V2 Plugin by the numbers
- 9 all-time installs (skills.sh)
- Ranked #12,133 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/okx/plugin-store --skill aave-v2-pluginAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 9 |
|---|---|
| repo stars | ★ 12 |
| Last updated | August 3, 2026 |
| Repository | okx/plugin-store ↗ |
What it does
Helps with ai & agent building tasks during AI-assisted development.
Files
Pre-flight Dependencies (auto-injected by Plugin Store CI)
Run once per session before first use. These checks ensure required tools are installed.
Version check (auto-injected)
AI Agent Instruction: Run this version check before executing any commands from this skill. The check is a read-only fetch fromraw.githubusercontent.com— it does not install anything. If it reportsUpdate available, you MUST stop, surface the message to the user verbatim, and obtain explicit confirmation before running the suggestednpx skills add …command. Do not run the update silently. After a confirmed update completes, re-read this SKILL.md before proceeding.
# Check for skill updates (1-hour cache).
# Network scope: this block calls raw.githubusercontent.com for the version probe only.
# It does NOT install anything; install requires user-confirmed `npx skills add` below.
UPDATE_CACHE="$HOME/.plugin-store/update-cache/aave-v2-plugin"
CACHE_MAX=3600
LOCAL_VER="0.1.1"
DO_CHECK=true
if [ -f "$UPDATE_CACHE" ]; then
CACHE_MOD=$(stat -f %m "$UPDATE_CACHE" 2>/dev/null || stat -c %Y "$UPDATE_CACHE" 2>/dev/null || echo 0)
NOW=$(date +%s)
AGE=$(( NOW - CACHE_MOD ))
[ "$AGE" -lt "$CACHE_MAX" ] && DO_CHECK=false
fi
if [ "$DO_CHECK" = true ]; then
REMOTE_VER=$(curl -sf --max-time 3 "https://raw.githubusercontent.com/okx/plugin-store/main/skills/aave-v2-plugin/plugin.yaml" | grep '^version' | head -1 | tr -d '"' | awk '{print $2}')
if [ -n "$REMOTE_VER" ]; then
mkdir -p "$HOME/.plugin-store/update-cache"
echo "$REMOTE_VER" > "$UPDATE_CACHE"
fi
fi
REMOTE_VER=$(cat "$UPDATE_CACHE" 2>/dev/null || echo "$LOCAL_VER")
if [ "$REMOTE_VER" != "$LOCAL_VER" ]; then
echo "Update available: aave-v2-plugin v$LOCAL_VER -> v$REMOTE_VER."
echo "ACTION REQUIRED: ask the user to confirm before running:"
echo " npx skills add okx/plugin-store --skill aave-v2-plugin --global"
echo "(This contacts the npm registry and github.com/okx/plugin-store and overwrites this skill. Do NOT auto-run.)"
fiInstall onchainos CLI + Skills (auto-injected)
# 1. Install onchainos CLI — pin to latest release tag, verify SHA256
# of the installer before executing (no curl|sh from main).
if ! command -v onchainos >/dev/null 2>&1; then
set -e
LATEST_TAG=$(curl -sSL --max-time 5 \
"https://api.github.com/repos/okx/onchainos-skills/releases/latest" \
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
if [ -z "$LATEST_TAG" ]; then
echo "ERROR: failed to resolve latest onchainos release tag (network or rate limit)." >&2
echo " Manual install: https://github.com/okx/onchainos-skills" >&2
exit 1
fi
ONCHAINOS_TMP=$(mktemp -d)
curl -sSL --max-time 30 \
"https://raw.githubusercontent.com/okx/onchainos-skills/${LATEST_TAG}/install.sh" \
-o "$ONCHAINOS_TMP/install.sh"
curl -sSL --max-time 30 \
"https://github.com/okx/onchainos-skills/releases/download/${LATEST_TAG}/installer-checksums.txt" \
-o "$ONCHAINOS_TMP/installer-checksums.txt"
EXPECTED=$(awk '$2 ~ /install\.sh$/ {print $1; exit}' "$ONCHAINOS_TMP/installer-checksums.txt")
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL=$(sha256sum "$ONCHAINOS_TMP/install.sh" | awk '{print $1}')
else
ACTUAL=$(shasum -a 256 "$ONCHAINOS_TMP/install.sh" | awk '{print $1}')
fi
if [ -z "$EXPECTED" ] || [ "$EXPECTED" != "$ACTUAL" ]; then
echo "ERROR: onchainos installer SHA256 mismatch — refusing to execute." >&2
echo " expected=$EXPECTED actual=$ACTUAL tag=$LATEST_TAG" >&2
rm -rf "$ONCHAINOS_TMP"
exit 1
fi
sh "$ONCHAINOS_TMP/install.sh"
rm -rf "$ONCHAINOS_TMP"
set +e
fi
# 2. Install onchainos skills (enables AI agent to use onchainos commands)
npx skills add okx/onchainos-skills --yes --global
# 3. Install plugin-store skills (enables plugin discovery and management)
npx skills add okx/plugin-store --skill plugin-store --yes --globalInstall aave-v2-plugin binary + launcher (auto-injected)
# Install shared infrastructure (launcher + update checker, only once)
LAUNCHER="$HOME/.plugin-store/launcher.sh"
CHECKER="$HOME/.plugin-store/update-checker.py"
if [ ! -f "$LAUNCHER" ]; then
mkdir -p "$HOME/.plugin-store"
curl -fsSL "https://raw.githubusercontent.com/okx/plugin-store/main/scripts/launcher.sh" -o "$LAUNCHER" 2>/dev/null || true
chmod +x "$LAUNCHER"
fi
if [ ! -f "$CHECKER" ]; then
curl -fsSL "https://raw.githubusercontent.com/okx/plugin-store/main/scripts/update-checker.py" -o "$CHECKER" 2>/dev/null || true
fi
# Clean up old installation
rm -f "$HOME/.local/bin/aave-v2-plugin" "$HOME/.local/bin/.aave-v2-plugin-core" 2>/dev/null
# Download binary
OS=$(uname -s | tr A-Z a-z)
ARCH=$(uname -m)
EXT=""
case "${OS}_${ARCH}" in
darwin_arm64) TARGET="aarch64-apple-darwin" ;;
darwin_x86_64) TARGET="x86_64-apple-darwin" ;;
linux_x86_64) TARGET="x86_64-unknown-linux-musl" ;;
linux_i686) TARGET="i686-unknown-linux-musl" ;;
linux_aarch64) TARGET="aarch64-unknown-linux-musl" ;;
linux_armv7l) TARGET="armv7-unknown-linux-musleabihf" ;;
mingw*_x86_64|msys*_x86_64|cygwin*_x86_64) TARGET="x86_64-pc-windows-msvc"; EXT=".exe" ;;
mingw*_i686|msys*_i686|cygwin*_i686) TARGET="i686-pc-windows-msvc"; EXT=".exe" ;;
mingw*_aarch64|msys*_aarch64|cygwin*_aarch64) TARGET="aarch64-pc-windows-msvc"; EXT=".exe" ;;
esac
mkdir -p ~/.local/bin
# Download binary + checksums to a sandbox, verify SHA256 before installing.
# Fail-closed: any mismatch / missing checksum entry refuses the install.
# Matches the producer-side workflow at
# .github/workflows/plugin-publish.yml which uploads `checksums.txt`
# alongside the 9 platform binaries under each release tag.
BIN_TMP=$(mktemp -d)
RELEASE_BASE="https://github.com/okx/plugin-store/releases/download/plugins/aave-v2-plugin@0.1.1"
curl -fsSL "${RELEASE_BASE}/aave-v2-plugin-${TARGET}${EXT}" -o "$BIN_TMP/aave-v2-plugin${EXT}" || {
echo "ERROR: failed to download aave-v2-plugin-${TARGET}${EXT}" >&2
rm -rf "$BIN_TMP"; exit 1; }
curl -fsSL "${RELEASE_BASE}/checksums.txt" -o "$BIN_TMP/checksums.txt" || {
echo "ERROR: failed to download checksums.txt for aave-v2-plugin@0.1.1" >&2
rm -rf "$BIN_TMP"; exit 1; }
EXPECTED=$(awk -v b="aave-v2-plugin-${TARGET}${EXT}" '$2 == b {print $1; exit}' "$BIN_TMP/checksums.txt")
if command -v sha256sum >/dev/null 2>&1; then
ACTUAL=$(sha256sum "$BIN_TMP/aave-v2-plugin${EXT}" | awk '{print $1}')
else
ACTUAL=$(shasum -a 256 "$BIN_TMP/aave-v2-plugin${EXT}" | awk '{print $1}')
fi
if [ -z "$EXPECTED" ] || [ "$EXPECTED" != "$ACTUAL" ]; then
echo "ERROR: aave-v2-plugin SHA256 mismatch — refusing to install." >&2
echo " expected=$EXPECTED actual=$ACTUAL target=${TARGET}" >&2
rm -rf "$BIN_TMP"; exit 1
fi
mv "$BIN_TMP/aave-v2-plugin${EXT}" ~/.local/bin/.aave-v2-plugin-core${EXT}
chmod +x ~/.local/bin/.aave-v2-plugin-core${EXT}
rm -rf "$BIN_TMP"
# Symlink CLI name to universal launcher
ln -sf "$LAUNCHER" ~/.local/bin/aave-v2-plugin
# Register version
mkdir -p "$HOME/.plugin-store/managed"
echo "0.1.1" > "$HOME/.plugin-store/managed/aave-v2-plugin"---
Aave V2 (Ethereum, Polygon, Avalanche)
## !! V2 IS IN GOVERNANCE-LED WIND-DOWN MODE !!
>
All Aave V2 reserves on Ethereum (37), Polygon (13), and Avalanche (7) have `is_frozen = true` as part of the V3 migration. Newsupplyand newborrowrevert on-chain withVL_RESERVE_FROZEN(error code3).
>
Supply/borrow commands fail at pre-flight with RESERVE_FROZEN error_code (zero gas wasted). The structured error response includes:>
```json
{
"error_code": "RESERVE_FROZEN",
"redirect": {
"alternative_plugin": "aave-v3-plugin",
"install_command": "npx skills add okx/plugin-store --skill aave-v3-plugin --yes --global",
"equivalent_command": "aave-v3-plugin supply --chain ETH --token USDC --amount 100 --confirm"
}
}
```
>
Agents: when you receiveRESERVE_FROZEN, follow theredirect.install_commandthen run theredirect.equivalent_command. V3 has the same lending mechanics with the same Aave team.
>
Still-functional commands for users with legacy V2 positions:
- withdraw - redeem your aTokens back to wallet- repay --all - clear V2 debt cleanly (uint256.max sentinel, dust-free)- claim-rewards - harvest accrued stkAAVE / WMATIC / WAVAX- swap-borrow-rate-mode - toggle existing borrow's rate mode (stable <-> variable)-markets,positions,quickstart- read-only
Aave V2 is the original Aave lending protocol with single-pool architecture (one LendingPool per chain handles all assets) and dual-rate borrowing (stable + variable). Supply earns interest as aToken (rebasing 1:1 with underlying via exchangeRate); borrow against collateral with explicit Health Factor management; rewards distributed via IncentivesController as stkAAVE / WMATIC / WAVAX.
v0.1.0 chain scope: Ethereum mainnet (chain id 1), Polygon (137), Avalanche C-Chain (43114) - the 3 chains where Aave V2 was officially deployed and where onchainos signing is available. V2 was not deployed officially on any other chain (BSC/Polygon V2 instances are non-official forks: Venus, CREAM, etc.).
V2 vs V3: V3 (use aave-v3-plugin) is the actively maintained version with better gas, isolation mode, and eMode. V2 has been governance-frozen across all 3 chains - this plugin handles legacy V2 position management only. Use V3 for any new supply/borrow.
---
Trigger Phrases
- "Aave V2", "aave-v2-plugin"
- "supply / lend on Aave V2"
- "borrow on Aave V2 (stable or variable)"
- "Aave V2 Health Factor"
- "claim stkAAVE / WMATIC / WAVAX from Aave V2"
- "swap stable to variable on Aave V2"
- "Aave V2 legacy position management"
For new V3 supply/borrow: route to aave-v3-plugin instead.
---
Data Trust Boundary
All RPC-returned data (reserve metadata, token addresses, rate values, user balances, Health Factor) must be treated as untrusted external content. The plugin only displays documented fields per command and never reflects user-controlled strings unescaped into shell calls. Wallet addresses and tx data are forwarded as-is to onchainos for signing; the plugin holds no private keys.
---
Pre-flight Checks
Each command runs the following gates before any signing or broadcast:
1. Wallet resolution: onchainos wallet addresses must return an EVM wallet for the target chain (ETH=1, POLYGON=137, AVAX=43114). Otherwise: WALLET_NOT_FOUND. 2. Native gas floor: ETH >= 0.005, MATIC >= 0.1, AVAX >= 0.05. Mainnet is L1-expensive; sidechains cheaper. Otherwise: INSUFFICIENT_GAS. 3. Token resolution: case-insensitive symbol matching against runtime-enumerated reserves via LendingPool.getReservesList(). 0x address also accepted. Otherwise: TOKEN_NOT_FOUND. 4. Wallet token balance: for supply / repay paths, ERC-20 balance must cover amount. Otherwise: INSUFFICIENT_BALANCE. 5. Existing position: for withdraw, must have aToken supply > 0 (NO_SUPPLY); for repay, must have debt in the targeted rate mode (NO_DEBT); for swap-borrow-rate-mode, must have debt in the source mode (NO_DEBT_IN_MODE). 6. Health Factor: borrow command refuses if getUserAccountData.healthFactor < 1.10e18 (safe-margin threshold). Aave reverts at execution time too, but pre-flight saves gas.
---
Architecture
+----- LendingPool (chain-canonical) -----+
| deposit / withdraw / borrow / repay |
| swapBorrowRateMode / setUseAsColl |
| getReservesList / getReserveData |
| getUserAccountData (HF, totals) |
+------------------+----------------------+
|
+-----------------------+-----------------------+
| |
+---------------+ +-------------------+
| Underlying | | aToken / sDebt / |
| ERC-20 tokens | -- transferFrom --> aToken | vDebt tokens |
+---------------+ +-------------------+
|
(balanceOf user)
(totalSupply)
+------------------------+
| IncentivesController | --> claimRewards(assets[], amount, to)
| (stkAAVE / WMATIC / | --> getUserUnclaimedRewards(user)
| WAVAX rewards) |
+------------------------+All read paths source data from LendingPool + ERC-20 calls on aToken/sDebt/vDebt addresses (no AaveProtocolDataProvider dependency, since the canonical V2 mainnet PDP at 0x057835aDc8d6F0b9bA17f5b56C71f7Db84B16B36 has no code on Ethereum). Markets enumerated at runtime via LendingPool.getReservesList(); no hardcoded whitelist.
---
Commands
0. quickstart - First-time onboarding
Scans selected chain (default ETH; pass --chain POLYGON or --chain AVAX) for native gas, account totals (HF, totalCollateralETH, totalDebtETH), all listed reserves at runtime via getReservesList(), accrued rewards, and per-reserve user balances + market rates. Returns structured status enum + ready-to-run next_command.
aave-v2-plugin quickstart # ETH default
aave-v2-plugin quickstart --chain POLYGON
aave-v2-plugin quickstart --address 0xYourStatus enum (priority-ordered, exit-tool-oriented):
status | Meaning | next_command |
|---|---|---|
rpc_degraded | >= 3 reserve scans failed | (none - retry) |
unhealthy_position | HF < 1.05 with active debt - urgent! | positions --chain X |
has_active_borrow | Active borrow position to clear | repay --chain X --token Y --all --rate-mode N --confirm |
has_supply_can_redeem | Existing supply, redeem to exit V2 | withdraw --chain X --token Y --amount all --confirm |
has_rewards_accrued | >= 0.05 stkAAVE/WMATIC/WAVAX claimable | claim-rewards --chain X --confirm |
insufficient_gas | Native < gas floor + no V2 history | (none - top up gas) |
protocol_winddown | No V2 history, supply frozen - redirect to V3 | npx skills add okx/plugin-store --skill aave-v3-plugin --yes --global |
Output: chain, wallet, winddown_warning, v3_redirect (alternative_plugin, install_command, reason), native_balance, account (HF, totalCollateral, totalDebt, availableBorrows), rewards_accrued, status, next_command, tip, reserves[].
---
1. markets - List markets + APYs
Runtime enumerates ALL reserves on selected chain via LendingPool.getReservesList(), parallel-fetches per-asset rates / TVL / configuration / pause flags. No hardcoded whitelist.
aave-v2-plugin markets # ETH, all 37 reserves
aave-v2-plugin markets --chain POLYGON # 13 reserves
aave-v2-plugin markets --chain AVAX --limit 5Output fields per reserve: asset, symbol, decimals, a_token, s_debt_token, v_debt_token, supply_apr_pct, variable_borrow_apr_pct, stable_borrow_apr_pct, available_liquidity, total_stable_debt, total_variable_debt, total_supply_underlying, utilization_pct, liquidity_index_ray, config (decoded from configuration bitmap: ltv_pct, liquidation_threshold_pct, liquidation_bonus_pct, reserve_factor_pct, borrowing_enabled, stable_borrow_rate_enabled, is_active, is_frozen).
Rates: 1e27 ray-scaled annual; APR pct = rate / 1e27 * 100.
---
2. positions - User's open positions
aave-v2-plugin positions
aave-v2-plugin positions --chain POLYGON
aave-v2-plugin positions --address 0x...Output: wallet, account (HF, totalCollateral, totalDebt, availableBorrows, ltv_pct, liquidationThreshold_pct), rewards_accrued, positions[] (per-asset supply + variable_debt + stable_debt with APRs). Only reserves with non-zero user balance are shown.
---
3. supply - !! BLOCKED in v0.1.0 (all reserves frozen); redirects to V3 (requires --confirm)
Pre-flight is_frozen check. All reserves on all 3 chains are governance-frozen in v0.1.0 -> returns RESERVE_FROZEN error before any approve happens (zero gas wasted). Structured redirect field includes install_command + equivalent_command for aave-v3-plugin.
aave-v2-plugin supply --chain ETH --token USDC --amount 100 --confirm
# Returns:
# {
# "ok": false,
# "error_code": "RESERVE_FROZEN",
# "redirect": {
# "install_command": "npx skills add okx/plugin-store --skill aave-v3-plugin --yes --global",
# "equivalent_command": "aave-v3-plugin supply --chain ETH --token USDC --amount 100 --confirm"
# }
# }The full implementation (LendingPool.deposit(asset, amount, onBehalfOf, referralCode) with approve + EVM-014 retry + TX-001 confirm) is preserved for future where governance might unfreeze any reserve, OR for chains where additional V2 deployments come online.
Parameters:
| Flag | Required | Default | Notes |
|---|---|---|---|
--chain | no | ETH | ETH / POLYGON / AVAX |
--token | yes | - | Symbol (USDC / DAI / etc.) or 0x address |
--amount | yes | - | Human-readable underlying amount |
--referral-code | no | 0 | Aave referral code |
--dry-run / --confirm / --approve-timeout-secs | - | - | Standard write flags. Submission only happens with --confirm. |
Flow: 1. Resolve token via runtime reserves enumeration 2. Pre-flight: token balance, native gas 3. Approve LendingPool (max approve, EVM-006 wait_for_tx) 4. Submit LendingPool.deposit(...) via onchainos wallet contract-call --force --gas-limit 350_000 - this step only runs when --confirm is passed; otherwise the command exits in preview/dry-run mode. 5. EVM-014 retry on allowance lag (3 patterns) 6. wait_for_tx confirms status=0x1 (TX-001)
Errors: TOKEN_NOT_FOUND | INVALID_ARGUMENT | WALLET_NOT_FOUND | INSUFFICIENT_BALANCE | INSUFFICIENT_GAS | RPC_ERROR | APPROVE_FAILED | APPROVE_NOT_CONFIRMED | SUPPLY_SUBMIT_FAILED | TX_REVERTED | NATIVE_NOT_SUPPORTED_V01.
---
4. withdraw - Redeem aToken back to wallet (requires --confirm)
Calls LendingPool.withdraw(asset, amount, to). Pass --amount all to redeem entire supply position; the LendingPool caps at user's aToken balance, so uint256.max is safe.
aave-v2-plugin withdraw --chain ETH --token USDC --amount 50 --confirm
aave-v2-plugin withdraw --chain ETH --token DAI --amount all --confirmErrors: NO_SUPPLY | WITHDRAW_SUBMIT_FAILED (typically: withdraw would push HF below 1) | others same as supply.
---
5. borrow - !! BLOCKED in v0.1.0 (all reserves frozen); redirects to V3 (requires --confirm)
Same pre-flight as supply: is_frozen returns RESERVE_FROZEN for all chains in v0.1.0. Also pre-checks is_active, borrowing_enabled, and stable_rate_enabled (when --rate-mode 1).
aave-v2-plugin borrow --chain ETH --token USDT --amount 50 --rate-mode 2 --confirm
# Returns RESERVE_FROZEN with redirect:
# {
# "redirect": {
# "equivalent_command": "aave-v3-plugin borrow --chain ETH --token USDT --amount 50 --rate-mode 2 --confirm",
# "rate_mode_note": "Aave V3 removed stable rate mode entirely"
# }
# }For users with existing V2 collateral (rare since wind-down): the full implementation (LendingPool.borrow(asset, amount, rateMode, referralCode, onBehalfOf) with HF pre-flight + TX-001 confirm) is preserved. HF refuses if pre-flight < 1.10e18 (safe margin).
Errors: TOKEN_NOT_FOUND | INVALID_ARGUMENT | `RESERVE_FROZEN` | RESERVE_INACTIVE | BORROWING_DISABLED | STABLE_RATE_DISABLED | NO_COLLATERAL | NO_BORROW_CAPACITY | UNHEALTHY_HF | BORROW_SUBMIT_FAILED | TX_REVERTED.
---
6. repay - Pay back debt (requires --confirm)
--all: passes uint256.max as amount. Aave V2's Pool.repay() caps to min(amount, currentDebt) at execution time -> exactly zero dust on the targeted rate mode. Same mechanism as Aave V3 / Compound V2 max-sentinel. This is Aave V2's native LEND-001 dust-free guarantee.
--amount X: partial repay; pre-cap at min(user_amount, current_debt) for clearer wallet-balance pre-flight.
--rate-mode is required - specifies which debt to repay (1=stable or 2=variable).
aave-v2-plugin repay --chain ETH --token USDT --amount 25 --rate-mode 2 --confirm
aave-v2-plugin repay --chain ETH --token DAI --all --rate-mode 2 --confirm # exact-zeroOutput: settled_debt, tx_hash, dust_guarantee (exact_zero (uint256.max sentinel) for --all, amount-based for --amount).
Errors: NO_DEBT (no debt in target rate mode) | INSUFFICIENT_BALANCE (wallet < debt + 0.1% buffer) | APPROVE_FAILED | REPAY_SUBMIT_FAILED | TX_REVERTED.
---
7. claim-rewards - Claim accrued rewards (requires --confirm)
Calls IncentivesController.claimRewards(assets[], uint256.max, to). Reward token varies by chain: stkAAVE on Ethereum, WMATIC on Polygon, WAVAX on Avalanche.
aave-v2-plugin claim-rewards --chain ETH --confirmOutput: reward_token, reward_token_balance_before, reward_token_balance_after, claimed, tx_hash. The diff between before/after balance is the actual claimed amount (controller distributes COMP/MATIC/AVAX state at claim time, so stored compAccrued underestimates actual).
Errors: NO_REWARDS_CONTROLLER | INSUFFICIENT_GAS | CLAIM_FAILED | TX_REVERTED.
---
8. swap-borrow-rate-mode - Swap an existing borrow's rate mode (requires --confirm)
V2-only feature; V3 removed stable mode entirely. Useful when stable rate has been rebalanced upwards and variable looks cheaper, or vice versa.
--rate-mode is the user's CURRENT mode (the one being swapped FROM).
# Currently variable -> swap to stable
aave-v2-plugin swap-borrow-rate-mode --chain ETH --token USDT --rate-mode 2 --confirm
# Currently stable -> swap to variable
aave-v2-plugin swap-borrow-rate-mode --chain ETH --token DAI --rate-mode 1 --confirmErrors: NO_DEBT_IN_MODE | SWAP_FAILED (typically: target mode disabled for this reserve - check markets for stable_borrow_rate_enabled).
---
Health Factor Rules
- HF >= 2.0: very safe
- 1.5 <= HF < 2.0: comfortable
- 1.10 <= HF < 1.5: caution; avoid additional borrowing
- 1.0 <= HF < 1.10: dangerous; one price tick away from liquidation
- HF < 1.0: liquidatable; bots will trigger liquidate immediately
The borrow command refuses to submit if pre-flight HF < 1.10. repay always succeeds as long as wallet balance covers the debt - it can only improve HF, never worsen it.
---
Skill Routing
- For active Aave development: `aave-v3-plugin` (V3, current Aave team focus)
- For Compound V3:
compound-v3-plugin - For Compound V2 exit (winddown protocol):
compound-v2-plugin - For Morpho Blue:
morpho-plugin - For Sky/Spark Savings (USDS yield, no borrowing):
spark-savings-plugin - For Dolomite isolated borrow positions on Arbitrum:
dolomite-plugin - For cross-chain bridging:
lifi-plugin
---
Security Notice
Aave V2 is well-audited (Trail of Bits, OpenZeppelin, Consensys Diligence, Certora) but still represents legacy infrastructure:
- Aave team's active development is on V3 - V2 receives only critical bug fixes
- Smart contract risk persists - V2 codebase is mature but still has attack surface
- Liquidation engine is active - HF < 1 triggers bot liquidation with up to 5% bonus to liquidator
- All write ops require explicit --confirm; signing routes through onchainos TEEKey mental model: each chain has ONE LendingPool that handles ALL listed reserves. Your supply position = aToken balance (1:1 with underlying via exchangeRate). Your debt = stableDebtToken + variableDebtToken balances. HF = (totalCollateralETH * weightedLiquidationThreshold) / totalDebtETH. Aave's getUserAccountData centralizes these.
---
Do NOT use for
- New supply / borrow on chains that have V3 deployments (Arbitrum, Optimism, Base, BSC, Fantom, etc.) - use
aave-v3-plugin - BSC/Polygon "V2" forks (Venus, CREAM) - those are unrelated protocols with their own plugins
- Native ETH/MATIC/AVAX direct supply/withdraw/borrow (v0.1.0 ERC-20 only - wrap to W* externally)
- Health Factor recovery via flash-loan refinancing - manual repay/withdraw only
- DeFi-Saver-style auto-repay-on-shortfall - out of scope; use DeFi Saver / Instadapp wrappers
---
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
RPC_ERROR on quickstart | Public RPC rate-limited | Wait 1 min, retry. Public node endpoints (publicnode.com) are throttled per-IP. |
INSUFFICIENT_GAS | Wallet has < 0.005 ETH (or 0.1 MATIC, 0.05 AVAX) | Top up native gas; mainnet ops are L1-expensive |
TOKEN_NOT_FOUND | Symbol mismatch | Run markets --chain X to see exact symbols (USDC vs USDC.e on Avalanche, USDT vs USDT0 on Polygon, etc.) |
NO_COLLATERAL on borrow | No supplied assets enabled as collateral | First supply --token X --amount Y --confirm; Aave auto-enables newly-supplied as collateral |
UNHEALTHY_HF | Pre-flight HF < 1.10 | Repay existing debt before borrowing more |
BORROW_SUBMIT_FAILED at execution | Oracle price moved between preview and submit, OR stable rate disabled for reserve | Check markets for stable_borrow_rate_enabled; use --rate-mode 2 for variable |
repay fails with STABLE_BORROW_RATE_NOT_ENABLED | Aave reverts when target reserve has stable rate disabled | Always use --rate-mode 2 (variable) unless you specifically want stable |
NATIVE_NOT_SUPPORTED_V01 | Tried to use ETH/MATIC/AVAX directly | Wrap first to WETH/WMATIC/WAVAX, then supply the wrapped version |
---
Changelog
v0.1.1 (2026-05-07)
- feat:
wallet contract-callnow passes--biz-type dappand--strategy aave-v2-plugin(onchainos 3.0.0+) so backend attribution dashboards can group calls by source plugin. - fix (EVM-012): 13-place sweep of silent
unwrap_or(0)RPC error swallowers. Before the sweep, public RPC blips were rendered as user-facing "0" values and triggered misleading status decisions. Affected paths: quickstart/positions:LendingPool.getUserAccountDatafailure used to fall back to(0,0,0,0,0,0)→ "totalDebt=0, HF=infinite" — misleading users into thinking accounts were healthy when they could have been liquidatable. Now returns structuredRPC_ERRORJSON via stdout.quickstart/positions: per-reserve balance reads (aToken,stableDebtToken,variableDebtToken) no longer collapse to 0 on RPC failure (which previously hid a reserve via the all-zero filter, or routed users toprotocol_winddowndespite an active V2 position).quickstartpropagates failures asrpc_failuresso the existingrpc_degradedstatus fires;positionssurfaces them in a top-levelpartial_reservesarray.quickstart/positions/claim-rewards:rewards_accruedand reward-token-balance reads now expose*_query_errorfields so callers can distinguish "no rewards accrued" from "incentive controller RPC failed".quickstart: native gas balance read now bails withRPC_ERRORinstead of falling back to 0 (which used to misroute toinsufficient_gas).swap-borrow-rate-mode/withdraw/repay: balance reads (debt token, aToken, wallet underlying) now distinguishRPC_ERRORfrom "no debt / no supply / insufficient balance" — previously a public RPC outage produced misleadingNO_DEBT_IN_MODE/NO_SUPPLY/INSUFFICIENT_BALANCEerrors.supply/repay: pre-flight allowance reads no longer silently force a redundant approve on every RPC blip.markets: per-reserve pool data reads (available_liquidity,total_supply,total_debt) now expose apartial_data_errorsarray so a single failed read doesn't render as an empty market or break the utilization calculation.
v0.1.0 (2026-04-29)
- feat: initial release with 9 commands (
quickstart,markets,positions,supply,withdraw,borrow,repay,claim-rewards,swap-borrow-rate-mode) - feat: 3 chains (Ethereum mainnet, Polygon, Avalanche C-Chain) - covers all official Aave V2 deployments
- feat: runtime market enumeration via
LendingPool.getReservesList()+LendingPool.getReserveData()- no hardcoded whitelist; new reserves automatically supported on listing - feat: configuration bitmap decoder for ltv / liquidation threshold / pause flags / decimals (no PDP dependency)
- feat: dust-free
repay --allvia Aave V2's nativePool.repay(amount=type(uint256).max)sentinel - addresses LEND-001 - feat: stable + variable rate modes (V2 unique; V3 removed stable).
borrowtakes--rate-mode 1|2;swap-borrow-rate-modeflips between modes for an existing borrow. - feat: rewards via IncentivesController.claimRewards (stkAAVE on mainnet, WMATIC on Polygon, WAVAX on Avalanche)
- feat: pre-flight Health Factor gate (refuse borrow if HF < 1.10 even though Aave allows down to 1.0)
- feat: positioned as exit tool with structured V3 redirect. Live reality check: ALL 57 reserves across all 3 chains (37 ETH + 13 Polygon + 7 Avalanche) have
is_frozen=true(governance-led wind-down for V3 migration).supplyandborrowpre-flightis_frozenand returnRESERVE_FROZENwith structuredredirectfield (install_command+equivalent_command) so Agents can auto-route toaave-v3-plugin. Pre-flight saves users wasted approve gas (verified ~$0.36 saved per attempted supply on ETH at current 1.83 gwei). Quickstart returnsprotocol_winddownstatus with V3 install command for users with no V2 history. - architecture: PDP-free design. Canonical Aave V2 mainnet PDP at
0x057835aDc8d6F0b9bA17f5b56C71f7Db84B16B36has no code on Ethereum (Polygon/Avalanche PDPs alive but we use unified path). All read paths source from LendingPool + ERC-20 calls on aToken/sDebt/vDebt addresses. - selectors: keccak256 verified - LendingPool deposit
0xe8eda9df, withdraw0x69328dec, borrow0xa415bcad, repay0x573ade81, swapBorrowRateMode0x94ba89a2, getReservesList0xd1946dbc, getReserveData0x35ea6a75, getUserAccountData0xbf92857c. IncentivesController claimRewards0x3111e7b3. - Verified end-to-end on all 3 chains: read commands return real on-chain APRs (ETH USDC supply 0.43%, variable borrow 10.65%; Polygon USDC borrow 16.82%; Avalanche DAI.e supply 2.22%); supply USDC 0.5 on ETH correctly returned
RESERVE_FROZENafter pre-flight (zero gas) post-fix; error paths (NO_COLLATERAL,NO_DEBT,INSUFFICIENT_BALANCE,INSUFFICIENT_GAS,NATIVE_NOT_SUPPORTED_V01,RESERVE_FROZEN) return structured GEN-001 JSON via stdout
{
"name": "aave-v2-plugin",
"version": "0.1.1",
"description": "Aave V2 exit tool across Ethereum, Polygon, and Avalanche - all reserves frozen by governance. Redeem aTokens, repay legacy debt, claim stkAAVE/WMATIC/WAVAX rewards. Use aave-v3-plugin for active supply/borrow."
}
target/
.ai-review/
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aave-v2-plugin"
version = "0.1.1"
dependencies = [
"anyhow",
"clap",
"futures",
"hex",
"reqwest",
"serde",
"serde_json",
"sha3",
"tokio",
]
[[package]]
name = "anstream"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000"
[[package]]
name = "anstyle-parse"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
]
[[package]]
name = "anyhow"
version = "1.0.102"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
name = "cc"
version = "1.2.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "clap"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "colorchoice"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "encoding_rs"
version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
[[package]]
name = "errno"
version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "fastrand"
version = "2.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
[[package]]
name = "find-msvc-tools"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foldhash"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
dependencies = [
"percent-encoding",
]
[[package]]
name = "futures"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d"
dependencies = [
"futures-channel",
"futures-core",
"futures-executor",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-channel"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d"
dependencies = [
"futures-core",
"futures-sink",
]
[[package]]
name = "futures-core"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
[[package]]
name = "futures-executor"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-io"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718"
[[package]]
name = "futures-macro"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893"
[[package]]
name = "futures-task"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
[[package]]
name = "futures-util"
version = "0.3.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-macro",
"futures-sink",
"futures-task",
"memchr",
"pin-project-lite",
"slab",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "getrandom"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
dependencies = [
"cfg-if",
"libc",
"r-efi",
"wasip2",
"wasip3",
]
[[package]]
name = "h2"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54"
dependencies = [
"atomic-waker",
"bytes",
"fnv",
"futures-core",
"futures-sink",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "hashbrown"
version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
dependencies = [
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "http"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a"
dependencies = [
"bytes",
"itoa",
]
[[package]]
name = "http-body"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
dependencies = [
"bytes",
"http",
]
[[package]]
name = "http-body-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
[[package]]
name = "hyper"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca"
dependencies = [
"atomic-waker",
"bytes",
"futures-channel",
"futures-core",
"h2",
"http",
"http-body",
"httparse",
"itoa",
"pin-project-lite",
"smallvec",
"tokio",
"want",
]
[[package]]
name = "hyper-rustls"
version = "0.27.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f"
dependencies = [
"http",
"hyper",
"hyper-util",
"rustls",
"tokio",
"tokio-rustls",
"tower-service",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
dependencies = [
"base64",
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
"hyper",
"ipnet",
"libc",
"percent-encoding",
"pin-project-lite",
"socket2",
"system-configuration",
"tokio",
"tower-service",
"tracing",
"windows-registry",
]
[[package]]
name = "icu_collections"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c"
dependencies = [
"displaydoc",
"potential_utf",
"utf8_iter",
"yoke",
"zerofrom",
"zerovec",
]
[[package]]
name = "icu_locale_core"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29"
dependencies = [
"displaydoc",
"litemap",
"tinystr",
"writeable",
"zerovec",
]
[[package]]
name = "icu_normalizer"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4"
dependencies = [
"icu_collections",
"icu_normalizer_data",
"icu_properties",
"icu_provider",
"smallvec",
"zerovec",
]
[[package]]
name = "icu_normalizer_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38"
[[package]]
name = "icu_properties"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de"
dependencies = [
"icu_collections",
"icu_locale_core",
"icu_properties_data",
"icu_provider",
"zerotrie",
"zerovec",
]
[[package]]
name = "icu_properties_data"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14"
[[package]]
name = "icu_provider"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421"
dependencies = [
"displaydoc",
"icu_locale_core",
"writeable",
"yoke",
"zerofrom",
"zerotrie",
"zerovec",
]
[[package]]
name = "id-arena"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
[[package]]
name = "idna"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de"
dependencies = [
"idna_adapter",
"smallvec",
"utf8_iter",
]
[[package]]
name = "idna_adapter"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714"
dependencies = [
"icu_normalizer",
"icu_properties",
]
[[package]]
name = "indexmap"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
dependencies = [
"equivalent",
"hashbrown 0.17.0",
"serde",
"serde_core",
]
[[package]]
name = "ipnet"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2"
[[package]]
name = "iri-string"
version = "0.7.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
name = "js-sys"
version = "0.3.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf"
dependencies = [
"cfg-if",
"futures-util",
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "keccak"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653"
dependencies = [
"cpufeatures",
]
[[package]]
name = "leb128fmt"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
[[package]]
name = "litemap"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0"
[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897"
[[package]]
name = "memchr"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mio"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1"
dependencies = [
"libc",
"wasi",
"windows-sys 0.61.2",
]
[[package]]
name = "native-tls"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "openssl"
version = "0.10.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "openssl-probe"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe"
[[package]]
name = "openssl-sys"
version = "0.9.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-link",
]
[[package]]
name = "percent-encoding"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pin-project-lite"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "pkg-config"
version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "potential_utf"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564"
dependencies = [
"zerovec",
]
[[package]]
name = "prettyplease"
version = "0.2.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "r-efi"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "redox_syscall"
version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
]
[[package]]
name = "reqwest"
version = "0.12.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"h2",
"http",
"http-body",
"http-body-util",
"hyper",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"js-sys",
"log",
"mime",
"native-tls",
"percent-encoding",
"pin-project-lite",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"tokio",
"tokio-native-tls",
"tower",
"tower-http",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "ring"
version = "0.17.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [
"cc",
"cfg-if",
"getrandom 0.2.17",
"libc",
"untrusted",
"windows-sys 0.52.0",
]
[[package]]
name = "rustix"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.61.2",
]
[[package]]
name = "rustls"
version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [
"zeroize",
]
[[package]]
name = "rustls-webpki"
version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "ryu"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]]
name = "schannel"
version = "0.1.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [
"bitflags",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "semver"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"indexmap",
"itoa",
"memchr",
"serde",
"serde_core",
"zmij",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sha3"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874"
dependencies = [
"digest",
"keccak",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
dependencies = [
"errno",
"libc",
]
[[package]]
name = "slab"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
version = "1.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
[[package]]
name = "socket2"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e"
dependencies = [
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "stable_deref_trait"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
dependencies = [
"futures-core",
]
[[package]]
name = "synstructure"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "system-configuration"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b"
dependencies = [
"bitflags",
"core-foundation 0.9.4",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "tempfile"
version = "3.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
dependencies = [
"fastrand",
"getrandom 0.4.2",
"once_cell",
"rustix",
"windows-sys 0.61.2",
]
[[package]]
name = "tinystr"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d"
dependencies = [
"displaydoc",
"zerovec",
]
[[package]]
name = "tokio"
version = "1.52.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6"
dependencies = [
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.61.2",
]
[[package]]
name = "tokio-macros"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
]
[[package]]
name = "tower"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
dependencies = [
"futures-core",
"futures-util",
"pin-project-lite",
"sync_wrapper",
"tokio",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-http"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [
"bitflags",
"bytes",
"futures-util",
"http",
"http-body",
"iri-string",
"pin-project-lite",
"tower",
"tower-layer",
"tower-service",
]
[[package]]
name = "tower-layer"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
[[package]]
name = "tower-service"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
[[package]]
name = "tracing"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "typenum"
version = "1.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "url"
version = "2.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
"serde",
]
[[package]]
name = "utf8_iter"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.3+wasi-0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6"
dependencies = [
"wit-bindgen 0.57.1",
]
[[package]]
name = "wasip3"
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
dependencies = [
"wit-bindgen 0.51.0",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.70"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.120"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasm-encoder"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
dependencies = [
"leb128fmt",
"wasmparser",
]
[[package]]
name = "wasm-metadata"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
dependencies = [
"anyhow",
"indexmap",
"wasm-encoder",
"wasmparser",
]
[[package]]
name = "wasmparser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [
"bitflags",
"hashbrown 0.15.5",
"indexmap",
"semver",
]
[[package]]
name = "web-sys"
version = "0.3.97"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-registry"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720"
dependencies = [
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "wit-bindgen"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
dependencies = [
"wit-bindgen-rust-macro",
]
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]]
name = "wit-bindgen-core"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
dependencies = [
"anyhow",
"heck",
"wit-parser",
]
[[package]]
name = "wit-bindgen-rust"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
dependencies = [
"anyhow",
"heck",
"indexmap",
"prettyplease",
"syn",
"wasm-metadata",
"wit-bindgen-core",
"wit-component",
]
[[package]]
name = "wit-bindgen-rust-macro"
version = "0.51.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
dependencies = [
"anyhow",
"prettyplease",
"proc-macro2",
"quote",
"syn",
"wit-bindgen-core",
"wit-bindgen-rust",
]
[[package]]
name = "wit-component"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [
"anyhow",
"bitflags",
"indexmap",
"log",
"serde",
"serde_derive",
"serde_json",
"wasm-encoder",
"wasm-metadata",
"wasmparser",
"wit-parser",
]
[[package]]
name = "wit-parser"
version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
dependencies = [
"anyhow",
"id-arena",
"indexmap",
"log",
"semver",
"serde",
"serde_derive",
"serde_json",
"unicode-xid",
"wasmparser",
]
[[package]]
name = "writeable"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4"
[[package]]
name = "yoke"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca"
dependencies = [
"stable_deref_trait",
"yoke-derive",
"zerofrom",
]
[[package]]
name = "yoke-derive"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zerofrom"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df"
dependencies = [
"zerofrom-derive",
]
[[package]]
name = "zerofrom-derive"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1"
dependencies = [
"proc-macro2",
"quote",
"syn",
"synstructure",
]
[[package]]
name = "zeroize"
version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0"
[[package]]
name = "zerotrie"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf"
dependencies = [
"displaydoc",
"yoke",
"zerofrom",
]
[[package]]
name = "zerovec"
version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239"
dependencies = [
"yoke",
"zerofrom",
"zerovec-derive",
]
[[package]]
name = "zerovec-derive"
version = "0.11.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[package]
name = "aave-v2-plugin"
version = "0.1.1"
edition = "2021"
[[bin]]
name = "aave-v2-plugin"
path = "src/main.rs"
[dependencies]
clap = { version = "4", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
reqwest = { version = "0.12", features = ["json"] }
serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", features = ["preserve_order"] }
anyhow = "1"
futures = "0.3"
[dev-dependencies]
sha3 = "0.10"
hex = "0.4"
MIT License
Copyright (c) 2024 GeoGu360
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
schema_version: 1
name: aave-v2-plugin
version: "0.1.1"
description: "Aave V2 exit tool on Ethereum, Polygon, and Avalanche - all reserves are frozen by governance. Redeem aTokens, repay debt cleanly via uint256.max sentinel, claim stkAAVE/WMATIC/WAVAX rewards. New supply/borrow rejected; use aave-v3-plugin for active flows"
author:
name: GeoGu360
github: GeoGu360
category: dapp
tags:
- aave
- lending
- borrowing
- ethereum
- polygon
- avalanche
- aave-v2
- defi
license: MIT
components:
skill:
dir: .
build:
lang: rust
binary_name: aave-v2-plugin
api_calls:
- "https://ethereum-rpc.publicnode.com"
- "https://polygon-bor-rpc.publicnode.com"
- "https://avalanche-c-chain-rpc.publicnode.com"
use clap::Args;
use serde_json::json;
use crate::config::{parse_chain, supported_chains_help, ChainInfo, RateMode, SUPPORTED_CHAINS};
use crate::onchainos::{extract_tx_hash, resolve_wallet, wallet_contract_call};
use crate::rpc::{
erc20_decimals, erc20_symbol, fmt_1e18, fmt_token_amount, get_reserves_list,
get_user_account_data, human_to_atomic, lp_get_reserve_data, native_balance,
pad_address, pad_u256, selectors, wait_for_tx,
};
/// Borrow underlying token from Aave V2 LendingPool. Requires existing collateral
/// (i.e. previously supplied + enabled-as-collateral assets) such that
/// availableBorrowsETH > requested_amount_in_eth_equivalent.
///
/// `--rate-mode 1` = stable (V2 only; V3 removed); `--rate-mode 2` = variable.
/// Stable rate is fixed at borrow time but can be rebalanced by anyone if it drifts
/// significantly from market. Variable floats with utilization curve.
///
/// All operations require explicit `--confirm`. v0.1.0 ERC-20 only.
#[derive(Args)]
pub struct BorrowArgs {
/// Chain key or id (ETH / POLYGON / AVAX).
#[arg(long, default_value = "ETH")]
pub chain: String,
/// Token to borrow (case-insensitive symbol or 0x address).
#[arg(long)]
pub token: String,
/// Underlying amount to borrow (human-readable).
#[arg(long, allow_hyphen_values = true)]
pub amount: String,
/// Interest rate mode: 1=stable, 2=variable. Default 2 (variable; recommended).
#[arg(long, default_value = "2")]
pub rate_mode: u8,
/// Aave referral code (default 0)
#[arg(long, default_value = "0")]
pub referral_code: u16,
#[arg(long)]
pub dry_run: bool,
#[arg(long)]
pub confirm: bool,
#[arg(long, default_value = "180")]
pub timeout_secs: u64,
}
/// HF threshold below which we refuse to borrow even at preview (1.10x safe margin).
const HF_BORROW_FLOOR: u128 = 1_100_000_000_000_000_000; // 1.10e18
pub async fn run(args: BorrowArgs) -> anyhow::Result<()> {
let chain: &ChainInfo = match parse_chain(&args.chain) {
Some(c) => c,
None => return print_err(
&format!("Unknown --chain '{}'", args.chain),
"INVALID_CHAIN",
&format!("Supported: {}", supported_chains_help()),
),
};
let mode = match RateMode::from_u8(args.rate_mode) {
Some(m) => m,
None => return print_err(
&format!("Invalid --rate-mode '{}': must be 1 (stable) or 2 (variable)", args.rate_mode),
"INVALID_ARGUMENT", "Pass --rate-mode 2 for variable (recommended) or 1 for stable.",
),
};
let upper = args.token.to_uppercase();
if upper == chain.native_symbol {
return print_err(
&format!("Native {} borrow deferred to v0.2.0. Borrow the wrapped W{} instead.",
chain.native_symbol, chain.native_symbol),
"NATIVE_NOT_SUPPORTED_V01",
&format!("Use --token W{} (wrapped).", chain.native_symbol),
);
}
let (asset_addr, symbol, decimals) = if args.token.starts_with("0x") && args.token.len() == 42 {
let dec = erc20_decimals(&args.token, chain.rpc).await
.map_err(|e| anyhow::anyhow!("erc20 decimals: {}", e))?;
let sym = erc20_symbol(&args.token, chain.rpc).await;
(args.token.to_lowercase(), sym, dec)
} else {
match resolve_symbol(&args.token, chain).await {
Some(t) => t,
None => return print_err(
&format!("Token '{}' not found among Aave V2 reserves on {}", args.token, chain.key),
"TOKEN_NOT_FOUND",
"Run `aave-v2-plugin markets --chain X` to see all listed reserves.",
),
}
};
let amount_raw = match human_to_atomic(&args.amount, decimals) {
Ok(v) => v,
Err(e) => return print_err(&format!("Invalid --amount: {}", e),
"INVALID_ARGUMENT", "Pass a positive number, e.g. --amount 100"),
};
let from_addr = match resolve_wallet(chain.id) {
Ok(a) => a,
Err(e) => return print_err(&format!("{:#}", e), "WALLET_NOT_FOUND",
"Run `onchainos wallet addresses`."),
};
// Pre-flight: reserve must not be frozen / borrowing-disabled / stable-rate-disabled.
// Frozen reserves reject borrow on-chain with VL_RESERVE_FROZEN ('3'); pre-checking
// saves wasted gas if borrowingEnabled or stable rate isn't toggled.
let rd = lp_get_reserve_data(chain.lending_pool, &asset_addr, chain.rpc).await
.map_err(|e| anyhow::anyhow!("LendingPool.getReserveData: {}", e))?;
let cfg = rd.decode_config();
if !cfg.is_active {
return print_err(
&format!("Reserve {} on {} is inactive.", symbol, chain.key),
"RESERVE_INACTIVE",
"Try a different chain or use aave-v3-plugin.",
);
}
if cfg.is_frozen {
let v3_install_cmd = "npx skills add okx/plugin-store --skill aave-v3-plugin --yes --global";
let v3_equivalent = format!(
"aave-v3-plugin borrow --chain {} --token {} --amount {} --rate-mode {} --confirm",
chain.key, symbol, args.amount, args.rate_mode
);
let payload = serde_json::json!({
"ok": false,
"error": format!("Reserve {} on {} is frozen by Aave governance - new borrow rejected on-chain (VL_RESERVE_FROZEN, error code 3). All Aave V2 reserves across Ethereum / Polygon / Avalanche are frozen as part of the V3 migration; existing borrows can still be repaid and rate-mode-swapped, but new borrows must use V3 (note: V3 removed stable rate, so --rate-mode 1 maps to variable on V3).",
symbol, chain.key),
"error_code": "RESERVE_FROZEN",
"suggestion": format!("Install aave-v3-plugin and run the equivalent V3 borrow: `{}` then `{}`", v3_install_cmd, v3_equivalent),
"redirect": {
"reason": "Aave V2 wind-down: governance has frozen all reserves on this chain. V3 is the actively maintained version.",
"install_command": v3_install_cmd,
"equivalent_command": v3_equivalent,
"alternative_plugin": "aave-v3-plugin",
"rate_mode_note": "Aave V3 removed stable rate mode entirely; if you wanted stable on V2, V3 only supports variable.",
},
});
println!("{}", serde_json::to_string_pretty(&payload)
.unwrap_or_else(|_| format!(r#"{{"ok":false,"error_code":"RESERVE_FROZEN"}}"#)));
return Ok(());
}
if !cfg.borrowing_enabled {
return print_err(
&format!("Borrowing is disabled for reserve {} on {} (governance toggle).", symbol, chain.key),
"BORROWING_DISABLED",
"This reserve is supply-only on V2. Use aave-v3-plugin or another reserve.",
);
}
if mode == RateMode::Stable && !cfg.stable_rate_enabled {
return print_err(
&format!("Stable rate is disabled for reserve {} on {}.", symbol, chain.key),
"STABLE_RATE_DISABLED",
"Pass --rate-mode 2 (variable) instead.",
);
}
// Pre-flight: account liquidity & HF
let (total_collateral_eth, total_debt_eth, available_borrows_eth, _liq_thresh, _ltv, hf) =
get_user_account_data(chain.lending_pool, &from_addr, chain.rpc).await
.map_err(|e| anyhow::anyhow!("getUserAccountData: {}", e))?;
if total_collateral_eth == 0 {
return print_err(
"No collateral on Aave V2 - cannot borrow. Supply an asset first.",
"NO_COLLATERAL",
"Run `aave-v2-plugin supply --chain X --token Y --amount Z --confirm` to add collateral.",
);
}
if available_borrows_eth == 0 {
return print_err(
&format!("Account is at borrow capacity (totalDebt={} ETH-eq, totalCollateral={} ETH-eq, HF={}).",
fmt_1e18(total_debt_eth), fmt_1e18(total_collateral_eth), fmt_1e18(hf)),
"NO_BORROW_CAPACITY",
"Repay existing debt or add more collateral.",
);
}
// Note: we can't directly translate amount_raw to ETH-equivalent without an oracle call.
// Aave's borrow() will revert at execution if undercollateralized. We just warn at HF level.
if hf > 0 && hf < HF_BORROW_FLOOR && total_debt_eth > 0 {
return print_err(
&format!("Health Factor {:.4} is below safe borrow threshold (1.10). Borrowing more would risk imminent liquidation.",
hf as f64 / 1e18),
"UNHEALTHY_HF",
"Repay existing debt or add more collateral first.",
);
}
// Pre-flight: native gas
let native = native_balance(&from_addr, chain.rpc).await
.map_err(|e| anyhow::anyhow!("RPC: {}", e))?;
if native < chain.gas_floor_wei {
return print_err(
&format!("Native {} insufficient on {}", chain.native_symbol, chain.key),
"INSUFFICIENT_GAS", "Top up native gas.",
);
}
// Build calldata: borrow(asset, amount, rateMode, referralCode, onBehalfOf)
let calldata = format!("{}{}{}{}{}{}",
selectors::BORROW,
pad_address(&asset_addr),
pad_u256(amount_raw),
pad_u256(mode.as_u128()),
pad_u256(args.referral_code as u128),
pad_address(&from_addr),
);
let stage = if args.dry_run { "dry_run" } else if args.confirm { "submit" } else { "preview" };
println!("{}", serde_json::to_string_pretty(&json!({
"ok": true,
"stage": stage,
"submitted": false,
"preview": {
"action": "borrow",
"chain": chain.key,
"from": from_addr,
"asset": asset_addr,
"symbol": symbol,
"amount": fmt_token_amount(amount_raw, decimals),
"amount_raw": amount_raw.to_string(),
"rate_mode": args.rate_mode,
"rate_mode_label": if args.rate_mode == 1 { "stable" } else { "variable" },
"current_health_factor": fmt_1e18(hf),
"available_borrows_eth_1e18": fmt_1e18(available_borrows_eth),
"call_target": chain.lending_pool,
"warning": "Aave checks collateralization at execution. If oracle price drops between preview and submit, borrow may revert. Variable rate (mode 2) is recommended; stable rate (mode 1) can be rebalanced by anyone if it drifts.",
}
}))?);
if args.dry_run { eprintln!("[DRY RUN]"); return Ok(()); }
if !args.confirm { eprintln!("[PREVIEW] Add --confirm to submit."); return Ok(()); }
let result = match wallet_contract_call(chain.id, chain.lending_pool, &calldata, None, Some(450_000), false) {
Ok(r) => r,
Err(e) => return print_err(
&format!("borrow failed: {:#}", e),
"BORROW_SUBMIT_FAILED",
"Common: undercollateralization at execution time, stable rate disabled for this reserve, frozen reserve, gas, RPC.",
),
};
let tx_hash = extract_tx_hash(&result);
match tx_hash.as_ref() {
Some(h) => {
eprintln!("[borrow] Submit tx: {} - waiting...", h);
if let Err(e) = wait_for_tx(h, chain.rpc, args.timeout_secs).await {
return print_err(&format!("Tx {} reverted: {:#}", h, e),
"TX_REVERTED",
"Most common: undercollateralization at execution time. Run `positions` to inspect.");
}
eprintln!("[borrow] On-chain confirmed.");
}
None => return print_err("Borrow broadcast but no tx hash",
"TX_HASH_MISSING", "Check `onchainos wallet history`."),
}
println!("{}", serde_json::to_string_pretty(&json!({
"ok": true,
"action": "borrow",
"chain": chain.key,
"asset": asset_addr,
"symbol": symbol,
"amount": fmt_token_amount(amount_raw, decimals),
"amount_raw": amount_raw.to_string(),
"rate_mode": args.rate_mode,
"tx_hash": tx_hash,
"on_chain_status": "0x1",
"tip": "Run `aave-v2-plugin positions --chain X` to verify Health Factor. Repay with `repay --token X --all --rate-mode N --confirm` (uint256.max sentinel - no dust).",
}))?);
Ok(())
}
async fn resolve_symbol(token: &str, chain: &ChainInfo) -> Option<(String, String, u32)> {
let reserves = get_reserves_list(chain.lending_pool, chain.rpc).await.ok()?;
let upper = token.to_uppercase();
for asset in reserves {
let sym = erc20_symbol(&asset, chain.rpc).await;
if sym.to_uppercase() == upper {
let dec = erc20_decimals(&asset, chain.rpc).await.unwrap_or(18);
return Some((asset, sym, dec));
}
}
None
}
fn print_err(msg: &str, code: &str, suggestion: &str) -> anyhow::Result<()> {
println!("{}", super::error_response(msg, code, suggestion));
Ok(())
}
use clap::Args;
use serde_json::json;
use crate::config::{parse_chain, supported_chains_help, ChainInfo, SUPPORTED_CHAINS};
use crate::onchainos::{extract_tx_hash, resolve_wallet, wallet_contract_call};
use crate::rpc::{
erc20_balance, fmt_token_amount, get_reserves_list, incentives_get_unclaimed_rewards,
incentives_reward_token, lp_get_reserve_data, native_balance, pad_address, pad_u256,
pad_u256_max, selectors, wait_for_tx,
};
/// Claim accrued rewards from Aave V2 IncentivesController.
///
/// Reward token varies by chain:
/// - Ethereum: stkAAVE (Aave staking token; can be unstaked to AAVE after cooldown)
/// - Polygon V2: WMATIC
/// - Avalanche V2: WAVAX
///
/// Implementation: claimRewards(address[] assets, uint256 amount, address to). Pass
/// uint256.max for `amount` to claim everything; the controller caps internally.
/// `assets` parameter is the union of all aTokens + s/v debt tokens for reserves the
/// user has activity in (we just pass all aTokens + s/v debt for ALL reserves on the
/// chain - controller ignores zero-balance ones).
///
/// All operations require explicit `--confirm`.
#[derive(Args)]
pub struct ClaimRewardsArgs {
/// Chain key or id (ETH / POLYGON / AVAX).
#[arg(long, default_value = "ETH")]
pub chain: String,
#[arg(long)]
pub dry_run: bool,
#[arg(long)]
pub confirm: bool,
#[arg(long, default_value = "300")]
pub timeout_secs: u64,
}
pub async fn run(args: ClaimRewardsArgs) -> anyhow::Result<()> {
let chain: &ChainInfo = match parse_chain(&args.chain) {
Some(c) => c,
None => return print_err(
&format!("Unknown --chain '{}'", args.chain),
"INVALID_CHAIN",
&format!("Supported: {}", supported_chains_help()),
),
};
if chain.incentives_controller.is_empty() {
return print_err(
&format!("No active rewards program for Aave V2 on {}", chain.key),
"NO_REWARDS_CONTROLLER",
"This chain has no IncentivesController in v0.1.0 config.",
);
}
let from_addr = match resolve_wallet(chain.id) {
Ok(a) => a,
Err(e) => return print_err(&format!("{:#}", e), "WALLET_NOT_FOUND",
"Run `onchainos wallet addresses`."),
};
// Pre-flight: gas + accrued rewards
let native = native_balance(&from_addr, chain.rpc).await
.map_err(|e| anyhow::anyhow!("RPC: {}", e))?;
if native < chain.gas_floor_wei {
return print_err(
&format!("Native {} insufficient on {}", chain.native_symbol, chain.key),
"INSUFFICIENT_GAS", "Top up native gas.",
);
}
// EVM-012: claim_rewards relies on `unclaimed` to decide whether the
// claim is worth submitting. Silent unwrap_or(0) used to send users a
// "nothing to claim" path even when the incentives controller RPC call
// had failed.
let unclaimed = match incentives_get_unclaimed_rewards(chain.incentives_controller, &from_addr, chain.rpc).await {
Ok(v) => v,
Err(e) => return print_err(
&format!("Failed to read unclaimed rewards from incentives controller on {}: {:#}", chain.key, e),
"RPC_ERROR",
"Public RPC may be limited; retry shortly.",
),
};
// reward_token / its balance are only used for an after-vs-before delta
// display. Keep the soft fallback but expose query failures so callers can
// tell "0 token claimed" from "RPC failed during snapshot".
let (reward_token, reward_token_resolve_error) =
match incentives_reward_token(chain.incentives_controller, chain.rpc).await {
Ok(addr) => (addr, None),
Err(e) => ("0x0000000000000000000000000000000000000000".to_string(), Some(format!("{:#}", e))),
};
let (reward_token_balance_before, reward_balance_before_query_error) =
if reward_token != "0x0000000000000000000000000000000000000000" {
match erc20_balance(&reward_token, &from_addr, chain.rpc).await {
Ok(v) => (v, None),
Err(e) => (0u128, Some(format!("{:#}", e))),
}
} else { (0, None) };
// Build assets[] = aToken + sDebt + vDebt for ALL reserves (controller ignores zero)
eprintln!("[claim-rewards] Enumerating reserves to build assets[] for claimRewards...");
let reserves = match get_reserves_list(chain.lending_pool, chain.rpc).await {
Ok(r) => r,
Err(e) => return print_err(
&format!("getReservesList: {:#}", e), "RPC_ERROR",
"Public RPC may be limited; retry shortly.",
),
};
let token_futs: Vec<_> = reserves.iter().map(|asset| {
let chain = chain.clone();
let asset = asset.clone();
async move { lp_get_reserve_data(chain.lending_pool, &asset, chain.rpc).await }
}).collect();
let token_results = futures::future::join_all(token_futs).await;
let mut assets: Vec<String> = Vec::new();
let zero_addr = "0x0000000000000000000000000000000000000000";
for r in token_results {
if let Ok(rd) = r {
if !rd.a_token.is_empty() && rd.a_token != zero_addr { assets.push(rd.a_token); }
if !rd.stable_debt_token.is_empty() && rd.stable_debt_token != zero_addr { assets.push(rd.stable_debt_token); }
if !rd.variable_debt_token.is_empty() && rd.variable_debt_token != zero_addr { assets.push(rd.variable_debt_token); }
}
}
if assets.is_empty() {
return print_err(
"No claimable assets resolved.", "NO_ASSETS",
"Reserves may have failed to enumerate. Retry.",
);
}
// Build calldata: claimRewards(address[] assets, uint256 amount, address to)
// ABI: selector + offset_to_array(0x60) + amount + to + array_length + addr × N
let mut calldata = String::new();
calldata.push_str(selectors::CLAIM_REWARDS);
calldata.push_str(&pad_u256(0x60)); // offset to assets array
calldata.push_str(&pad_u256_max()); // amount = uint256.max (claim all)
calldata.push_str(&pad_address(&from_addr)); // to
calldata.push_str(&pad_u256(assets.len() as u128)); // array length
for a in &assets {
calldata.push_str(&pad_address(a));
}
let stage = if args.dry_run { "dry_run" } else if args.confirm { "submit" } else { "preview" };
println!("{}", serde_json::to_string_pretty(&json!({
"ok": true,
"stage": stage,
"submitted": false,
"preview": {
"action": "claim_rewards",
"chain": chain.key,
"controller": chain.incentives_controller,
"reward_token": reward_token,
"user": from_addr,
"assets_count": assets.len(),
"unclaimed_pre_distribute": fmt_token_amount(unclaimed, 18),
"unclaimed_pre_distribute_raw": unclaimed.to_string(),
"reward_token_balance_before": fmt_token_amount(reward_token_balance_before, 18),
"amount_to_claim": "uint256.max (claim all)",
"note": "Stored compAccrued underestimates actual claimable; claimRewards triggers distribution settlement first, then transfers full accrued amount.",
}
}))?);
if args.dry_run { eprintln!("[DRY RUN]"); return Ok(()); }
if !args.confirm { eprintln!("[PREVIEW] Add --confirm to submit."); return Ok(()); }
// Gas: per-asset distribution iteration; conservative 200k + 30k * N
let gas_limit = 200_000u64 + (assets.len() as u64) * 30_000;
eprintln!("[claim-rewards] Submitting claimRewards({} assets)...", assets.len());
let result = match wallet_contract_call(chain.id, chain.incentives_controller, &calldata, None, Some(gas_limit), false) {
Ok(r) => r,
Err(e) => return print_err(&format!("claimRewards failed: {:#}", e),
"CLAIM_FAILED", "Common: gas, RPC, controller paused."),
};
let tx_hash = extract_tx_hash(&result);
match tx_hash.as_ref() {
Some(h) => {
eprintln!("[claim-rewards] Submit tx: {} - waiting...", h);
if let Err(e) = wait_for_tx(h, chain.rpc, args.timeout_secs).await {
return print_err(&format!("Tx {} reverted: {:#}", h, e),
"TX_REVERTED", "On-chain revert. Inspect on the block explorer.");
}
eprintln!("[claim-rewards] On-chain confirmed.");
}
None => return print_err("claimRewards broadcast but no tx hash",
"TX_HASH_MISSING", "Check `onchainos wallet history`."),
}
// EVM-012: post-claim balance read — keep the soft fallback (the tx already
// confirmed; we just want to surface the delta) but expose a query error so
// the displayed `claimed` amount can be marked as best-effort when the
// post-claim RPC call fails.
let (reward_token_balance_after, reward_balance_after_query_error) =
if reward_token != "0x0000000000000000000000000000000000000000" {
match erc20_balance(&reward_token, &from_addr, chain.rpc).await {
Ok(v) => (v, None),
Err(e) => (reward_token_balance_before, Some(format!("{:#}", e))),
}
} else { (0, None) };
let claimed = reward_token_balance_after.saturating_sub(reward_token_balance_before);
println!("{}", serde_json::to_string_pretty(&json!({
"ok": true,
"action": "claim_rewards",
"chain": chain.key,
"user": from_addr,
"reward_token": reward_token,
"reward_token_balance_before": fmt_token_amount(reward_token_balance_before, 18),
"reward_token_balance_after": fmt_token_amount(reward_token_balance_after, 18),
"claimed": fmt_token_amount(claimed, 18),
"claimed_raw": claimed.to_string(),
"reward_token_resolve_error": reward_token_resolve_error,
"reward_balance_before_query_error": reward_balance_before_query_error,
"reward_balance_after_query_error": reward_balance_after_query_error,
"tx_hash": tx_hash,
"on_chain_status": "0x1",
"tip": "Reward token now in your wallet. On Ethereum: stkAAVE has a 10-day unstake cooldown to convert to AAVE. On Polygon/Avalanche: rewards are wrapped native (WMATIC/WAVAX) - unwrap to native externally.",
}))?);
Ok(())
}
fn print_err(msg: &str, code: &str, suggestion: &str) -> anyhow::Result<()> {
println!("{}", super::error_response(msg, code, suggestion));
Ok(())
}
use clap::Args;
use serde_json::{json, Value};
use crate::config::{parse_chain, supported_chains_help, ChainInfo, SUPPORTED_CHAINS};
use crate::rpc::{
bps_to_pct, erc20_balance, erc20_symbol, erc20_total_supply, fmt_token_amount,
get_reserves_list, lp_get_reserve_data, ray_to_apr_pct,
};
#[derive(Args)]
pub struct MarketsArgs {
/// Chain key or id (ETH / POLYGON / AVAX). Default: ETH.
#[arg(long, default_value = "ETH")]
pub chain: String,
/// Limit number of reserves shown (default: all). Useful on Ethereum (37 reserves).
#[arg(long)]
pub limit: Option<usize>,
}
pub async fn run(args: MarketsArgs) -> anyhow::Result<()> {
let chain: &ChainInfo = match parse_chain(&args.chain) {
Some(c) => c,
None => return print_err(
&format!("Unknown --chain '{}'", args.chain),
"INVALID_CHAIN",
&format!("Supported: {}", supported_chains_help()),
),
};
let reserves = match get_reserves_list(chain.lending_pool, chain.rpc).await {
Ok(r) => r,
Err(e) => return print_err(
&format!("Failed to enumerate reserves on {}: {:#}", chain.key, e),
"RPC_ERROR",
"Public RPC may be limited; retry shortly.",
),
};
// Per-reserve parallel: LendingPool reserve data + symbol + cash/totals
let futs: Vec<_> = reserves.iter().map(|asset| {
let chain = chain.clone();
let asset = asset.clone();
async move {
let rd = match lp_get_reserve_data(chain.lending_pool, &asset, chain.rpc).await {
Ok(r) => r,
Err(_) => return (asset, "?".to_string(), None),
};
let cfg = rd.decode_config();
let dec = if cfg.decimals > 0 { cfg.decimals } else { 18 };
let symbol_fut = erc20_symbol(&asset, chain.rpc);
let avail_fut = erc20_balance(&asset, &rd.a_token, chain.rpc);
let total_a_fut = erc20_total_supply(&rd.a_token, chain.rpc);
let total_s_fut = erc20_total_supply(&rd.stable_debt_token, chain.rpc);
let total_v_fut = erc20_total_supply(&rd.variable_debt_token, chain.rpc);
let (sym, avail, total_a, total_s, total_v) = tokio::join!(
symbol_fut, avail_fut, total_a_fut, total_s_fut, total_v_fut
);
// EVM-012: track per-balance errors so the output JSON can flag
// partial data instead of silently rendering 0 (which would
// misreport an empty market or break utilization math via /0).
let mut errs: Vec<String> = Vec::new();
let avail = avail.unwrap_or_else(|e| { errs.push(format!("available_liquidity: {:#}", e)); 0 });
let total_a = total_a.unwrap_or_else(|e| { errs.push(format!("total_a_supply: {:#}", e)); 0 });
let total_s = total_s.unwrap_or_else(|e| { errs.push(format!("total_s_debt: {:#}", e)); 0 });
let total_v = total_v.unwrap_or_else(|e| { errs.push(format!("total_v_debt: {:#}", e)); 0 });
(asset, sym, Some((rd, cfg, dec, avail, total_a, total_s, total_v, errs)))
}
}).collect();
let results = futures::future::join_all(futs).await;
let entries: Vec<Value> = results.into_iter().map(|(asset, sym, payload)| {
match payload {
None => json!({"asset": asset, "symbol": sym, "error": "RPC failed (getReserveData)"}),
Some((rd, cfg, dec, avail, total_a, total_s, total_v, errs)) => {
let total_debt = total_s + total_v;
let utilization_pct = if total_a > 0 {
Some(format!("{:.2}", (total_debt as f64 / total_a as f64) * 100.0))
} else { None };
json!({
"asset": asset,
"symbol": sym,
"decimals": dec,
"a_token": rd.a_token,
"s_debt_token": rd.stable_debt_token,
"v_debt_token": rd.variable_debt_token,
"supply_apr_pct": format!("{:.4}", ray_to_apr_pct(rd.current_liquidity_rate_ray)),
"variable_borrow_apr_pct": format!("{:.4}", ray_to_apr_pct(rd.current_variable_borrow_rate_ray)),
"stable_borrow_apr_pct": format!("{:.4}", ray_to_apr_pct(rd.current_stable_borrow_rate_ray)),
"available_liquidity": fmt_token_amount(avail, dec),
"available_liquidity_raw": avail.to_string(),
"total_stable_debt": fmt_token_amount(total_s, dec),
"total_variable_debt": fmt_token_amount(total_v, dec),
"total_supply_underlying": fmt_token_amount(total_a, dec),
"utilization_pct": utilization_pct,
"liquidity_index_ray": rd.liquidity_index_ray.to_string(),
"config": {
"ltv_pct": bps_to_pct(cfg.ltv_bps as u128),
"liquidation_threshold_pct": bps_to_pct(cfg.liq_threshold_bps as u128),
"liquidation_bonus_pct": bps_to_pct(cfg.liq_bonus_bps as u128),
"reserve_factor_pct": bps_to_pct(cfg.reserve_factor_bps as u128),
"borrowing_enabled": cfg.borrowing_enabled,
"stable_borrow_rate_enabled": cfg.stable_rate_enabled,
"is_active": cfg.is_active,
"is_frozen": cfg.is_frozen,
},
"partial_data_errors": if errs.is_empty() { None } else { Some(errs) },
})
}
}
}).collect();
let limit = args.limit.unwrap_or(entries.len()).min(entries.len());
let displayed = &entries[..limit];
println!("{}", serde_json::to_string_pretty(&json!({
"ok": true,
"chain": chain.key,
"chain_id": chain.id,
"lending_pool": chain.lending_pool,
"weth_gateway": chain.weth_gateway,
"incentives_controller": chain.incentives_controller,
"reserves_total": entries.len(),
"reserves_displayed": displayed.len(),
"reserves": displayed,
"note": "Reserves enumerated at runtime via LendingPool.getReservesList(); rates/config decoded from LendingPool.getReserveData (the canonical Aave V2 PDP at 0x057835...3B16B36 has no code on Ethereum mainnet, so we source data from LendingPool directly + ERC-20 calls on aToken/sDebt/vDebt). Rates are 1e27 ray-scaled annual; config fields in basis points (10000 = 100%).",
}))?);
Ok(())
}
fn print_err(msg: &str, code: &str, suggestion: &str) -> anyhow::Result<()> {
println!("{}", super::error_response(msg, code, suggestion));
Ok(())
}
pub mod borrow;
pub mod claim_rewards;
pub mod markets;
pub mod positions;
pub mod quickstart;
pub mod repay;
pub mod supply;
pub mod swap_borrow_rate_mode;
pub mod withdraw;
/// Standardized GEN-001 error JSON. Always print to stdout; never stderr.
pub fn error_response(msg: &str, code: &str, suggestion: &str) -> String {
serde_json::to_string_pretty(&serde_json::json!({
"ok": false,
"error": msg,
"error_code": code,
"suggestion": suggestion,
}))
.unwrap_or_else(|_| format!(r#"{{"ok":false,"error":{:?},"error_code":{:?}}}"#, msg, code))
}
use clap::Args;
use serde_json::{json, Value};
use crate::config::{parse_chain, supported_chains_help, ChainInfo, SUPPORTED_CHAINS};
use crate::onchainos::resolve_wallet;
use crate::rpc::{
erc20_balance, erc20_symbol, fmt_1e18, fmt_token_amount, get_reserves_list,
get_user_account_data, incentives_get_unclaimed_rewards, lp_get_reserve_data,
ray_to_apr_pct,
};
#[derive(Args)]
pub struct PositionsArgs {
/// Chain key or id (ETH / POLYGON / AVAX). Default: ETH.
#[arg(long, default_value = "ETH")]
pub chain: String,
/// Wallet address (default: onchainos wallet for the chosen chain).
#[arg(long)]
pub address: Option<String>,
}
pub async fn run(args: PositionsArgs) -> anyhow::Result<()> {
let chain: &ChainInfo = match parse_chain(&args.chain) {
Some(c) => c,
None => return print_err(
&format!("Unknown --chain '{}'", args.chain),
"INVALID_CHAIN",
&format!("Supported: {}", supported_chains_help()),
),
};
let wallet = match args.address {
Some(a) => a,
None => match resolve_wallet(chain.id) {
Ok(a) => a,
Err(e) => return print_err(&format!("{:#}", e), "WALLET_NOT_FOUND",
"Run `onchainos wallet addresses` to verify login or pass --address."),
},
};
let acct_fut = get_user_account_data(chain.lending_pool, &wallet, chain.rpc);
let reserves_fut = get_reserves_list(chain.lending_pool, chain.rpc);
let rewards_fut = async {
if chain.incentives_controller.is_empty() { Ok(0u128) }
else { incentives_get_unclaimed_rewards(chain.incentives_controller, &wallet, chain.rpc).await }
};
let (acct_res, reserves_res, rewards_res) = tokio::join!(acct_fut, reserves_fut, rewards_fut);
// EVM-012: account data is the canonical health snapshot — RPC failure must
// surface as an error, not a silent (0,0,0,0,0,0) fallback (which would render
// as "totalDebt=0, HF=infinite" and mislead users into thinking they're safe
// when their account may actually be liquidatable).
let (total_collateral_eth, total_debt_eth, available_borrows_eth, liq_threshold, ltv, hf) =
match acct_res {
Ok(t) => t,
Err(e) => return print_err(
&format!("Failed to read account data from LendingPool on {}: {:#}", chain.key, e),
"RPC_ERROR",
"Public RPC may be limited; retry shortly. Account health cannot be reported \
without this read.",
),
};
// Rewards are non-critical (nice-to-have). Keep the 0 fallback but surface a
// structured error indicator so callers can distinguish "no rewards accrued"
// from "RPC failed". (EVM-012)
let (rewards_accrued, rewards_query_error) = match rewards_res {
Ok(v) => (v, None),
Err(e) => (0u128, Some(format!("{:#}", e))),
};
let reserves: Vec<String> = match reserves_res {
Ok(r) => r,
Err(e) => return print_err(
&format!("Failed to enumerate reserves on {}: {:#}", chain.key, e),
"RPC_ERROR",
"Public RPC may be limited; retry shortly.",
),
};
// For each reserve: lp_get_reserve_data → get aToken/sDebt/vDebt → balanceOf user on each.
// EVM-012: any per-balance RPC failure is now reported instead of silently
// collapsing the reserve to (0,0,0) — which would either hide a real position
// (when L91 filtered all-zero rows) or report "0 supply" to a user who actually
// has a position. Reserves with at least one failed balance read are surfaced
// in the `partial_reserves` array of the output JSON.
let futs: Vec<_> = reserves.iter().map(|asset| {
let chain = chain.clone();
let wallet = wallet.clone();
let asset = asset.clone();
async move {
let rd = match lp_get_reserve_data(chain.lending_pool, &asset, chain.rpc).await {
Ok(r) => r,
Err(e) => return Err((asset, format!("getReserveData: {:#}", e))),
};
let cfg = rd.decode_config();
let dec = if cfg.decimals > 0 { cfg.decimals } else { 18 };
let symbol_fut = erc20_symbol(&asset, chain.rpc);
let supply_fut = erc20_balance(&rd.a_token, &wallet, chain.rpc);
let v_debt_fut = erc20_balance(&rd.variable_debt_token, &wallet, chain.rpc);
let s_debt_fut = erc20_balance(&rd.stable_debt_token, &wallet, chain.rpc);
let (sym, supply, v_debt, s_debt) = tokio::join!(
symbol_fut, supply_fut, v_debt_fut, s_debt_fut
);
Ok((asset, sym, dec, rd, supply, v_debt, s_debt))
}
}).collect();
let results = futures::future::join_all(futs).await;
let mut positions: Vec<Value> = Vec::new();
let mut partial_reserves: Vec<Value> = Vec::new();
for r in results.into_iter() {
let (asset, sym, dec, rd, supply, v_debt, s_debt) = match r {
Ok(t) => t,
Err((asset, err)) => {
partial_reserves.push(json!({ "asset": asset, "error": err }));
continue;
}
};
// Track per-balance RPC errors so we don't silently zero them out.
let mut errs: Vec<String> = Vec::new();
let supply_raw = supply.unwrap_or_else(|e| { errs.push(format!("supply: {:#}", e)); 0 });
let v_debt_raw = v_debt.unwrap_or_else(|e| { errs.push(format!("variable_debt: {:#}", e)); 0 });
let s_debt_raw = s_debt.unwrap_or_else(|e| { errs.push(format!("stable_debt: {:#}", e)); 0 });
if !errs.is_empty() {
partial_reserves.push(json!({
"asset": asset,
"symbol": sym.clone(),
"errors": errs,
}));
// Don't show a reserve we couldn't fully read — partial data is worse
// than absent data when the user is making position-management decisions.
continue;
}
if supply_raw == 0 && v_debt_raw == 0 && s_debt_raw == 0 { continue; }
positions.push(json!({
"asset": asset,
"symbol": sym,
"decimals": dec,
"supply": fmt_token_amount(supply_raw, dec),
"supply_raw": supply_raw.to_string(),
"supply_apr_pct": format!("{:.4}", ray_to_apr_pct(rd.current_liquidity_rate_ray)),
"variable_debt": fmt_token_amount(v_debt_raw, dec),
"variable_debt_raw": v_debt_raw.to_string(),
"variable_apr_pct": format!("{:.4}", ray_to_apr_pct(rd.current_variable_borrow_rate_ray)),
"stable_debt": fmt_token_amount(s_debt_raw, dec),
"stable_debt_raw": s_debt_raw.to_string(),
"stable_apr_pct": format!("{:.4}", ray_to_apr_pct(rd.current_stable_borrow_rate_ray)),
"a_token": rd.a_token,
}));
}
let hf_display = if hf == u128::MAX || total_debt_eth == 0 {
"infinite (no debt)".to_string()
} else {
fmt_1e18(hf)
};
println!("{}", serde_json::to_string_pretty(&json!({
"ok": true,
"chain": chain.key,
"chain_id": chain.id,
"wallet": wallet,
"lending_pool": chain.lending_pool,
"account": {
"total_collateral_eth_1e18": fmt_1e18(total_collateral_eth),
"total_debt_eth_1e18": fmt_1e18(total_debt_eth),
"available_borrows_eth_1e18": fmt_1e18(available_borrows_eth),
"current_liquidation_threshold_pct": format!("{:.2}", liq_threshold as f64 / 100.0),
"ltv_pct": format!("{:.2}", ltv as f64 / 100.0),
"health_factor_1e18": hf_display,
"health_factor_raw": hf.to_string(),
"note": "ETH-equivalent values 1e18-scaled. On Polygon/Avalanche the base unit is USD (oracle-priced), labeled '_eth' for V2 ABI consistency. HF >= 1.0 healthy; < 1.0 liquidatable. ltv & liquidation_threshold are basis points / 100 = pct.",
},
"rewards_accrued": fmt_token_amount(rewards_accrued, 18),
"rewards_accrued_raw": rewards_accrued.to_string(),
"rewards_query_error": rewards_query_error,
"rewards_token_note": "stkAAVE on Ethereum / WMATIC on Polygon / WAVAX on Avalanche. Run claim-rewards to harvest.",
"position_count": positions.len(),
"positions": positions,
"partial_reserves": partial_reserves,
}))?);
Ok(())
}
fn print_err(msg: &str, code: &str, suggestion: &str) -> anyhow::Result<()> {
println!("{}", super::error_response(msg, code, suggestion));
Ok(())
}
/// Static config for aave-v2-plugin: chain whitelist + Aave V2 contract addresses.
///
/// v0.1.0 scope: Aave V2 was deployed on Ethereum mainnet, Polygon, and Avalanche.
/// All 3 chains have official LendingPool + LendingPoolAddressesProvider +
/// AaveProtocolDataProvider deployments. All 3 are supported by onchainos.
///
/// V2 vs V3:
/// - V3 is the actively maintained Compound (use aave-v3-plugin for new positions)
/// - V2 is supply/borrow-active (NOT paused, unlike Compound V2). Continues to serve
/// legacy positions and protocols still integrated with V2 (Maker DSS, Yearn V2,
/// etc.). Smaller but stable TVL.
///
/// Markets are NOT hardcoded — enumerated at runtime via LendingPool.getReservesList().
/// Per-asset metadata (symbol, decimals, aToken/sDebt/vDebt addresses) fetched on demand
/// via AaveProtocolDataProvider.
#[derive(Debug, Clone)]
pub struct ChainInfo {
pub id: u64,
pub key: &'static str,
pub name: &'static str,
pub rpc: &'static str,
pub native_symbol: &'static str,
/// Native gas floor for write operations (in wei). Mainnet is L1-expensive,
/// L2/sidechains cheaper.
pub gas_floor_wei: u128,
/// LendingPoolAddressesProvider — root config; resolves current LendingPool /
/// PriceOracle / etc. via getter functions. Use this if LendingPool ever migrates.
pub addresses_provider: &'static str,
/// LendingPool — main user-facing entry point for deposit / withdraw / borrow / repay.
pub lending_pool: &'static str,
/// AaveProtocolDataProvider - DEPRECATED in v0.1.0 (Ethereum mainnet PDP at the
/// canonical address has no code; we source all data from LendingPool directly +
/// ERC-20 calls on aToken/sDebt/vDebt addresses). Field kept for v0.2.0 if we
/// ever route to PDP for chains where it's healthy (Polygon/Avalanche).
pub data_provider: &'static str,
/// WETHGateway — required for native ETH/MATIC/AVAX deposit/withdraw/borrow/repay.
/// LendingPool itself does NOT accept native; the gateway wraps to WETH first.
pub weth_gateway: &'static str,
/// IncentivesController — distributes stkAAVE / WMATIC / WAVAX rewards depending
/// on chain. Empty string if rewards not active for this chain in V2.
pub incentives_controller: &'static str,
}
/// 3 chains: Ethereum mainnet, Polygon (PoS), Avalanche C-Chain.
/// Addresses verified on-chain via AddressesProvider.getLendingPool() etc.
pub const SUPPORTED_CHAINS: &[ChainInfo] = &[
ChainInfo {
id: 1,
key: "ETH",
name: "Ethereum",
rpc: "https://ethereum-rpc.publicnode.com",
native_symbol: "ETH",
gas_floor_wei: 5_000_000_000_000_000, // 0.005 ETH (~$15) — L1 mainnet expensive
addresses_provider: "0xb53c1a33016b2dc6ff3153d380e0789baeb13fe1",
lending_pool: "0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9",
data_provider: "0x057835aDc8d6F0b9bA17f5b56C71f7Db84B16B36",
weth_gateway: "0xcc9a0B7c43DC2a5F023Bb9b738E45B0Ef6B06E04",
incentives_controller: "0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5", // stkAAVE on mainnet
},
ChainInfo {
id: 137,
key: "POLYGON",
name: "Polygon",
rpc: "https://polygon-bor-rpc.publicnode.com",
native_symbol: "MATIC",
gas_floor_wei: 100_000_000_000_000_000, // 0.1 MATIC — Polygon cheap
addresses_provider: "0xd05e3E715d945B59290df0ae8eF85c1BdB684744",
lending_pool: "0x8dFf5E27EA6b7AC08EbFdf9eB090F32ee9a30fcf",
data_provider: "0x7551b5D2763519d4e37e8B81929D336De671d46d",
weth_gateway: "0xbEadf48d62aCC944a06EEaE0A9054A90E5A7dc97",
incentives_controller: "0x357D51124f59836DeD84c8a1730D72B749d8BC23", // WMATIC rewards
},
ChainInfo {
id: 43114,
key: "AVAX",
name: "Avalanche",
rpc: "https://avalanche-c-chain-rpc.publicnode.com",
native_symbol: "AVAX",
gas_floor_wei: 50_000_000_000_000_000, // 0.05 AVAX
addresses_provider: "0xb6A86025F0FE1862B372cb0ca18CE3EDe02A318f",
lending_pool: "0x4F01AeD16D97E3aB5ab2B501154DC9bb0F1A5A2C",
data_provider: "0x65285E9dfab318f57051ab2b139ccCf232945451",
weth_gateway: "0x8a47F74d1eE0e2edEB4F3A7e64EF3bD8e11D27C8",
incentives_controller: "0x01D83Fe6A10D2f2B7AF17034343746188272cAc9", // WAVAX rewards
},
];
pub fn chain_by_id(id: u64) -> Option<&'static ChainInfo> {
SUPPORTED_CHAINS.iter().find(|c| c.id == id)
}
pub fn chain_by_key(key: &str) -> Option<&'static ChainInfo> {
let upper = key.to_uppercase();
let canon: &str = match upper.as_str() {
"ETH" | "ETHEREUM" | "MAINNET" | "1" => "ETH",
"POLYGON" | "MATIC" | "POL" | "137" => "POLYGON",
"AVAX" | "AVALANCHE" | "C-CHAIN" | "43114" => "AVAX",
other => other,
};
SUPPORTED_CHAINS.iter().find(|c| c.key == canon)
}
pub fn parse_chain(s: &str) -> Option<&'static ChainInfo> {
if let Ok(id) = s.parse::<u64>() {
return chain_by_id(id);
}
chain_by_key(s)
}
pub fn supported_chains_help() -> String {
SUPPORTED_CHAINS
.iter()
.map(|c| format!("{} (id={}, native={})", c.key, c.id, c.native_symbol))
.collect::<Vec<_>>()
.join(", ")
}
/// Aave V2 InterestRateMode enum (matches Pool.borrow / Pool.repay rate_mode arg).
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RateMode {
/// Stable rate borrow — fixed rate that can be rebalanced by anyone if it drifts.
/// V2 has stable rate; V3 removed stable mode entirely.
Stable = 1,
/// Variable rate borrow — floats with utilization curve.
Variable = 2,
}
impl RateMode {
pub fn from_u8(v: u8) -> Option<Self> {
match v {
1 => Some(Self::Stable),
2 => Some(Self::Variable),
_ => None,
}
}
pub fn as_u128(self) -> u128 {
self as u128
}
}