Security tool that scans a wallet for active ERC-20 token approvals across 12 EVM chains. Surfaces unlimited-allowance grants that could drain you in a single transaction if the spender is ever compromised.
npx token-approval-checker 0xYourAddressOr clone:
git clone https://github.com/swiftnodes/token-approval-checker
cd token-approval-checker
cp .env.example .env # add your SwiftNodes API key
npm install
node src/cli.js 0xYourAddressSample output:
=== Ethereum === 4 active approval(s)
⚠️ USDC -> 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 UNLIMITED
token: https://etherscan.io/address/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
last: https://etherscan.io/tx/0xdeadbeef...
⚠️ USDT -> 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45 UNLIMITED
...
LINK -> 0x111111125421cA6dc452d289314280a0f8842A65 1,000 LINK
...
=== Base === 1 active approval(s)
⚠️ USDC -> 0xcF77a3Ba9A5CA399B7c97c74d54e5b1Beb874E43 UNLIMITED
...
Summary: 5 active approval(s), 3 effectively unlimited.
⚠️ Unlimited approvals can drain your wallet in a single tx if the spender is compromised.
Revoke via https://revoke.cash or directly call `approve(spender, 0)` on each token.
When you click "Approve USDC" in a dApp, you're often granting an unlimited allowance. That dApp's contract can then move any amount of your USDC at any time — forever, unless you revoke.
Real risk:
- If a protocol gets hacked → drained immediately
- If a protocol is upgraded to a malicious version → drained
- If you stop using a dApp → the approval stays active indefinitely
- Phishing dApps trick you into approving them → drained on next interaction
Most users have dozens of stale unlimited approvals across multiple chains. This tool finds them all in one shot.
- ✅ 12 chains scanned in parallel — Ethereum, Base, Arbitrum, Optimism, Polygon, BSC, Avalanche, Scroll, Linea, zkSync Era, Blast, Mantle
- ✅ Verifies current allowance on-chain — not just historical events. Approvals that have been consumed are filtered out.
- ✅ Flags "effectively unlimited" (allowance > 10⁶⁰) — the dangerous category
- ✅ Sorted by risk — unlimited first, then by amount
- ✅ Direct explorer links for token and last approval tx
- ✅
--jsonfor piping into a database or alert script - ✅
--unlimited-onlyfilter for quick risk-focused audit - ✅ Custom lookback window via
--blocks N - ✅ Single-chain scans via
--chain eth
- Query
Approval(owner=YOU, spender, value)event logs across a lookback window per chain. - Group by (token, spender) — only the latest event matters (later approvals override earlier).
- Drop revoked approvals (
value = 0). - Verify current on-chain allowance for each remaining (token, spender) pair via
eth_call. This filters out approvals that have been consumed bytransferFromsince the event. - Flag anything with allowance > 10⁶⁰ as "effectively unlimited."
The verify-on-chain step is critical: many tools just look at event history and report stale approvals as active. This one actually checks.
swiftnodes.io — free, no KYC.
cp .env.example .env
# edit: SWIFTNODES_API_KEY=sn_...npm install
node src/cli.js 0xYourAddress
# Common variations:
node src/cli.js 0xYourAddress --unlimited-only # only risky ones
node src/cli.js 0xYourAddress --chain base # one chain
node src/cli.js 0xYourAddress --json > scan.json # pipeable
node src/cli.js 0xYourAddress --blocks 100000 # quicker but less historyFor each unlimited approval to a contract you no longer use or trust:
- revoke.cash — handles all major EVM chains
- unrekt.net — alternative
- Each chain's official block explorer often has a "token approvals" page
Call approve(spender, 0) on the token contract via Etherscan's "Write Contract" UI, or sign the tx with viem/ethers:
import { createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { mainnet } from "viem/chains";
const account = privateKeyToAccount("0x...");
const wallet = createWalletClient({ account, chain: mainnet, transport: http("https://...") });
await wallet.writeContract({
address: "0x...token...", // the token contract
abi: [{
name: "approve",
type: "function",
stateMutability: "nonpayable",
inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" },
],
outputs: [{ name: "", type: "bool" }],
}],
functionName: "approve",
args: ["0x...spender...", 0n],
});Each revoke costs gas (~$0.50-$5 on Ethereum, pennies on L2s). Not free but cheap insurance.
A full 12-chain scan takes 30-90 seconds depending on lookback window and number of historical approvals. Each chain is scanned in parallel; per-chain time is dominated by getLogs and the post-scan allowance verification calls.
If a chain has too much history to scan in one shot, reduce --blocks or override defaultLookback per chain in src/chains.js.
- Only ERC-20 approvals — not ERC-721/1155
setApprovalForAll. NFT approvals are equally dangerous; revoke.cash handles those. - Approval2-style routers (Uniswap Permit2, etc.) use a different mechanism — they don't always emit standard
Approvalevents for the underlying token. Check Permit2 separately. - Default lookback is conservative — you may miss approvals older than ~1-2 years on chains with high block production. Increase
--blocksor run a full scan if you've been active for longer. - No private keys — this is a read-only scan. To actually revoke, use a wallet UI or sign txs yourself.
- Wallet hygiene — periodic audit of your own wallets
- Security incident response — if a protocol gets hacked, find users who had approvals to it
- DeFi onboarding — scan a freshly-airdrop-farmed wallet before pulling funds
- For wallet builders — integrate this scan as a wallet security feature
This tool fires getLogs and allowance calls across 12 chains. Most providers either:
- Limit
getLogsblock-range aggressively (you'd need many sequential calls) - Don't support some chains here (Blast, Mantle, zkSync)
- Charge per-request
SwiftNodes covers all 12 chains under one flat-rate API key with generous getLogs windows.
MIT