Skill: aaronjmars/agent-credit (version 1.0.0)

--- name: agent-credit description: Borrow from Aave via credit delegation. Agent self-funds by borrowing against delegator collateral. Supports borrow, repay, health checks. Works on Aave V2/V3. --- # Aave Credit Delegation Borrow funds from Aave using delegated credit. Your main wallet supplies collateral and delegates borrowing power to the agent's wallet. The agent can then autonomously borrow tokens when needed — the debt accrues against the delegator's position. > **Protocol:** Works on **Aave V3** and **Aave V2** — the function signatures for credit delegation (`borrow`, `repay`, `approveDelegation`, `borrowAllowance`) are identical across both versions. Just swap in the V2 LendingPool and ProtocolDataProvider addresses. The only cosmetic difference: V3 returns collateral/debt in USD (8 decimals), V2 in ETH (18 decimals). The health factor safety check works correctly on both. ## Compatible With - **[OpenClaw](https://openclaw.ai/)** — Install as a skill, the agent borrows autonomously - **[Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code)** — Run scripts directly from a Claude Code session - **Any agent framework** — Plain bash + Foundry's `cast`, works anywhere with a shell Combines with **[Bankr](https://bankr.bot/)** skills for borrow-then-swap flows: borrow USDC via delegation, then use Bankr to swap, bridge, or deploy it. ## How Credit Delegation Works Credit delegation in Aave V3 separates two things: **borrowing power** and **delegation approval**. **Borrowing power is holistic.** It comes from your entire collateral position across all assets. If you deposit $10k worth of ETH at 80% LTV, you have $8k of borrowing power — period. That borrowing power isn't locked to any specific asset. **Delegation approval is isolated per debt token.** You control *which* assets the agent can borrow and *how much* of each by calling `approveDelegation()` on individual VariableDebtTokens. Each asset has its own debt token contract, and each approval is independent. This means you can, for example: - Deposit ETH as collateral (gives you broad borrowing power) - Approve the agent to borrow up to 500 USDC (via the USDC VariableDebtToken) - Approve the agent to borrow up to 0.1 WETH (via the WETH VariableDebtToken) - Leave cbETH unapproved (agent cannot borrow it at all) The agent can only borrow assets you've explicitly approved, up to the amounts you've set — but the *capacity* to borrow comes from your total collateral, not from any single deposit. ``` Your Collateral (holistic) Delegation Approvals (isolated) ┌─────────────────────────┐ ┌──────────────────────────────┐ │ $5k ETH │ │ USDC DebtToken → agent: 500 │ │ $3k USDC │ ───LTV───▶ │ WETH DebtToken → agent: 0.1 │ │ $2k cbETH │ = $8k │ cbETH DebtToken → agent: 0 │ │ Total: $10k @ 80% LTV │ capacity └──────────────────────────────┘ └─────────────────────────┘ ``` ## Flow ``` Delegator (your wallet) Agent Wallet (delegatee) │ │ │ 1. supply collateral to Aave │ │ 2. approveDelegation(agent, amount) │ │ on the VariableDebtToken │ │ │ │ ┌─── 3. borrow(asset, │ │ │ amount, onBehalfOf │ │ │ = delegator) │ │ │ │ │ [debt on YOUR position] [tokens in agent wallet] │ │ │ │ └─── 4. repay(asset, │ │ amount, onBehalfOf │ │ = delegator) │ ``` ## Quick Start ### Prerequisites 1. **Foundry** must be installed (`cast` CLI): ```bash curl -L https://foundry.paradigm.xyz | bash && foundryup ``` 2. **Delegator setup** (done ONCE by the user, NOT the agent): - Supply collateral to Aave V3 (via app.aave.com or contract) - Call `approveDelegation(agentAddress, maxAmount)` on the **VariableDebtToken** of the asset you want the agent to borrow - The VariableDebtToken address can be found via: `cast call $DATA_PROVIDER "getReserveTokensAddresses(address)(address,address,address)" $ASSET --rpc-url $RPC` 3. **Configure the skill**: ```bash mkdir -p ~/.openclaw/skills/aave-delegation cat > ~/.openclaw/skills/aave-delegation/config.json << 'EOF' { "chain": "base", "rpcUrl": "https://mainnet.base.org", "agentPrivateKey": "0xYOUR_AGENT_PRIVATE_KEY", "delegatorAddress": "0xYOUR_MAIN_WALLET", "poolAddress": [REDACTED_SECRET], "dataProviderAddress": [REDACTED_SECRET], "assets": { "USDC": { "address": [REDACTED_SECRET], "decimals": 6 }, "WETH": { "address": [REDACTED_SECRET], "decimals": 18 } }, "safety": { "minHealthFactor": "1.5", "maxBorrowPerTx": "1000", "maxBorrowPerTxUnit": "USDC" } } EOF ``` 4. **Verify setup**: ```bash scripts/aave-setup.sh ``` ## Core Usage ### Check Status (allowance, health, debt) ```bash # Full status report scripts/aave-status.sh # Check specific asset delegation scripts/aave-status.sh USDC # Just health factor scripts/aave-status.sh --health-only ``` ### Borrow via Delegation ```bash # Borrow 100 USDC scripts/aave-borrow.sh USDC 100 # Borrow 0.5 WETH scripts/aave-borrow.sh WETH 0.5 ``` The borrow script automatically: 1. Checks delegation allowance (sufficient?) 2. Checks delegator health factor (safe to borrow?) 3. Executes the borrow 4. Reports the result ### Repay Debt ```bash # Repay 100 USDC scripts/aave-repay.sh USDC 100 # Repay all USDC debt scripts/aave-repay.sh USDC max ``` The repay script automatically: 1. Approves the Pool to spend the token (if needed) 2. Executes the repay 3. Reports remaining debt ## Safety System **Every borrow operation runs these checks BEFORE executing:** 1. **Delegation allowance** — Is the remaining allowance >= requested amount? 2. **Health factor** — Is the delegator's health factor > `minHealthFactor` (default 1.5) AFTER this borrow? 3. **Per-tx cap** — Is the amount <= `maxBorrowPerTx`? 4. **Confirmation** — Logs the full operation details before sending If ANY check fails, the borrow is **aborted** with a clear error message. ⚠️ **The agent must NEVER bypass safety checks.** If the user asks the agent to borrow and the health factor is too low, the agent should refuse and explain why. ## Capabilities ### Read Operations (no gas needed) - **Check delegation allowance** — How much can the agent still borrow? - **Check health factor** — Is the delegator's position safe? - **Check outstanding debt** — How much does the delegator owe on each asset? - **Check available liquidity** — Is there enough in the Aave pool to borrow? - **Resolve debt token addresses** — Look up VariableDebtToken for any asset ### Write Operations (needs gas in agent wallet) - **Borrow** — Draw funds from Aave against delegated credit - **Repay** — Return borrowed funds to reduce delegator's debt - **Approve** — Approve Pool to spend tokens for repayment ## Supported Chains | Chain | Pool Address | Gas Cost | |-----------|----------------------------------------------|-----------| | Base | [REDACTED_SECRET] | Very Low | | Ethereum | [REDACTED_SECRET] | High | | Polygon | [REDACTED_SECRET] | Very Low | | Arbitrum | [REDACTED_SECRET] | Low | See [deployments.md](deployments.md) for full address list including debt tokens. ## Common Patterns ### Agent Self-Funding for Gas ```bash # Check if we have enough gas BALANCE=$(cast balance $AGENT_ADDRESS --rpc-url $RPC) if [ "$BALANCE" -lt "1000000000000000" ]; then # < 0.001 ETH # Borrow a small amount of WETH for gas aave-borrow.sh WETH 0.005 fi ``` ### Borrow + Swap via Bankr ```bash # Borrow USDC from delegated credit aave-borrow.sh USDC 100 # Swap to ETH using Bankr bankr.sh "Swap 100 USDC for ETH on Base" ``` ### Periodic DCA ```bash # Agent borrows USDC weekly and swaps to ETH aave-borrow.sh USDC 100 bankr.sh "Swap 100 USDC for ETH on Base" ``` ### Safety-First Portfolio Rebalance ```bash # Always check health first aave-status.sh # Only borrow if healthy aave-borrow.sh USDC 500 ``` ## Configuration Reference ### config.json Fields | Field | Required | Description | |--------------------------|----------|-----------------------------------------------| | `chain` | Yes | Chain name (base, ethereum, polygon, arbitrum) | | `rpcUrl` | Yes | JSON-RPC endpoint URL | | `agentPrivateKey` | Yes | Agent wallet private key (0x-prefixed) | | `delegatorAddress` | Yes | User's main wallet that delegated credit | | `poolAddress` | Yes | Aave V3 Pool contract address | | `dataProviderAddress` | Yes | Aave V3 PoolDataProvider address | | `assets` | Yes | Map of symbol → {address, decimals} | | `safety.minHealthFactor` | No | Min HF after borrow (default: 1.5) | | `safety.maxBorrowPerTx` | No | Max borrow per transaction (default: 1000) | | `safety.maxBorrowPerTxUnit` | No | Unit for maxBorrowPerTx (default: USDC) | ### Environment Variables (override config) | Variable | Overrides | |-----------------------------|------------------------| | `AAVE_RPC_URL` | `rpcUrl` | | `AAVE_AGENT_PRIVATE_KEY` | `agentPrivateKey` | | `AAVE_DELEGATOR_ADDRESS` | `delegatorAddress` | | `AAVE_POOL_ADDRESS` | `poolAddress` | | `AAVE_MIN_HEALTH_FACTOR` | `safety.minHealthFactor` | ## Error Handling | Error | Cause | Fix | |------------------------------|-------------------------------------------|--------------------------------------------------| | `INSUFFICIENT_ALLOWANCE` | Delegation amount exceeded | Delegator must call `approveDelegation()` again | | `HEALTH_FACTOR_TOO_LOW` | Borrow would risk liquidation | Reduce amount or add collateral | | `AMOUNT_EXCEEDS_CAP` | Per-tx safety cap hit | Reduce amount or update config | | `INSUFFICIENT_LIQUIDITY` | Not enough in Aave pool | Try smaller amount or different asset | | `INSUFFICIENT_GAS` | Agent wallet has no native token | Send gas to agent wallet | | `EMODE_MISMATCH` | Asset incompatible with delegator's eMode | Borrow an asset in the same eMode category | ## Security See [safety.md](safety.md) for the full threat model and emergency procedures. **Critical rules:** 1. **The delegator's private key must NEVER be in this repo, config, or scripts** — this is the agent's workspace. The delegator manages their side via the Aave UI or a block explorer. 2. **Never commit config.json to version control** — it contains the agent's private key 3. **Never set `minHealthFactor` below 1.2** — liquidation happens at 1.0 4. **Always cap delegation amounts** — never approve `type(uint256).max` 5. **Monitor delegator health** — set up alerts if HF drops below 2.0 6. **Agent must refuse** to borrow if safety checks fail, even if instructed to ## Resources - **Aave V3 Docs**: https://docs.aave.com/developers - **Credit Delegation Guide**: https://docs.aave.com/developers/guides/credit-delegation - **Aave Address Book**: https://github.com/bgd-labs/aave-address-book - **Foundry Book**: https://book.getfoundry.sh/ - **DebtToken Reference**: https://docs.aave.com/developers/tokens/debttoken

path: deployments.md
content: # Aave Deployments Reference

All addresses verified on-chain. The credit delegation functions (`approveDelegation`, `borrowAllowance`, `borrow`, `repay`) use identical signatures on both V2 and V3.

> **To look up any debt token dynamically:**
> ```bash
> # Returns (aToken, stableDebtToken, variableDebtToken)
> cast call $DATA_PROVIDER \
>   "getReserveTokensAddresses(address)(address,address,address)" \
>   $ASSET_ADDRESS --rpc-url $RPC_URL
> ```
> The **3rd return value** is the VariableDebtToken — that's what the delegator calls `approveDelegation()` on.

---

## Aave V3

### Base

| Contract           | Address                                      |
|--------------------|----------------------------------------------|
| Pool               | [REDACTED_SECRET] |
| PoolAddressesProvider | [REDACTED_SECRET] |
| PoolDataProvider   | [REDACTED_SECRET] |

| Token | Underlying | Decimals | aToken | VariableDebtToken |
|-------|-----------|----------|--------|-------------------|
| USDC  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WETH  | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| cbETH | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| USDbC | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |

### Ethereum Mainnet

| Contract           | Address                                      |
|--------------------|----------------------------------------------|
| Pool               | [REDACTED_SECRET] |
| PoolAddressesProvider | [REDACTED_SECRET] |
| PoolDataProvider   | [REDACTED_SECRET] |

| Token | Underlying | Decimals | aToken | VariableDebtToken |
|-------|-----------|----------|--------|-------------------|
| USDC  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| USDT  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WETH  | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| DAI   | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WBTC  | [REDACTED_SECRET] | 8 | [REDACTED_SECRET] | [REDACTED_SECRET] |

### Polygon

| Contract           | Address                                      |
|--------------------|----------------------------------------------|
| Pool               | [REDACTED_SECRET] |
| PoolAddressesProvider | [REDACTED_SECRET] |
| PoolDataProvider   | [REDACTED_SECRET] |

| Token  | Underlying | Decimals | aToken | VariableDebtToken |
|--------|-----------|----------|--------|-------------------|
| USDC   | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| USDCe  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| USDT   | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WETH   | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WMATIC | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WBTC   | [REDACTED_SECRET] | 8 | [REDACTED_SECRET] | [REDACTED_SECRET] |

### Arbitrum

| Contract           | Address                                      |
|--------------------|----------------------------------------------|
| Pool               | [REDACTED_SECRET] |
| PoolAddressesProvider | [REDACTED_SECRET] |
| PoolDataProvider   | [REDACTED_SECRET] |

| Token | Underlying | Decimals | aToken | VariableDebtToken |
|-------|-----------|----------|--------|-------------------|
| USDC  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| USDCe | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| USDT  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WETH  | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WBTC  | [REDACTED_SECRET] | 8 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| DAI   | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| ARB   | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |

### Testnets

| Chain        | Pool                                           | Notes |
|--------------|------------------------------------------------|-------|
| Base Sepolia | [REDACTED_SECRET] | PoolAddressesProvider: [REDACTED_SECRET] |
| Eth Sepolia  | [REDACTED_SECRET] | Faucet at app.aave.com (testnet mode) |

---

## Aave V2

> V2 was never deployed to Arbitrum or Base. V2 naming: LendingPool (not Pool), ProtocolDataProvider (not PoolDataProvider).
> V2's `getUserAccountData` returns values denominated in ETH (18 decimals), not USD (8 decimals) like V3. The health factor check still works identically.

### Ethereum Mainnet

| Contract                | Address                                      |
|-------------------------|----------------------------------------------|
| LendingPool             | [REDACTED_SECRET] |
| LendingPoolAddressesProvider | [REDACTED_SECRET] |
| ProtocolDataProvider    | [REDACTED_SECRET] |

| Token | Underlying | Decimals | aToken | VariableDebtToken |
|-------|-----------|----------|--------|-------------------|
| USDC  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| USDT  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WETH  | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| DAI   | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WBTC  | [REDACTED_SECRET] | 8 | [REDACTED_SECRET] | [REDACTED_SECRET] |

### Polygon

| Contract                | Address                                      |
|-------------------------|----------------------------------------------|
| LendingPool             | [REDACTED_SECRET] |
| LendingPoolAddressesProvider | [REDACTED_SECRET] |
| ProtocolDataProvider    | [REDACTED_SECRET] |

| Token  | Underlying | Decimals | aToken | VariableDebtToken |
|--------|-----------|----------|--------|-------------------|
| USDCe  | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| USDT   | [REDACTED_SECRET] | 6 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WETH   | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| DAI    | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WBTC   | [REDACTED_SECRET] | 8 | [REDACTED_SECRET] | [REDACTED_SECRET] |
| WMATIC | [REDACTED_SECRET] | 18 | [REDACTED_SECRET] | [REDACTED_SECRET] |

---

## V2 vs V3 Contract Naming

| V2                             | V3                    |
|--------------------------------|-----------------------|
| LendingPool                    | Pool                  |
| LendingPoolAddressesProvider   | PoolAddressesProvider |
| ProtocolDataProvider           | PoolDataProvider      |

The function signatures for credit delegation are identical across both versions.

---

## Sources

- [Aave V3 Deployed Contracts](https://docs.aave.com/developers/deployed-contracts)
- [Aave V2 Deployed Contracts](https://docs.aave.com/developers/v/2.0/deployed-contracts)
- [bgd-labs/aave-address-book](https://github.com/bgd-labs/aave-address-book)
- Debt tokens resolved on-chain via `getReserveTokensAddresses()` on each chain's DataProvider


path: contracts.md
content: # Aave V3 Contract Addresses

> All addresses below are **Aave V3** deployments. The skill also works with **Aave V2** (same function signatures for credit delegation) — just swap in the V2 LendingPool and ProtocolDataProvider addresses.

## Mainnet Deployments

### Base
| Contract                | Address                                      |
|-------------------------|----------------------------------------------|
| Pool (L2Pool)           | [REDACTED_SECRET]  |
| PoolAddressesProvider   | [REDACTED_SECRET]  |
| PoolDataProvider        | [REDACTED_SECRET]  |
| WETHGateway             | [REDACTED_SECRET]  |

**Common Assets on Base:**
| Token | Address                                      | Decimals |
|-------|----------------------------------------------|----------|
| USDC  | [REDACTED_SECRET] | 6        |
| USDbC | [REDACTED_SECRET] | 6        |
| WETH  | `0x4200000000000000000000000000000000000006`   | 18       |
| cbETH | [REDACTED_SECRET] | 18       |

### Ethereum Mainnet
| Contract                | Address                                      |
|-------------------------|----------------------------------------------|
| Pool                    | [REDACTED_SECRET]  |
| PoolAddressesProvider   | [REDACTED_SECRET]  |
| PoolDataProvider        | [REDACTED_SECRET]  |

### Polygon
| Contract                | Address                                      |
|-------------------------|----------------------------------------------|
| Pool                    | [REDACTED_SECRET]  |
| PoolAddressesProvider   | [REDACTED_SECRET]  |
| PoolDataProvider        | [REDACTED_SECRET]  |

### Arbitrum
| Contract                | Address                                      |
|-------------------------|----------------------------------------------|
| Pool                    | [REDACTED_SECRET]  |
| PoolAddressesProvider   | [REDACTED_SECRET]  |
| PoolDataProvider        | [REDACTED_SECRET]  |

## Testnet Deployments

### Base Sepolia
| Contract                | Address                                      |
|-------------------------|----------------------------------------------|
| Pool                    | [REDACTED_SECRET]  |
| PoolAddressesProvider   | [REDACTED_SECRET]  |

### Ethereum Sepolia
| Contract                | Address                                      |
|-------------------------|----------------------------------------------|
| Pool                    | [REDACTED_SECRET]  |
| Faucet                  | Available at app.aave.com (testnet mode)      |

## How to Resolve DebtToken Addresses

DebtToken addresses are per-asset and per-chain. Look them up dynamically:

```bash
# Returns (aTokenAddress, stableDebtTokenAddress, variableDebtTokenAddress)
cast call $DATA_PROVIDER \
  "getReserveTokensAddresses(address)(address,address,address)" \
  $ASSET_ADDRESS \
  --rpc-url $RPC_URL
```

The **3rd return value** is the VariableDebtToken — that's the one the delegator calls `approveDelegation()` on.

## Delegator Setup

The delegator sets up collateral and delegation from **their own wallet** — via [app.aave.com](https://app.aave.com) and a block explorer. See [README.md](README.md) for the full step-by-step guide.

The delegator's private key should **never** be in this repo or config.

### Verify delegation (read-only, no key needed)
```bash
# Look up the VariableDebtToken for an asset
cast call $DATA_PROVIDER \
  "getReserveTokensAddresses(address)(address,address,address)" \
  $ASSET_ADDRESS --rpc-url $RPC

# Check allowance
cast call $VAR_DEBT "borrowAllowance(address,address)(uint256)" \
  $DELEGATOR_ADDRESS $AGENT_ADDRESS --rpc-url $RPC
```

## Official Resources

- **Aave Address Book (npm/Solidity)**: https://github.com/bgd-labs/aave-address-book
- **Deployed Contracts Docs**: https://docs.aave.com/developers/deployed-contracts
- **Interactive Dashboard**: https://aave.com/docs/resources/addresses


path: aave-repay.sh
content: #!/usr/bin/env bash
# aave-repay.sh — Repay Aave V3 debt on behalf of delegator
# Usage: aave-repay.sh <SYMBOL> <AMOUNT|max>
# Example: aave-repay.sh USDC 100
#          aave-repay.sh USDC max
set -euo pipefail

# Strip cast's bracket annotations e.g. "7920000000000000 [7.92e15]" → "7920000000000000"
strip_cast() { sed 's/ *\[.*\]//' | tr -d ' '; }

SKILL_DIR="${SKILL_DIR:-$HOME/.openclaw/skills/aave-delegation}"
CONFIG="$SKILL_DIR/config.json"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# === Parse arguments ===
if [ $# -lt 2 ]; then
  echo "Usage: aave-repay.sh <SYMBOL> <AMOUNT|max>"
  echo "Example: aave-repay.sh USDC 100"
  exit 1
fi

SYMBOL="$1"
AMOUNT="$2"

# === Load config ===
RPC_URL="${AAVE_RPC_URL:-$(jq -r '.rpcUrl' "$CONFIG")}"
AGENT_PK="${AAVE_AGENT_PRIVATE_KEY:-$(jq -r '.agentPrivateKey' "$CONFIG")}"
DELEGATOR="${AAVE_DELEGATOR_ADDRESS:-$(jq -r '.delegatorAddress' "$CONFIG")}"
POOL="${AAVE_POOL_ADDRESS:-$(jq -r '.poolAddress' "$CONFIG")}"
DATA_PROVIDER=$(jq -r '.dataProviderAddress' "$CONFIG")
CHAIN=$(jq -r '.chain // "unknown"' "$CONFIG")

ASSET_ADDR=$(jq -r ".assets[\"$SYMBOL\"].address // empty" "$CONFIG")
DECIMALS=$(jq -r ".assets[\"$SYMBOL\"].decimals // empty" "$CONFIG")

if [ -z "$ASSET_ADDR" ] || [ -z "$DECIMALS" ]; then
  echo -e "${RED}✗ Asset $SYMBOL not found in config${NC}"
  exit 1
fi

AGENT_ADDR=$(cast wallet address "$AGENT_PK")
MAX_UINT="115792089237316195423570985008687907853269984665640564039457584007913129639935"

# Resolve variable debt token
TOKENS=$(cast call "$DATA_PROVIDER" \
  "getReserveTokensAddresses(address)(address,address,address)" \
  "$ASSET_ADDR" \
  --rpc-url "$RPC_URL")
VAR_DEBT_TOKEN=$(echo "$TOKENS" | sed -n '3p' | strip_cast)

# Check current debt
DEBT_RAW=$(cast call "$VAR_DEBT_TOKEN" \
  "balanceOf(address)(uint256)" \
  "$DELEGATOR" \
  --rpc-url "$RPC_URL")
DEBT_RAW=$(echo "$DEBT_RAW" | strip_cast)
DEBT=$(echo "scale=$DECIMALS; $DEBT_RAW / (10^$DECIMALS)" | bc)

echo "=== Aave V3 Debt Repayment ==="
echo "  Chain:      $CHAIN"
echo "  Asset:      $SYMBOL"
echo "  Delegator:  $DELEGATOR"
echo "  Agent:      $AGENT_ADDR"
echo "  Current debt: $DEBT $SYMBOL"
echo ""

if [ "$DEBT_RAW" = "0" ]; then
  echo -e "${YELLOW}⚠ No outstanding $SYMBOL debt for delegator${NC}"
  exit 0
fi

# Handle "max" repayment
if [ "$AMOUNT" = "max" ]; then
  # For max repay, use type(uint256).max which tells Aave to repay the full debt
  AMOUNT_RAW="$MAX_UINT"
  AMOUNT="$DEBT"
  echo "  Repaying: MAX (full debt = $DEBT $SYMBOL)"
else
  AMOUNT_RAW=$(echo "$AMOUNT * (10^$DECIMALS)" | bc | cut -d'.' -f1)
  echo "  Repaying: $AMOUNT $SYMBOL ($AMOUNT_RAW raw)"
fi

# Check agent has enough tokens to repay
AGENT_BALANCE_RAW=$(cast call "$ASSET_ADDR" \
  "balanceOf(address)(uint256)" \
  "$AGENT_ADDR" \
  --rpc-url "$RPC_URL")
AGENT_BALANCE_RAW=$(echo "$AGENT_BALANCE_RAW" | strip_cast)
AGENT_BALANCE=$(echo "scale=$DECIMALS; $AGENT_BALANCE_RAW / (10^$DECIMALS)" | bc)

echo "  Agent $SYMBOL balance: $AGENT_BALANCE"

# For max repay, we need at least the debt amount
NEEDED_RAW="$DEBT_RAW"
if [ "$AMOUNT" != "$DEBT" ]; then
  NEEDED_RAW="$AMOUNT_RAW"
fi

if (( $(echo "$AGENT_BALANCE_RAW < $NEEDED_RAW" | bc) )); then
  echo -e "${RED}✗ Agent doesn't have enough $SYMBOL to repay${NC}"
  echo "  Has: $AGENT_BALANCE $SYMBOL"
  echo "  Needs: $AMOUNT $SYMBOL"
  exit 1
fi
echo -e "${GREEN}✓${NC} Agent has sufficient $SYMBOL"

# === Step 1: Approve Pool to spend tokens ===
echo ""
echo "--- Step 1: Approve Pool ---"

# Check existing allowance
EXISTING_ALLOWANCE=$(cast call "$ASSET_ADDR" \
  "allowance(address,address)(uint256)" \
  "$AGENT_ADDR" "$POOL" \
  --rpc-url "$RPC_URL")
EXISTING_ALLOWANCE=$(echo "$EXISTING_ALLOWANCE" | strip_cast)

# Determine approval amount
if [ "$AMOUNT_RAW" = "$MAX_UINT" ]; then
  APPROVE_AMOUNT="$DEBT_RAW"
  # Add 1% buffer for accrued interest between approval and repay
  APPROVE_AMOUNT=$(echo "$APPROVE_AMOUNT * 101 / 100" | bc)
else
  APPROVE_AMOUNT="$AMOUNT_RAW"
fi

if (( $(echo "$EXISTING_ALLOWANCE >= $APPROVE_AMOUNT" | bc) )); then
  echo -e "${GREEN}✓${NC} Pool already has sufficient allowance"
else
  echo "  Approving Pool to spend $SYMBOL..."
  APPROVE_TX=$(cast send "$ASSET_ADDR" \
    "approve(address,uint256)" \
    "$POOL" \
    "$APPROVE_AMOUNT" \
    --private-key "$AGENT_PK" \
    --rpc-url "$RPC_URL" \
    --json 2>/dev/null | jq -r '.transactionHash // .hash // empty' || echo "")
  
  if [ -z "$APPROVE_TX" ]; then
    # Fallback without --json
    cast send "$ASSET_ADDR" \
      "approve(address,uint256)" \
      "$POOL" \
      "$APPROVE_AMOUNT" \
      --private-key "$AGENT_PK" \
      --rpc-url "$RPC_URL"
  else
    echo -e "${GREEN}✓${NC} Approved. TX: $APPROVE_TX"
  fi
fi

# === Step 2: Execute Repay ===
echo ""
echo "--- Step 2: Repay ---"

# For max repay, pass type(uint256).max so Aave repays exact debt amount
if [ "$AMOUNT_RAW" = "$MAX_UINT" ]; then
  REPAY_AMOUNT="$MAX_UINT"
else
  REPAY_AMOUNT="$AMOUNT_RAW"
fi

echo "  Pool.repay($ASSET_ADDR, $REPAY_AMOUNT, 2, $DELEGATOR)"

TX_HASH=$(cast send "$POOL" \
  "repay(address,uint256,uint256,address)" \
  "$ASSET_ADDR" \
  "$REPAY_AMOUNT" \
  2 \
  "$DELEGATOR" \
  --private-key "$AGENT_PK" \
  --rpc-url "$RPC_URL" \
  --json 2>/dev/null | jq -r '.transactionHash // .hash // empty')

if [ -z "$TX_HASH" ]; then
  TX_OUTPUT=$(cast send "$POOL" \
    "repay(address,uint256,uint256,address)" \
    "$ASSET_ADDR" \
    "$REPAY_AMOUNT" \
    2 \
    "$DELEGATOR" \
    --private-key "$AGENT_PK" \
    --rpc-url "$RPC_URL" 2>&1)
  echo "$TX_OUTPUT"
  TX_HASH=$(echo "$TX_OUTPUT" | grep -oE '0x[a-fA-F0-9]{64}' | head -1 || echo "")
fi

if [ -n "$TX_HASH" ]; then
  echo -e "${GREEN}✓ Repayment successful!${NC}"
  echo "  TX: $TX_HASH"
  
  # Check remaining debt
  sleep 2  # wait for state update
  NEW_DEBT_RAW=$(cast call "$VAR_DEBT_TOKEN" \
    "balanceOf(address)(uint256)" \
    "$DELEGATOR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "?")
  NEW_DEBT_RAW=$(echo "$NEW_DEBT_RAW" | strip_cast)
  if [ "$NEW_DEBT_RAW" != "?" ]; then
    NEW_DEBT=$(echo "scale=$DECIMALS; $NEW_DEBT_RAW / (10^$DECIMALS)" | bc)
    echo "  Remaining $SYMBOL debt: $NEW_DEBT"
  fi
  
  # Check new health factor
  NEW_ACCOUNT=$(cast call "$POOL" \
    "getUserAccountData(address)(uint256,uint256,uint256,uint256,uint256,uint256)" \
    "$DELEGATOR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "")
  if [ -n "$NEW_ACCOUNT" ]; then
    NEW_HF_RAW=$(echo "$NEW_ACCOUNT" | sed -n '6p' | strip_cast)
    if [ "$NEW_HF_RAW" = "$MAX_UINT" ]; then
      echo "  Health factor: ∞ (all debt repaid)"
    else
      NEW_HF=$(echo "scale=4; $NEW_HF_RAW / 1000000000000000000" | bc)
      echo "  Health factor: $NEW_HF"
    fi
  fi
else
  echo -e "${RED}✗ Repayment may have failed. Check status manually.${NC}"
  exit 1
fi


path: safety.md
content: # Safety Guidelines — Aave Credit Delegation Skill

## Threat Model

This skill allows an AI agent to autonomously borrow funds against a human's collateral. The attack surface is significant.

### Risk 1: Prompt Injection → Wallet Drain
**Severity: CRITICAL**

If an attacker injects a malicious prompt into the agent (via a message, webhook, or compromised skill), they could instruct the agent to:
- Borrow the maximum delegated amount
- Transfer borrowed funds to an attacker address
- Do this silently, without user notification

**Mitigations:**
- Cap delegation amounts via `approveDelegation()` — never use `type(uint256).max`
- Set `safety.maxBorrowPerTx` to a sane limit in config
- Monitor the delegator's health factor externally (e.g., DeFi Saver automations)
- Run OpenClaw in a sandboxed environment
- Consider revoking delegation when not actively needed

### Risk 2: Health Factor Collapse → Liquidation
**Severity: HIGH**

If the agent borrows too much, the delegator's health factor drops below 1.0 and their collateral gets liquidated.

**Mitigations:**
- `safety.minHealthFactor` defaults to 1.5 (50% buffer above liquidation)
- The borrow script checks HF before every transaction
- Set up external health factor monitoring (Aave notifications, DeFi Saver)
- Never set `minHealthFactor` below 1.2

### Risk 3: Private Key Exposure
**Severity: HIGH**

The agent's private key is stored in config.json. If the machine is compromised, the key is exposed.

**Mitigations:**
- Use a dedicated wallet for the agent — never your main wallet's key
- Set restrictive file permissions: `chmod 600 ~/.openclaw/skills/aave-delegation/config.json`
- Never commit config.json to version control
- Consider environment variables instead of file storage
- The agent wallet should only hold minimal gas — all borrowed funds should be used or returned promptly

### Risk 4: Stale Delegation
**Severity: MEDIUM**

If you forget about an active delegation, the agent (or a compromised agent) can borrow at any time.

**Mitigations:**
- Regularly audit active delegations: `scripts/aave-status.sh`
- Revoke delegation when not needed: call `approveDelegation(agentAddr, 0)` on the DebtToken
- Set calendar reminders to review delegation status

### Risk 5: Oracle Manipulation / Market Volatility
**Severity: MEDIUM**

If the collateral asset drops sharply in value, the health factor can plummet between the agent's safety check and the borrow execution.

**Mitigations:**
- Use conservative `minHealthFactor` (2.0+ for volatile collateral)
- Prefer stablecoin collateral when possible
- Don't borrow close to the maximum available

## Safety Configuration Recommendations

### Conservative (recommended for most users)
```json
{
  "safety": {
    "minHealthFactor": "2.0",
    "maxBorrowPerTx": "100",
    "maxBorrowPerTxUnit": "USDC"
  }
}
```

### Moderate
```json
{
  "safety": {
    "minHealthFactor": "1.5",
    "maxBorrowPerTx": "500",
    "maxBorrowPerTxUnit": "USDC"
  }
}
```

### Aggressive (experienced users only)
```json
{
  "safety": {
    "minHealthFactor": "1.2",
    "maxBorrowPerTx": "5000",
    "maxBorrowPerTxUnit": "USDC"
  }
}
```

## Delegation Best Practices

1. **Start small** — Delegate $50-100 initially. Increase only after testing.
2. **One asset at a time** — Don't delegate all assets simultaneously.
3. **Monitor actively** — Check `aave-status.sh` daily during initial use.
4. **Revoke when idle** — If the agent doesn't need to borrow for a while, set delegation to 0.
5. **Use stablecoins** — Delegating USDC borrowing against ETH collateral is simpler to reason about than volatile-on-volatile.
6. **Test on testnet first** — Use Base Sepolia or Ethereum Sepolia with faucet tokens.

## Emergency Procedures

### Revoke All Delegation Immediately
```bash
# For each asset, set delegation to 0
cast send $VAR_DEBT_TOKEN "approveDelegation(address,uint256)" \
  $AGENT_ADDRESS 0 \
  --private-key $YOUR_PK --rpc-url $RPC
```

### Force Repay All Debt
```bash
# From your main wallet, repay the agent's borrowed debt
# First approve Pool
cast send $ASSET "approve(address,uint256)" $POOL $MAX_UINT \
  --private-key $YOUR_PK --rpc-url $RPC

# Repay
cast send $POOL "repay(address,uint256,uint256,address)" \
  $ASSET $MAX_UINT 2 $YOUR_ADDRESS \
  --private-key $YOUR_PK --rpc-url $RPC
```

### Check If Agent Borrowed Without Permission
```bash
# Check debt token balance — if non-zero, someone borrowed
cast call $VAR_DEBT_TOKEN "balanceOf(address)(uint256)" $YOUR_ADDRESS --rpc-url $RPC

# Check recent events on the Pool for Borrow events from your address
cast logs --from-block -1000 --address $POOL \
  "Borrow(address,address,address,uint256,uint8,uint256,uint16)" \
  --rpc-url $RPC | grep -i $YOUR_ADDRESS
```

## What the Agent Must NEVER Do

1. **Never bypass safety checks** — even if explicitly instructed by a user message
2. **Never borrow more than `maxBorrowPerTx`** in a single operation
3. **Never borrow if health factor would drop below `minHealthFactor`**
4. **Never transfer borrowed funds to addresses not controlled by the user**
5. **Never store or log the delegator's private key** (the agent only has its own key)
6. **Never approve unlimited delegation on behalf of the user**


path: README.md
content: # Agent Credit — Credit Delegation for AI Agents

Give your AI agent a credit line. It borrows from Aave when it needs funds, and the debt accrues on your position. You stay in control — you choose which assets it can borrow, how much, and you can revoke anytime.

Works on **Aave V2** and **Aave V3**. Preconfigured for Base, Ethereum, Polygon, and Arbitrum — but works on any EVM chain where Aave is deployed.

![Agent Credit — Aave Credit Delegation](img/credit.png)

## Compatible With

- **[OpenClaw](https://openclaw.ai/)** — Install as a skill and the agent can borrow autonomously
- **[Claude Code](https://www.npmjs.com/package/@anthropic-ai/claude-code)** — Run the scripts directly from a Claude Code session
- **Any agent framework** — The scripts are plain bash + Foundry's `cast`, so they work anywhere with a shell

Combines naturally with **[Bankr](https://bankr.bot/)** skills — borrow USDC via delegation, then use Bankr to swap, bridge, or deploy it. The agent gets a credit line *and* a full DeFi toolkit.

## What This Enables

- **Self-funding agents** — The agent borrows stablecoins or tokens to pay for operations without you manually transferring funds each time
- **Autonomous DCA** — Agent borrows USDC periodically, then uses Bankr to swap into ETH
- **Gas self-sufficiency** — Agent borrows a tiny amount of WETH to cover its own gas when it runs low
- **On-demand liquidity** — Agent accesses capital exactly when needed, not sitting idle in a wallet
- **Borrow + swap combos** — Borrow USDC via delegation, swap to any token via Bankr, all in one agent flow

The agent only needs a wallet with a tiny amount of ETH for gas. All real capital comes from your Aave position via delegation.

## How Credit Delegation Works

Credit delegation separates two things: **borrowing power** and **delegation approval**.

**Borrowing power is holistic.** It comes from your entire collateral position across all assets. If you deposit $10k worth of ETH at 80% LTV, you have $8k of borrowing capacity. That capacity isn't locked to any specific asset — it's a pool-wide number.

**Delegation approval is isolated per debt token.** You control *which* assets the agent can borrow and *how much* of each by calling `approveDelegation()` on individual VariableDebtTokens. Each asset has its own debt token contract, and each approval is independent.

```
Your Collateral (holistic)              Delegation Approvals (isolated)
┌─────────────────────────┐             ┌──────────────────────────────┐
│  $5k ETH                │             │  USDC DebtToken → agent: 500 │
│  $3k USDC               │  ───LTV───▶ │  WETH DebtToken → agent: 0.5 │
│  $2k cbETH              │   = $8k     │  cbETH DebtToken → agent: 0  │
│  Total: $10k @ 80% LTV  │  capacity   └──────────────────────────────┘
└─────────────────────────┘
```

So if you deposit ETH as collateral, you can approve the agent to borrow up to 500 USDC and 0.1 WETH — but not cbETH. The agent can only borrow what you've explicitly approved, but the *capacity* to borrow comes from your total collateral.

## Scripts

These scripts are for the **agent** to use. The delegator never runs them.

| Script | What it does |
|--------|-------------|
| `aave-setup.sh` | Verify config, dependencies, and delegation status |
| `aave-borrow.sh <SYMBOL> <AMOUNT>` | Borrow via delegation (runs 4 safety checks first) |
| `aave-repay.sh <SYMBOL> <AMOUNT\|max>` | Repay debt on behalf of the delegator |
| `aave-status.sh [SYMBOL] [--health-only] [--json]` | Check allowances, health factor, and debt |

Every borrow runs these checks before executing:
1. **Per-tx cap** — amount within configured limit
2. **Delegation allowance** — sufficient allowance on the debt token
3. **Health factor** — delegator's position stays healthy after borrow
4. **Gas balance** — agent wallet has enough ETH for the transaction

If any check fails, the borrow is aborted with a clear error.

## Safety

The agent never has access to your private key. It only holds its own key (for signing borrow/repay transactions) and your public address (to know whose position to borrow against).

**Your private key should never be in this folder, in the config, or in any script here.** This repo is the agent's workspace. All delegator actions (supplying collateral, approving delegation, revoking) are done from your own wallet through the Aave UI or a block explorer.

You control exposure through:
- **Delegation ceilings** per asset (set via `approveDelegation`)
- **Per-transaction caps** in the config (`safety.maxBorrowPerTx`)
- **Health factor floor** (`safety.minHealthFactor`, default 1.5)
- **Instant revocation** — set delegation to 0 at any time

See [safety.md](safety.md) for the full threat model and emergency procedures.

---

# Delegator Setup Guide

Everything below is done from **your own wallet** — through the Aave web UI, a block explorer, or your preferred wallet app. You never need to clone this repo, run these scripts, or enter your private key anywhere here.

## Step 1: Supply Collateral

Go to [app.aave.com](https://app.aave.com), connect your wallet, and supply collateral (ETH, USDC, etc.). This is standard Aave usage — nothing specific to credit delegation yet.

If you already have a position on Aave, skip to Step 2.

## Step 2: Find the Debt Token

Each asset on Aave has a **VariableDebtToken** — that's the contract you approve delegation on. You need its address for every asset you want the agent to borrow.

### From the Aave UI

On [app.aave.com](https://app.aave.com), go to the reserve page for the asset. Click the token icon to expand a dropdown showing the underlying token, the aToken, and the **debt token** address:

![Finding the debt token address on the Aave UI](img/token-find.png)

Click the debt token address to open it on the block explorer — you'll need it for Step 3.

### From deployments.md

All debt token addresses for common assets are listed in [deployments.md](deployments.md). Look up your chain and asset.

## Step 3: Approve Delegation

This is the key step. You call `approveDelegation()` on the VariableDebtToken to tell Aave: "this agent address can borrow up to X of this asset, and the debt goes on my position."

### Via block explorer (recommended)

1. Go to the VariableDebtToken address on the block explorer (Basescan, Etherscan, Polygonscan, etc.)
2. Click **Contract** → **Write Contract** → **Connect to Web3** (connect your wallet)
3. Find **`approveDelegation`** (function #2)
4. Fill in:
   - **delegatee**: the agent's wallet address
   - **amount**: the maximum borrow amount in **raw units** (see note below)
5. Click **Write** and confirm the transaction in your wallet

![Calling approveDelegation on the block explorer](img/approve.png)

> **Raw units:** Amounts must be in the token's smallest unit. USDC has 6 decimals, so 500 USDC = `500000000`. WETH has 18 decimals, so 0.1 WETH = `100000000000000000`. See [deployments.md](deployments.md) for decimals per asset.

### Approve multiple assets

Each asset has its own debt token. Repeat the process above for each asset you want the agent to borrow. For example:
- USDC VariableDebtToken → `approveDelegation(agent, 500000000)` — up to 500 USDC
- WETH VariableDebtToken → `approveDelegation(agent, 100000000000000000)` — up to 0.1 WETH

The agent cannot borrow any asset you haven't approved.

## Step 4: Fund the Agent Wallet for Gas

The agent wallet needs a small amount of ETH to pay for transaction gas. On Base, a borrow costs ~$0.01, so even 0.001 ETH goes a long way.

Send a tiny amount of ETH to the agent's address from your wallet (any wallet app, exchange, or bridge works).

## Step 5: Verify

Run the setup check to confirm everything is connected:

```bash
./aave-setup.sh
```

This shows delegation allowances per asset, your health factor, and whether the agent has gas. No private key needed — it only reads on-chain data.

---

## Managing Delegation

### Increase or change an allowance

Call `approveDelegation` again on the block explorer with the new amount. It **replaces** the previous value (not additive).

### Revoke delegation for one asset

Call `approveDelegation` on the debt token with amount = `0`. The agent can no longer borrow that asset.

### Revoke all delegation

Call `approveDelegation(..., 0)` on every VariableDebtToken you previously approved.

### Check outstanding debt

On [app.aave.com](https://app.aave.com), your dashboard shows all outstanding borrows. Any debt the agent created shows up here — it's on your position.

### Repay debt yourself

On [app.aave.com](https://app.aave.com), click **Repay** on any borrow. Standard Aave repayment — the agent doesn't need to be involved. The agent can also repay via `aave-repay.sh`.

---

## Safety Recommendations

- **Start small.** Approve $50-100 initially. Increase after you've tested the flow.
- **Never approve `type(uint256).max`.** Always set a concrete ceiling per asset.
- **Monitor your health factor.** Set up alerts on [app.aave.com](https://app.aave.com) or [DeFi Saver](https://defisaver.com).
- **Revoke when idle.** If the agent doesn't need to borrow for a while, set delegation to 0.
- **Prefer stablecoins for borrowing.** Borrowing USDC against ETH collateral is simpler to reason about than volatile-on-volatile.
- **Test on a testnet first.** Use Base Sepolia or Ethereum Sepolia with faucet tokens before real funds.

See [safety.md](safety.md) for the full threat model and emergency procedures.

---

## Quick Reference

| Action | How |
|--------|-----|
| Supply collateral | [app.aave.com](https://app.aave.com) → Supply |
| Find debt token | [app.aave.com](https://app.aave.com) → Reserve page → token dropdown, or [deployments.md](deployments.md) |
| Approve delegation | Block explorer → VariableDebtToken → `approveDelegation(agent, amount)` |
| Revoke delegation | Block explorer → VariableDebtToken → `approveDelegation(agent, 0)` |
| Check delegation | `./aave-status.sh` or block explorer → `borrowAllowance(you, agent)` |
| Monitor health | [app.aave.com](https://app.aave.com) dashboard |
| Repay debt | [app.aave.com](https://app.aave.com) → Repay, or agent runs `aave-repay.sh` |
| Fund agent gas | Send ETH to agent address from any wallet |

## Reference Files

| File | Contents |
|------|----------|
| [SKILL.md](SKILL.md) | Agent-facing skill documentation |
| [deployments.md](deployments.md) | All Aave V2/V3 contract + debt token addresses |
| [contracts.md](contracts.md) | Core contract addresses and delegator setup commands |
| [safety.md](safety.md) | Threat model, risk mitigations, emergency procedures |


path: config.example.json
content: {
  "chain": "base",
  "rpcUrl": "https://mainnet.base.org",
  "agentPrivateKey": "0xYOUR_AGENT_WALLET_PRIVATE_KEY",
  "delegatorAddress": "0xYOUR_MAIN_WALLET_ADDRESS",
  "poolAddress": [REDACTED_SECRET],
  "dataProviderAddress": [REDACTED_SECRET],
  "assets": {
    "USDC": {
      "address": [REDACTED_SECRET],
      "decimals": 6
    },
    "WETH": {
      "address": "0x4200000000000000000000000000000000000006",
      "decimals": 18
    },
    "cbETH": {
      "address": [REDACTED_SECRET],
      "decimals": 18
    }
  },
  "safety": {
    "minHealthFactor": "1.5",
    "maxBorrowPerTx": "1000",
    "maxBorrowPerTxUnit": "USDC"
  }
}


path: aave-borrow.sh
content: #!/usr/bin/env bash
# aave-borrow.sh — Borrow from Aave V3 via credit delegation
# Usage: aave-borrow.sh <SYMBOL> <AMOUNT>
# Example: aave-borrow.sh USDC 100
set -euo pipefail

SKILL_DIR="${SKILL_DIR:-$HOME/.openclaw/skills/aave-delegation}"
CONFIG="$SKILL_DIR/config.json"

# Strip cast's bracket annotations e.g. "7920000000000000 [7.92e15]" → "7920000000000000"
strip_cast() { sed 's/ *\[.*\]//' | tr -d ' '; }

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# === Parse arguments ===
if [ $# -lt 2 ]; then
  echo "Usage: aave-borrow.sh <SYMBOL> <AMOUNT>"
  echo "Example: aave-borrow.sh USDC 100"
  exit 1
fi

SYMBOL="$1"
AMOUNT="$2"

# === Load config ===
RPC_URL="${AAVE_RPC_URL:-$(jq -r '.rpcUrl' "$CONFIG")}"
AGENT_PK="${AAVE_AGENT_PRIVATE_KEY:-$(jq -r '.agentPrivateKey' "$CONFIG")}"
DELEGATOR="${AAVE_DELEGATOR_ADDRESS:-$(jq -r '.delegatorAddress' "$CONFIG")}"
POOL="${AAVE_POOL_ADDRESS:-$(jq -r '.poolAddress' "$CONFIG")}"
DATA_PROVIDER=$(jq -r '.dataProviderAddress' "$CONFIG")
CHAIN=$(jq -r '.chain // "unknown"' "$CONFIG")

ASSET_ADDR=$(jq -r ".assets[\"$SYMBOL\"].address // empty" "$CONFIG")
DECIMALS=$(jq -r ".assets[\"$SYMBOL\"].decimals // empty" "$CONFIG")

if [ -z "$ASSET_ADDR" ] || [ -z "$DECIMALS" ]; then
  echo -e "${RED}✗ Asset $SYMBOL not found in config${NC}"
  exit 1
fi

AGENT_ADDR=$(cast wallet address "$AGENT_PK")

# Convert human amount to raw (e.g., 100 USDC → 100000000)
AMOUNT_RAW=$(echo "$AMOUNT * (10^$DECIMALS)" | bc | cut -d'.' -f1)

# Safety config
MIN_HF="${AAVE_MIN_HEALTH_FACTOR:-$(jq -r '.safety.minHealthFactor // "1.5"' "$CONFIG")}"
MAX_BORROW=$(jq -r '.safety.maxBorrowPerTx // "1000"' "$CONFIG")
MAX_BORROW_UNIT=$(jq -r '.safety.maxBorrowPerTxUnit // "USDC"' "$CONFIG")

echo "=== Aave V3 Credit Delegation Borrow ==="
echo "  Chain:      $CHAIN"
echo "  Asset:      $SYMBOL ($ASSET_ADDR)"
echo "  Amount:     $AMOUNT $SYMBOL ($AMOUNT_RAW raw)"
echo "  Delegator:  $DELEGATOR"
echo "  Agent:      $AGENT_ADDR"
echo "  Pool:       $POOL"
echo ""

# === SAFETY CHECK 1: Per-transaction cap ===
echo "--- Safety Check 1: Transaction Cap ---"
# Simple check — if same unit, compare directly
if [ "$SYMBOL" = "$MAX_BORROW_UNIT" ]; then
  if (( $(echo "$AMOUNT > $MAX_BORROW" | bc -l) )); then
    echo -e "${RED}✗ AMOUNT_EXCEEDS_CAP: $AMOUNT $SYMBOL exceeds max $MAX_BORROW $MAX_BORROW_UNIT per tx${NC}"
    echo "  Update safety.maxBorrowPerTx in config to increase limit."
    exit 1
  fi
fi
echo -e "${GREEN}✓${NC} Amount within per-tx cap ($MAX_BORROW $MAX_BORROW_UNIT)"

# === SAFETY CHECK 2: Delegation allowance ===
echo "--- Safety Check 2: Delegation Allowance ---"

# Resolve variable debt token
TOKENS=$(cast call "$DATA_PROVIDER" \
  "getReserveTokensAddresses(address)(address,address,address)" \
  "$ASSET_ADDR" \
  --rpc-url "$RPC_URL")
VAR_DEBT_TOKEN=$(echo "$TOKENS" | sed -n '3p' | strip_cast)

ALLOWANCE_RAW=$(cast call "$VAR_DEBT_TOKEN" \
  "borrowAllowance(address,address)(uint256)" \
  "$DELEGATOR" "$AGENT_ADDR" \
  --rpc-url "$RPC_URL")
ALLOWANCE_RAW=$(echo "$ALLOWANCE_RAW" | strip_cast)
ALLOWANCE=$(echo "scale=$DECIMALS; $ALLOWANCE_RAW / (10^$DECIMALS)" | bc)

if [ "$ALLOWANCE_RAW" = "0" ]; then
  echo -e "${RED}✗ INSUFFICIENT_ALLOWANCE: No delegation for $SYMBOL${NC}"
  echo "  Delegator must call: approveDelegation($AGENT_ADDR, amount) on $VAR_DEBT_TOKEN"
  exit 1
fi

if (( $(echo "$AMOUNT_RAW > $ALLOWANCE_RAW" | bc) )); then
  echo -e "${RED}✗ INSUFFICIENT_ALLOWANCE: Need $AMOUNT $SYMBOL but only $ALLOWANCE $SYMBOL delegated${NC}"
  echo "  Delegator must increase delegation on $VAR_DEBT_TOKEN"
  exit 1
fi
echo -e "${GREEN}✓${NC} Delegation allowance sufficient: $ALLOWANCE $SYMBOL available"

# === SAFETY CHECK 3: Health factor ===
echo "--- Safety Check 3: Health Factor ---"

ACCOUNT_DATA=$(cast call "$POOL" \
  "getUserAccountData(address)(uint256,uint256,uint256,uint256,uint256,uint256)" \
  "$DELEGATOR" \
  --rpc-url "$RPC_URL")

TOTAL_COLLATERAL=$(echo "$ACCOUNT_DATA" | sed -n '1p' | strip_cast)
TOTAL_DEBT=$(echo "$ACCOUNT_DATA" | sed -n '2p' | strip_cast)
AVAILABLE_BORROWS=$(echo "$ACCOUNT_DATA" | sed -n '3p' | strip_cast)
HEALTH_FACTOR_RAW=$(echo "$ACCOUNT_DATA" | sed -n '6p' | strip_cast)

MAX_UINT="115792089237316195423570985008687907853269984665640564039457584007913129639935"
if [ "$HEALTH_FACTOR_RAW" = "$MAX_UINT" ]; then
  HF="999"  # effectively infinite
  HF_DISPLAY="∞ (no current debt)"
else
  HF=$(echo "scale=4; $HEALTH_FACTOR_RAW / 1000000000000000000" | bc)
  HF_DISPLAY="$HF"
fi

COLLATERAL_USD=$(echo "scale=2; $TOTAL_COLLATERAL / 100000000" | bc)
DEBT_USD=$(echo "scale=2; $TOTAL_DEBT / 100000000" | bc)

echo "  Current HF:     $HF_DISPLAY"
echo "  Collateral:     \$$COLLATERAL_USD"
echo "  Existing debt:  \$$DEBT_USD"

# Check current HF is above minimum
if (( $(echo "$HF < $MIN_HF" | bc -l) )) && [ "$HF" != "999" ]; then
  echo -e "${RED}✗ HEALTH_FACTOR_TOO_LOW: Current HF ($HF) is already below minimum ($MIN_HF)${NC}"
  echo "  Delegator should add collateral or repay debt before agent borrows more."
  exit 1
fi

# Check available borrows
if [ "$AVAILABLE_BORROWS" = "0" ]; then
  echo -e "${RED}✗ No available borrowing capacity for delegator${NC}"
  exit 1
fi

echo -e "${GREEN}✓${NC} Health factor OK: $HF_DISPLAY (minimum: $MIN_HF)"

# === SAFETY CHECK 4: Agent has enough gas ===
echo "--- Safety Check 4: Gas Balance ---"
AGENT_BALANCE=$(cast balance "$AGENT_ADDR" --rpc-url "$RPC_URL")
AGENT_ETH=$(cast from-wei "$AGENT_BALANCE")
# Aave borrow tx uses ~300k-500k gas. Estimate cost conservatively.
GAS_PRICE=$(cast gas-price --rpc-url "$RPC_URL" 2>/dev/null || echo "0")
MIN_GAS_WEI=$(echo "$GAS_PRICE * 500000" | bc)

if [ "$AGENT_BALANCE" = "0" ]; then
  echo -e "${RED}✗ INSUFFICIENT_GAS: Agent wallet has 0 ETH${NC}"
  echo "  Send at least 0.001 ETH to $AGENT_ADDR on $CHAIN for gas."
  exit 1
elif (( $(echo "$AGENT_BALANCE < $MIN_GAS_WEI" | bc) )); then
  MIN_GAS_ETH=$(cast from-wei "$MIN_GAS_WEI")
  echo -e "${RED}✗ INSUFFICIENT_GAS: Agent has $AGENT_ETH ETH but needs ~$MIN_GAS_ETH ETH for gas${NC}"
  echo "  Send at least 0.001 ETH to $AGENT_ADDR on $CHAIN."
  exit 1
fi
echo -e "${GREEN}✓${NC} Agent gas balance: $AGENT_ETH ETH"

# === EXECUTE BORROW ===
echo ""
echo "=== Executing Borrow ==="
echo "  Pool.borrow($ASSET_ADDR, $AMOUNT_RAW, 2, 0, $DELEGATOR)"
echo ""

# interestRateMode: 2 = variable rate
# referralCode: 0 (inactive)
# onBehalfOf: delegator address (debt goes to them)
TX_OUTPUT=$(cast send "$POOL" \
  "borrow(address,uint256,uint256,uint16,address)" \
  "$ASSET_ADDR" \
  "$AMOUNT_RAW" \
  2 \
  0 \
  "$DELEGATOR" \
  --private-key "$AGENT_PK" \
  --rpc-url "$RPC_URL" \
  --gas-limit 500000 \
  --json 2>&1)
TX_EXIT=$?

if [ $TX_EXIT -ne 0 ]; then
  # Parse common errors into human-readable messages
  if echo "$TX_OUTPUT" | grep -qi "insufficient funds"; then
    echo -e "${RED}✗ INSUFFICIENT_GAS: Agent wallet can't afford gas for this transaction.${NC}"
    echo "  Send more ETH to $AGENT_ADDR on $CHAIN."
  elif echo "$TX_OUTPUT" | grep -qi "revert"; then
    # Decode Aave-specific revert reasons
    REASON="$TX_OUTPUT"
    if echo "$TX_OUTPUT" | grep -q "0x11"; then
      REASON="Arithmetic overflow — likely insufficient collateral or invalid borrow parameters"
    elif echo "$TX_OUTPUT" | grep -qi "BORROWING_NOT_ENABLED"; then
      REASON="Borrowing is not enabled for $SYMBOL on this pool"
    elif echo "$TX_OUTPUT" | grep -qi "COLLATERAL_CANNOT_COVER"; then
      REASON="Delegator's collateral cannot cover this borrow amount"
    elif echo "$TX_OUTPUT" | grep -qi "HEALTH_FACTOR_LOWER"; then
      REASON="Borrow would drop the delegator's health factor below liquidation threshold"
    fi
    echo -e "${RED}✗ BORROW_REVERTED: $REASON${NC}"
  else
    echo -e "${RED}✗ BORROW_FAILED: $TX_OUTPUT${NC}"
  fi
  exit 1
fi

TX_HASH=$(echo "$TX_OUTPUT" | jq -r '.transactionHash // .hash // empty' 2>/dev/null)

if [ -n "$TX_HASH" ]; then
  echo -e "${GREEN}✓ Borrow successful!${NC}"
  echo "  TX: $TX_HASH"
  echo "  Amount: $AMOUNT $SYMBOL"
  echo "  Tokens sent to: $AGENT_ADDR"
  echo "  Debt charged to: $DELEGATOR"
  
  # Verify new balance
  NEW_BALANCE_RAW=$(cast call "$ASSET_ADDR" \
    "balanceOf(address)(uint256)" \
    "$AGENT_ADDR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "?")
  NEW_BALANCE_RAW=$(echo "$NEW_BALANCE_RAW" | strip_cast)
  if [ "$NEW_BALANCE_RAW" != "?" ]; then
    NEW_BALANCE=$(echo "scale=$DECIMALS; $NEW_BALANCE_RAW / (10^$DECIMALS)" | bc)
    echo "  Agent $SYMBOL balance: $NEW_BALANCE"
  fi
  
  # Check new health factor
  NEW_ACCOUNT=$(cast call "$POOL" \
    "getUserAccountData(address)(uint256,uint256,uint256,uint256,uint256,uint256)" \
    "$DELEGATOR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "")
  if [ -n "$NEW_ACCOUNT" ]; then
    NEW_HF_RAW=$(echo "$NEW_ACCOUNT" | sed -n '6p' | strip_cast)
    if [ "$NEW_HF_RAW" != "$MAX_UINT" ]; then
      NEW_HF=$(echo "scale=4; $NEW_HF_RAW / 1000000000000000000" | bc)
      echo "  New health factor: $NEW_HF"
    fi
  fi
else
  echo -e "${RED}✗ Borrow may have failed. Check transaction status manually.${NC}"
  exit 1
fi


path: aave-status.sh
content: #!/usr/bin/env bash
# aave-status.sh — Check delegation allowance, health factor, and debt
# Usage: aave-status.sh [SYMBOL] [--health-only] [--json]
set -euo pipefail

# Strip cast's bracket annotations e.g. "7920000000000000 [7.92e15]" → "7920000000000000"
strip_cast() { sed 's/ *\[.*\]//' | tr -d ' '; }

SKILL_DIR="${SKILL_DIR:-$HOME/.openclaw/skills/aave-delegation}"
CONFIG="$SKILL_DIR/config.json"
SCRIPTS_DIR="$(cd "$(dirname "$0")" && pwd)"

# Parse args
SYMBOL=""
HEALTH_ONLY=false
JSON_OUTPUT=false
for arg in "$@"; do
  case "$arg" in
    --health-only) HEALTH_ONLY=true ;;
    --json) JSON_OUTPUT=true ;;
    *) SYMBOL="$arg" ;;
  esac
done

# Load config
RPC_URL="${AAVE_RPC_URL:-$(jq -r '.rpcUrl' "$CONFIG")}"
AGENT_PK="${AAVE_AGENT_PRIVATE_KEY:-$(jq -r '.agentPrivateKey' "$CONFIG")}"
DELEGATOR="${AAVE_DELEGATOR_ADDRESS:-$(jq -r '.delegatorAddress' "$CONFIG")}"
POOL="${AAVE_POOL_ADDRESS:-$(jq -r '.poolAddress' "$CONFIG")}"
DATA_PROVIDER=$(jq -r '.dataProviderAddress' "$CONFIG")
AGENT_ADDR=$(cast wallet address "$AGENT_PK")

# === Health Factor ===
ACCOUNT_DATA=$(cast call "$POOL" \
  "getUserAccountData(address)(uint256,uint256,uint256,uint256,uint256,uint256)" \
  "$DELEGATOR" \
  --rpc-url "$RPC_URL")

TOTAL_COLLATERAL=$(echo "$ACCOUNT_DATA" | sed -n '1p' | strip_cast)
TOTAL_DEBT=$(echo "$ACCOUNT_DATA" | sed -n '2p' | strip_cast)
AVAILABLE_BORROWS=$(echo "$ACCOUNT_DATA" | sed -n '3p' | strip_cast)
CURRENT_LT=$(echo "$ACCOUNT_DATA" | sed -n '4p' | strip_cast)
LTV=$(echo "$ACCOUNT_DATA" | sed -n '5p' | strip_cast)
HEALTH_FACTOR_RAW=$(echo "$ACCOUNT_DATA" | sed -n '6p' | strip_cast)

COLLATERAL_USD=$(echo "scale=2; $TOTAL_COLLATERAL / 100000000" | bc)
DEBT_USD=$(echo "scale=2; $TOTAL_DEBT / 100000000" | bc)
AVAILABLE_USD=$(echo "scale=2; $AVAILABLE_BORROWS / 100000000" | bc)

MAX_UINT="115792089237316195423570985008687907853269984665640564039457584007913129639935"
if [ "$HEALTH_FACTOR_RAW" = "$MAX_UINT" ]; then
  HF="inf"
  HF_DISPLAY="∞ (no debt)"
else
  HF=$(echo "scale=4; $HEALTH_FACTOR_RAW / 1000000000000000000" | bc)
  HF_DISPLAY="$HF"
fi

if [ "$HEALTH_ONLY" = true ]; then
  if [ "$JSON_OUTPUT" = true ]; then
    echo "{\"healthFactor\": \"$HF\", \"collateralUsd\": \"$COLLATERAL_USD\", \"debtUsd\": \"$DEBT_USD\", \"availableBorrowsUsd\": \"$AVAILABLE_USD\"}"
  else
    echo "Health Factor: $HF_DISPLAY"
    echo "Collateral: \$$COLLATERAL_USD"
    echo "Debt: \$$DEBT_USD"
    echo "Available to borrow: \$$AVAILABLE_USD"
  fi
  exit 0
fi

# === Full Status ===
if [ "$JSON_OUTPUT" != true ]; then
  echo "=== Aave V3 Delegation Status ==="
  echo ""
  echo "--- Delegator: $DELEGATOR ---"
  echo "  Collateral:       \$$COLLATERAL_USD"
  echo "  Debt:             \$$DEBT_USD"
  echo "  Available borrow: \$$AVAILABLE_USD"
  echo "  Health factor:    $HF_DISPLAY"
  echo ""
  echo "--- Agent: $AGENT_ADDR ---"
  AGENT_BALANCE=$(cast balance "$AGENT_ADDR" --rpc-url "$RPC_URL")
  AGENT_ETH=$(cast from-wei "$AGENT_BALANCE")
  echo "  Native balance:   $AGENT_ETH"
  echo ""
fi

# === Per-Asset Delegation & Debt ===
if [ -n "$SYMBOL" ]; then
  ASSETS="$SYMBOL"
else
  ASSETS=$(jq -r '.assets | keys | .[]' "$CONFIG")
fi

JSON_ASSETS="[]"

for SYM in $ASSETS; do
  ASSET_ADDR=$(jq -r ".assets[\"$SYM\"].address" "$CONFIG")
  DECIMALS=$(jq -r ".assets[\"$SYM\"].decimals" "$CONFIG")
  
  if [ "$ASSET_ADDR" = "null" ] || [ -z "$ASSET_ADDR" ]; then
    echo "  ⚠ $SYM: not found in config"
    continue
  fi

  # Get debt token addresses
  TOKENS=$(cast call "$DATA_PROVIDER" \
    "getReserveTokensAddresses(address)(address,address,address)" \
    "$ASSET_ADDR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "")
  
  VAR_DEBT_TOKEN=$(echo "$TOKENS" | sed -n '3p' | strip_cast)

  # Delegation allowance
  ALLOWANCE_RAW=$(cast call "$VAR_DEBT_TOKEN" \
    "borrowAllowance(address,address)(uint256)" \
    "$DELEGATOR" "$AGENT_ADDR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "0")
  ALLOWANCE_RAW=$(echo "$ALLOWANCE_RAW" | strip_cast)
  ALLOWANCE=$(echo "scale=$DECIMALS; $ALLOWANCE_RAW / (10^$DECIMALS)" | bc)

  # Current debt on this asset
  DEBT_RAW=$(cast call "$VAR_DEBT_TOKEN" \
    "balanceOf(address)(uint256)" \
    "$DELEGATOR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "0")
  DEBT_RAW=$(echo "$DEBT_RAW" | strip_cast)
  DEBT=$(echo "scale=$DECIMALS; $DEBT_RAW / (10^$DECIMALS)" | bc)

  # Agent's token balance
  AGENT_TOKEN_RAW=$(cast call "$ASSET_ADDR" \
    "balanceOf(address)(uint256)" \
    "$AGENT_ADDR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "0")
  AGENT_TOKEN_RAW=$(echo "$AGENT_TOKEN_RAW" | strip_cast)
  AGENT_TOKEN=$(echo "scale=$DECIMALS; $AGENT_TOKEN_RAW / (10^$DECIMALS)" | bc)

  if [ "$JSON_OUTPUT" = true ]; then
    JSON_ASSETS=$(echo "$JSON_ASSETS" | jq --arg sym "$SYM" --arg allow "$ALLOWANCE" \
      --arg debt "$DEBT" --arg agent_bal "$AGENT_TOKEN" --arg vdt "$VAR_DEBT_TOKEN" \
      '. += [{"symbol": $sym, "allowance": $allow, "delegatorDebt": $debt, "agentBalance": $agent_bal, "variableDebtToken": $vdt}]')
  else
    echo "--- $SYM ---"
    echo "  Delegation allowance: $ALLOWANCE $SYM"
    echo "  Delegator debt:       $DEBT $SYM"
    echo "  Agent balance:        $AGENT_TOKEN $SYM"
    echo "  VariableDebtToken:    $VAR_DEBT_TOKEN"
    echo ""
  fi
done

if [ "$JSON_OUTPUT" = true ]; then
  jq -n \
    --arg hf "$HF" \
    --arg collateral "$COLLATERAL_USD" \
    --arg debt "$DEBT_USD" \
    --arg available "$AVAILABLE_USD" \
    --arg agent "$AGENT_ADDR" \
    --arg delegator "$DELEGATOR" \
    --argjson assets "$JSON_ASSETS" \
    '{
      delegator: $delegator,
      agent: $agent,
      healthFactor: $hf,
      collateralUsd: $collateral,
      debtUsd: $debt,
      availableBorrowsUsd: $available,
      assets: $assets
    }'
fi


path: aave-setup.sh
content: #!/usr/bin/env bash
# aave-setup.sh — Verify skill configuration and dependencies
set -euo pipefail

# Strip cast's bracket annotations e.g. "7920000000000000 [7.92e15]" → "7920000000000000"
strip_cast() { sed 's/ *\[.*\]//' | tr -d ' '; }

SKILL_DIR="${SKILL_DIR:-$HOME/.openclaw/skills/aave-delegation}"
CONFIG="$SKILL_DIR/config.json"

RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

ok()   { echo -e "${GREEN}✓${NC} $1"; }
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
fail() { echo -e "${RED}✗${NC} $1"; }

ERRORS=0

echo "=== Aave Delegation Skill Setup Check ==="
echo ""

# 1. Check dependencies
echo "--- Dependencies ---"
for bin in cast jq bc; do
  if command -v "$bin" &>/dev/null; then
    ok "$bin found: $(command -v "$bin")"
  else
    fail "$bin not found. Install it first."
    ERRORS=$((ERRORS + 1))
  fi
done

# 2. Check config file
echo ""
echo "--- Configuration ---"
if [ ! -f "$CONFIG" ]; then
  fail "Config not found at $CONFIG"
  echo "  Create it with: mkdir -p $SKILL_DIR && cp config.example.json $CONFIG"
  exit 1
fi
ok "Config found: $CONFIG"

# 3. Validate config fields
RPC_URL=$(jq -r '.rpcUrl // empty' "$CONFIG")
RPC_URL="${AAVE_RPC_URL:-$RPC_URL}"
if [ -z "$RPC_URL" ]; then
  fail "rpcUrl not set in config or AAVE_RPC_URL"
  ERRORS=$((ERRORS + 1))
else
  ok "RPC URL: $RPC_URL"
fi

AGENT_PK=$(jq -r '.agentPrivateKey // empty' "$CONFIG")
AGENT_PK="${AAVE_AGENT_PRIVATE_KEY:-$AGENT_PK}"
if [ -z "$AGENT_PK" ]; then
  fail "agentPrivateKey not set"
  ERRORS=$((ERRORS + 1))
else
  ok "Agent private key: configured (hidden)"
fi

DELEGATOR=$(jq -r '.delegatorAddress // empty' "$CONFIG")
DELEGATOR="${AAVE_DELEGATOR_ADDRESS:-$DELEGATOR}"
if [ -z "$DELEGATOR" ]; then
  fail "delegatorAddress not set"
  ERRORS=$((ERRORS + 1))
else
  ok "Delegator address: $DELEGATOR"
fi

POOL=$(jq -r '.poolAddress // empty' "$CONFIG")
POOL="${AAVE_POOL_ADDRESS:-$POOL}"
if [ -z "$POOL" ]; then
  fail "poolAddress not set"
  ERRORS=$((ERRORS + 1))
else
  ok "Pool address: $POOL"
fi

DATA_PROVIDER=$(jq -r '.dataProviderAddress // empty' "$CONFIG")
if [ -z "$DATA_PROVIDER" ]; then
  warn "dataProviderAddress not set (needed for debt token resolution)"
else
  ok "DataProvider address: $DATA_PROVIDER"
fi

# 4. Check RPC connectivity
echo ""
echo "--- Network ---"
if [ -n "$RPC_URL" ]; then
  CHAIN_ID=$(cast chain-id --rpc-url "$RPC_URL" 2>/dev/null || echo "")
  if [ -n "$CHAIN_ID" ]; then
    ok "RPC connected. Chain ID: $CHAIN_ID"
  else
    fail "Cannot connect to RPC at $RPC_URL"
    ERRORS=$((ERRORS + 1))
  fi
fi

# 5. Check agent wallet
echo ""
echo "--- Agent Wallet ---"
if [ -n "$AGENT_PK" ] && [ -n "$RPC_URL" ]; then
  AGENT_ADDR=$(cast wallet address "$AGENT_PK" 2>/dev/null || echo "")
  if [ -n "$AGENT_ADDR" ]; then
    ok "Agent address: $AGENT_ADDR"
    BALANCE=$(cast balance "$AGENT_ADDR" --rpc-url "$RPC_URL" 2>/dev/null || echo "0")
    BALANCE_ETH=$(cast from-wei "$BALANCE" 2>/dev/null || echo "0")
    if [ "$BALANCE" = "0" ]; then
      warn "Agent wallet has 0 native token — needs gas to execute transactions"
    else
      ok "Agent balance: $BALANCE_ETH ETH (native)"
    fi
  else
    fail "Invalid private key"
    ERRORS=$((ERRORS + 1))
  fi
fi

# 6. Check Pool contract
echo ""
echo "--- Aave Pool ---"
if [ -n "$POOL" ] && [ -n "$RPC_URL" ]; then
  # Check if pool is a contract
  CODE=$(cast code "$POOL" --rpc-url "$RPC_URL" 2>/dev/null || echo "0x")
  if [ "$CODE" = "0x" ] || [ -z "$CODE" ]; then
    fail "Pool address $POOL has no code — wrong address or wrong chain?"
    ERRORS=$((ERRORS + 1))
  else
    ok "Pool contract verified"
  fi
fi

# 7. Check delegation status for configured assets
echo ""
echo "--- Delegation Status ---"
if [ -n "$AGENT_PK" ] && [ -n "$RPC_URL" ] && [ -n "$DELEGATOR" ] && [ -n "$DATA_PROVIDER" ]; then
  AGENT_ADDR=$(cast wallet address "$AGENT_PK" 2>/dev/null)
  
  # Iterate configured assets
  for SYMBOL in $(jq -r '.assets | keys[]' "$CONFIG" 2>/dev/null); do
    ASSET_ADDR=$(jq -r ".assets[\"$SYMBOL\"].address" "$CONFIG")
    DECIMALS=$(jq -r ".assets[\"$SYMBOL\"].decimals" "$CONFIG")
    
    # Get debt token addresses
    TOKENS=$(cast call "$DATA_PROVIDER" \
      "getReserveTokensAddresses(address)(address,address,address)" \
      "$ASSET_ADDR" \
      --rpc-url "$RPC_URL" 2>/dev/null || echo "")
    
    if [ -z "$TOKENS" ]; then
      warn "$SYMBOL: Could not resolve debt token addresses"
      continue
    fi
    
    # Parse variable debt token (3rd return value)
    VAR_DEBT_TOKEN=$(echo "$TOKENS" | sed -n '3p' | strip_cast)
    
    if [ -z "$VAR_DEBT_TOKEN" ] || [ "$VAR_DEBT_TOKEN" = "0x0000000000000000000000000000000000000000" ]; then
      warn "$SYMBOL: No variable debt token found"
      continue
    fi
    
    # Check allowance
    ALLOWANCE_RAW=$(cast call "$VAR_DEBT_TOKEN" \
      "borrowAllowance(address,address)(uint256)" \
      "$DELEGATOR" "$AGENT_ADDR" \
      --rpc-url "$RPC_URL" 2>/dev/null || echo "0")
    
    ALLOWANCE_RAW=$(echo "$ALLOWANCE_RAW" | strip_cast)
    
    if [ "$ALLOWANCE_RAW" = "0" ]; then
      warn "$SYMBOL: No delegation allowance — delegator must call approveDelegation()"
    else
      ALLOWANCE=$(echo "scale=$DECIMALS; $ALLOWANCE_RAW / (10^$DECIMALS)" | bc 2>/dev/null || echo "$ALLOWANCE_RAW")
      ok "$SYMBOL: Delegation allowance = $ALLOWANCE $SYMBOL (DebtToken: $VAR_DEBT_TOKEN)"
    fi
  done
fi

# 8. Check delegator health
echo ""
echo "--- Delegator Health ---"
if [ -n "$POOL" ] && [ -n "$RPC_URL" ] && [ -n "$DELEGATOR" ]; then
  ACCOUNT_DATA=$(cast call "$POOL" \
    "getUserAccountData(address)(uint256,uint256,uint256,uint256,uint256,uint256)" \
    "$DELEGATOR" \
    --rpc-url "$RPC_URL" 2>/dev/null || echo "")
  
  if [ -n "$ACCOUNT_DATA" ]; then
    TOTAL_COLLATERAL=$(echo "$ACCOUNT_DATA" | sed -n '1p' | strip_cast)
    TOTAL_DEBT=$(echo "$ACCOUNT_DATA" | sed -n '2p' | strip_cast)
    AVAILABLE_BORROWS=$(echo "$ACCOUNT_DATA" | sed -n '3p' | strip_cast)
    HEALTH_FACTOR_RAW=$(echo "$ACCOUNT_DATA" | sed -n '6p' | strip_cast)
    
    # Values are in base currency (USD with 8 decimals)
    COLLATERAL_USD=$(echo "scale=2; $TOTAL_COLLATERAL / 100000000" | bc 2>/dev/null || echo "?")
    DEBT_USD=$(echo "scale=2; $TOTAL_DEBT / 100000000" | bc 2>/dev/null || echo "?")
    AVAILABLE_USD=$(echo "scale=2; $AVAILABLE_BORROWS / 100000000" | bc 2>/dev/null || echo "?")
    
    if [ "$HEALTH_FACTOR_RAW" = "115792089237316195423570985008687907853269984665640564039457584007913129639935" ]; then
      HF="∞ (no debt)"
    else
      HF=$(echo "scale=4; $HEALTH_FACTOR_RAW / 1000000000000000000" | bc 2>/dev/null || echo "?")
    fi
    
    ok "Collateral: \$$COLLATERAL_USD"
    ok "Debt: \$$DEBT_USD"
    ok "Available borrows: \$$AVAILABLE_USD"
    ok "Health factor: $HF"
  else
    warn "Could not read delegator account data"
  fi
fi

# Summary
echo ""
echo "=== Summary ==="
if [ "$ERRORS" -eq 0 ]; then
  ok "All checks passed. Skill is ready."
else
  fail "$ERRORS error(s) found. Fix them before using the skill."
  exit 1
fi


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)