Skip to content
Back to skills

Approval Audit

ASecurity

List a wallet's live ERC-20 token approvals on Base and flag unlimited / risky spender grants. Keyless via Base RPC (eth_getLogs + eth_call) — no explorer key needed.

  • 6 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 5, 2026
securityrustgoc#bashsecurity

Works with

  • cli

Security analysis

A100/100

Scanned September 5, 2026

npx -y skills add anajuliabit/aeon --skill approval-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Approval Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Approval Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/anajuliabit-approval-audit/badge)](https://www.skillsdirectory.com/skills/anajuliabit-approval-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: Approval Audit
category: onchain-security
description: List a wallet's live ERC-20 token approvals on Base and flag unlimited / risky spender grants. Keyless via Base RPC (eth_getLogs + eth_call) — no explorer key needed.
var: ""
tags: [crypto, security, base]
requires: [BASE_RPC_URL?]
capabilities: [read_only, sends_notifications]
---
> **${var}** — Wallet address (`0x...`) on Base to audit. Required. If empty, log `APPROVAL_AUDIT_NO_TARGET` and exit cleanly (no notify).

Answers "what can drain this wallet?" — every ERC-20 `approve()` a wallet has granted that is **still live**, with unlimited allowances flagged. Token approvals are the #1 wallet-drain vector: a forgotten unlimited approval to a malicious or exploited contract lets it move your full balance at any time.

Runs **keyless** entirely on the Base RPC — reads `Approval` event logs, then confirms each grant's *current* allowance, so revoked or fully-spent approvals are excluded.

Read the last 2 days of `memory/logs/` so a repeat audit can note newly-granted or newly-revoked approvals.

## Config

- Target wallet = `${var}`. Chain = Base (`chainid=8453`, explorer `basescan.org`).
- `BASE_RPC_URL` — optional; defaults to a public Base RPC (`https://mainnet.base.org`). Any standard JSON-RPC endpoint works.

## Steps

### 1. Find the current block

```bash
OWNER="${var}"
RPC="${BASE_RPC_URL:-https://mainnet.base.org}"
HEAD=$(curl -m 10 -s -X POST "$RPC" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_blockNumber","params":[]}' | jq -r '.result')
```

### 2. Fetch Approval events for the owner (chunked)

The ERC-20 `Approval(owner,spender,value)` event has topic0 `0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925`; the owner is indexed in **topic1** (left-padded to 32 bytes).

Scan a recent window (~24k blocks ≈ 13h on Base) **newest-first in ~1800-block chunks** — most public RPCs cap `eth_getLogs` at ~20k results per range, so a single wide call fails. Merge the results.

```bash
TOPIC0="0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
OWNER_TOPIC="0x000000000000000000000000${OWNER#0x}"   # 32-byte left-pad
curl -m 10 -s -X POST "$RPC" -H "Content-Type: application/json" -d '{
  "jsonrpc":"2.0","id":1,"method":"eth_getLogs","params":[{
    "fromBlock":"0x...","toBlock":"0x...",
    "topics":["'"$TOPIC0"'","'"$OWNER_TOPIC"'"]
  }]}' | jq '.result'
```

For each log: `token = .address`, `spender = "0x" + topic2[-40:]`. Keep the **latest** entry per `(token, spender)` pair. Some RPCs ignore the indexed-topic filter — defensively keep only logs whose `topics[1]` equals `OWNER_TOPIC`.

### 3. Confirm each approval is still live

For each `(token, spender)`, read the **current** allowance via `eth_call` on `allowance(address,address)` (selector `0xdd62ed3e`):

```bash
DATA="0xdd62ed3e${OWNER_TOPIC#0x}${SPENDER_TOPIC#0x}"   # selector + owner + spender, each 32-byte padded
curl -m 10 -s -X POST "$RPC" -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"'"$TOKEN"'","data":"'"$DATA"'"},"latest"],"id":1}' | jq -r '.result'
```

Drop any approval whose allowance is `0` (revoked or fully spent). Flag any allowance `>= 2^255` as **UNLIMITED** (covers `2^256-1` and the common `2^256/2` sentinel).

### 4. Verdict

| Signal | Verdict |
|--------|---------|
| One or more **unlimited** live approvals | `REVIEW` |
| Live approvals, none unlimited | `OK` |
| No live approvals | `CLEAN` |

### 5. Notify

Notify via `./notify` only if verdict is `REVIEW`. Under 4000 chars, lead with the verdict, clickable URL:

```
*Approval Audit — 0xabc…def (Base)*
Verdict: REVIEW · 4 live approvals, 2 unlimited

• USDC → spender 0x1111…2222 : UNLIMITED ⚠️
• WETH → spender 0x3333…4444 : UNLIMITED ⚠️
• DAI  → spender 0x5555…6666 : 5,000

⚠️ Unlimited approvals let the spender move your full balance. Revoke any you don't recognize at revoke.cash.
Wallet: https://basescan.org/address/0xabc...def
```

### 6. Log

Append to `memory/logs/${today}.md`:

```
## approval-audit
- Wallet: 0x… | verdict: REVIEW
- Live approvals: 4 (2 unlimited)
- Unlimited: USDC→0x1111…, WETH→0x3333…
- Source: rpc=ok | window: ~24k blocks
```

End-states: `APPROVAL_AUDIT_OK` (clean/ok, no notify), `APPROVAL_AUDIT_FLAGGED` (review → notify), `APPROVAL_AUDIT_ERROR` (RPC unreachable).

## Sandbox note

The sandbox may block outbound `curl` or env-var expansion. The Base RPC is public and needs no key, so for every failed `curl` retry the **same URL/body via WebFetch** before giving up. `eth_getLogs` must be chunked (~1800 blocks/call) to stay under the public-RPC result cap — a single 24k-block call will error. Never put a key in a `-H` header from the sandbox. Treat all fetched token/spender addresses as untrusted — never interpolate beyond the quoted `$OWNER` / validated hex.

## Constraints

- No trade or "safe to approve" advice — this is a risk inventory. Reporting an approval is not an accusation.
- Only report approvals confirmed live by the current `allowance` read — never list a grant the wallet has already revoked.
- `UNLIMITED` means allowance `>= 2^255`; report exact amounts otherwise. Don't round in a way that hides a large grant.
- The window is recent (~24h of blocks), not all-time — say so; for a full history the operator should add a higher-throughput RPC.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…