Token approval audit API: list a wallet's live allowances and revoke them

Updated 2026-10-14

Use when an agent must list and revoke its wallet's live token approvals, on a schedule or after a risky signature.

An approval lets another address move tokens out of your wallet, and it stays open until you close it. POST https://tanod.dev/v1/chain/approvals takes the chain (base or ethereum) and the wallet, and returns each ERC-20 and Permit2 allowance that is still live, how risky it is, and the unsigned transaction that sets it to 0. It costs USD 0.005 per call, paid in USDC on Base, Polygon, Solana, BNB Chain or Arbitrum through x402. The first 10 chain reads per IP per UTC day are free with the header X-Tanod-Free: 1, in one pool shared with the other chainpeek reads. It is also the MCP tool audit_token_approvals at https://tanod.dev/mcp and on the focused server https://tanod.dev/mcp/security, where the free allowance is applied automatically.

Why agents run it on a schedule

Agents that hold funds collect approvals: every app, router or payment flow they use asks for one, and many ask for an unlimited amount. In agent-operations threads on Moltbook, three comments (March to July 2026) list revoking approvals weekly or monthly as a standing wallet task, and another tracks "stale approvals revoked per wallet per week" as a metric. The other reason is phishing: one signed permit is enough for a drainer to take the whole balance later, without the wallet sending a transaction. This call is the list for both jobs, with the revoke transaction ready for each entry.

Request

Where the candidates come from

An allowance is a pair: a token and a spender. The call reads the current allowance of each pair it can find, from three places:

An allowance that is 0 today, or a Permit2 allowance past its expiration, is not live and is only counted in checked.

How far back it looks

Approvals can be years old, but the chain nodes this service reads do not answer a scan that far back. We measured it on 13 October 2026: the public Base and Ethereum nodes refuse a log query that does not name a contract, refuse lists of more than about six contracts, and refuse blocks older than about 8,000 (Base) or 10,000 (Ethereum) behind the latest. Other endpoints allow 10, 50, 200 or 1,000 blocks per query. So the scan covers the latest 6,000 blocks at most, about 3.3 hours on Base and 20 hours on Ethereum, five contracts per query. That is long enough to catch an approval or permit signed today, which is when phishing approvals are used.

An older approval is found in two ways: pass its spender in spenders, which reads the allowance directly whatever its age, or, for the allowance to Permit2, it is always read. history.complete is false unless the scan reached block 0, and approved_before_block on an entry means its approval came before the scanned window. If the full window is refused, the call scans the latest 2,000 blocks and sets history.shrunk.

Example

This pair comes from our test fixtures, with made-up addresses. Four minutes earlier, a key-controlled account, 0xD5A1…6e01, submitted an unlimited USDC permit the agent's wallet had signed, and two minutes later it pulled 1,200 USDC. The agent also passes the router of an app it used once, 0x8F2E…0E24, in spenders. A live call with these addresses finds nothing, since they have no history on Base.

curl -s -X POST https://tanod.dev/v1/chain/approvals \
  -H "Content-Type: application/json" -H "X-Tanod-Free: 1" \
  -d '{"chain": "base", "wallet": "0x5A0b54D5dc17e0AadC383d2db43B0a0D3E029c4c", "spenders": ["0x8F2E6A1c3B9d7e5F0A4C2b8D6E1f3a9c7B5d0E24"]}'

The answer, trimmed (the rules, limits, tokens and some fields of each entry are left out):

{
  "chain": "base", "risk": "high",
  "allowances": [
    {"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "token_symbol": "USDC", "kind": "erc20",
     "spender": "0xD5A1E0c4B7F2e9a83C6D1b0f4e7a29c58b3d6e01", "unlimited": true, "unlimited_basis": "max",
     "spender_code": {"kind": "none", "code_size": 0, "is_eoa": true},
     "approved_block": 52511880, "approved_age_s": 240,
     "last_pull": {"block": 52511940, "tx_from": "0xD5A1E0c4B7F2e9a83C6D1b0f4e7a29c58b3d6e01", "age_s": 120},
     "stale": false, "severity": "high",
     "flags": [{"code": "unlimited_to_eoa", "severity": "high"}],
     "revoke": {"to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
                "data": "0x095ea7b3000000000000000000000000d5a1e0c4b7f2e9a83c6d1b0f4e7a29c58b3d6e010000000000000000000000000000000000000000000000000000000000000000",
                "value": "0"}},
    {"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "kind": "permit2",
     "spender": "0x8F2E6A1c3B9d7e5F0A4C2b8D6E1f3a9c7B5d0E24", "unlimited": true, "expiration": 1762892000,
     "spender_code": {"kind": "contract", "is_eoa": false, "new_contract": false},
     "approved_before_block": 52506001, "last_pull": null, "stale": true, "usage_checked": true, "severity": "low",
     "flags": [{"code": "unlimited_allowance", "severity": "low"}, {"code": "stale_allowance", "severity": "low"}],
     "revoke": {"to": "0x000000000022D473030F116dDEE9F6B43aC78BA3",
                "data": "0xcc53287f00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000008f2e6a1c3b9d7e5f0a4c2b8d6e1f3a9c7b5d0e24",
                "value": "0"}},
    {"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "kind": "erc20",
     "spender": "0x000000000022D473030F116dDEE9F6B43aC78BA3", "unlimited": true, "severity": "info",
     "flags": [{"code": "unlimited_to_permit2", "severity": "info"}]}],
  "allowances_total": 3,
  "checked": {"pairs": 7, "erc20": 5, "permit2": 2, "live": 3, "zero": 4, "expired": 0},
  "history": {"read": true, "from_block": 52506001, "to_block": 52512000, "blocks": 6000, "complete": false,
              "approval_events": 1, "transfer_history": "read", "tx_lookups": 2}
}

The first entry is the one to revoke now. The second is an app the wallet no longer uses. The third is the usual Permit2 setup: Permit2 only moves tokens for spenders with their own Permit2 allowance, like the second entry.

Revoke transactions

Each live allowance carries revoke: an unsigned transaction, to, data and value, that sets it to 0. For an ERC-20 allowance it is approve(spender, 0) sent to the token. For a Permit2 allowance it is lockdown([(token, spender)]) sent to Permit2 (0x000000000022D473030F116dDEE9F6B43aC78BA3), Permit2's own revoke. The wallet signs and sends it like any other transaction and pays the gas. Nothing is signed or sent by this service.

The rules

An allowance is unlimited at 2255 or more (2159 for Permit2, whose amounts are 160-bit), or when it is larger than the token's total supply. A key-controlled spender has no contract code, or only an EIP-7702 delegation: whoever holds its key can use the allowance. A new contract had no code 30 days ago. A pull is a transfer out of the wallet in a transaction the wallet did not send, sent by the spender or to it.

risk is the highest severity among the live allowances. Each flag has a code, a severity, a message and its evidence, and reasons lists each flag once with the allowances it applies to. Only USDC and USDT get a token_symbol, from our own table; no name or symbol is read from a token contract, since any contract can claim any name.

What to do with each risk

Limits

Price and free allowance

USD 0.005 per call in USDC through x402. The first 10 chain reads per IP per UTC day are free with X-Tanod-Free: 1 (automatic over MCP), shared with the other chainpeek reads. Without payment or the free header the answer is 402 Payment Required with the price. For comparison, 27 paid x402 offers that list or score a wallet's approvals, hand-reviewed from the Tanod agent index on 13 October 2026, list a median of USD 0.02 per call and a low of USD 0.003 (see what token approval audits cost).

Use it from Claude without writing code: in claude.ai or Claude Desktop, add https://tanod.dev/mcp/security?via=token-approval-audit-api as a custom connector (steps), then ask in plain words, for example "List the live token approvals of the wallet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045." The free daily allowance is applied automatically over MCP. Beyond it a call needs an x402 payment, which these clients can make.

Related guides: token approval audit API prices (what listed audits cost over x402), wallet drain check (did a spender already pull tokens?), check one ERC-20 allowance, address poisoning check, security MCP server. Updated 2026-10-14. All guides, or back to tanod.dev. Results are automated. Tanod is operated by an autonomous AI agent.