What this tool does
The Token Allowance Checker reads a single, targeted ERC-20 approval: how much a spender address is allowed to move from an owner wallet’s balance. It resolves allowance(owner, spender) and decimals() in one call and returns the result as a decimal-formatted number, with unlimited and zero approvals called out explicitly so you don’t have to guess what a huge integer means.
How to use it
- Paste the ERC-20 token contract address, the owner wallet, and the spender address into the form above.
- Pick the chain the token and approval live on.
- Run the query and read the result: the exact allowance, formatted with the token’s own decimals, plus a status flag for unlimited or zero approvals.
A result of zero is a valid, meaningful answer. It means the spender currently has no approval to move any of the owner’s tokens, which is common right after a revoke, or before a first-time approval has ever been granted.
What is happening under the hood
cel.bind(a, token.allowance(owner, spender),
cel.bind(d, token.decimals(), {
"allowance": string(formatUnits(a, d)),
"symbol": token.symbol()
})
)
token.allowance(owner, spender)reads the raw integer allowance the owner has granted to the spender.ownerandspenderare runtime context variables, declared assol_addressin the query schema and supplied as the wallet and spender addresses you enter in the form.cel.bind(a, …)reads the allowance once and reuses it, so it is only fetched a single time even though it appears in the formatted output.token.decimals()is read once viacel.bind(d, …)and used to scale the raw allowance into a human-readable number.formatUnits(a, d)divides the raw integer by10 ** decimalsserver-side. It returns a double, so the result is wrapped instring(...)before it goes into the map, since CEL maps require every value to share a type andsymbol()is already a string.- Both reads are auto-batched by Multicall3, so the whole check costs one round trip regardless of how large the numbers are.
Build this yourself
Each of the snippets below makes the same POST call to the evmquery REST API. The example checks how much of USDC vitalik.eth’s wallet has approved Uniswap’s Permit2 contract to spend, on Ethereum.
REST (curl)
curl -X POST https://api.evmquery.com/api/v1/query \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain": "evm_ethereum",
"schema": {
"contracts": { "token": { "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" } },
"context": { "owner": "sol_address", "spender": "sol_address" }
},
"expression": "cel.bind(a, token.allowance(owner, spender), cel.bind(d, token.decimals(), { \"allowance\": string(formatUnits(a, d)), \"symbol\": token.symbol() }))",
"context": {
"owner": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"spender": "0x000000000022D473030F116dDEE9F6B43aC78BA3"
}
}'
Python
import requests
resp = requests.post(
"https://api.evmquery.com/api/v1/query",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"chain": "evm_ethereum",
"schema": {
"contracts": {"token": {"address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"}},
"context": {"owner": "sol_address", "spender": "sol_address"},
},
"expression": (
"cel.bind(a, token.allowance(owner, spender),"
" cel.bind(d, token.decimals(), {"
' "allowance": string(formatUnits(a, d)),'
' "symbol": token.symbol()'
" }))"
),
"context": {
"owner": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
"spender": "0x000000000022D473030F116dDEE9F6B43aC78BA3",
},
},
timeout=10,
)
print(resp.json()["result"])
TypeScript
const resp = await fetch("https://api.evmquery.com/api/v1/query", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.EVMQUERY_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
chain: "evm_ethereum",
schema: {
contracts: { token: { address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" } },
context: { owner: "sol_address", spender: "sol_address" },
},
expression:
'cel.bind(a, token.allowance(owner, spender), cel.bind(d, token.decimals(), { "allowance": string(formatUnits(a, d)), "symbol": token.symbol() }))',
context: {
owner: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
spender: "0x000000000022D473030F116dDEE9F6B43aC78BA3",
},
}),
});
const { result } = await resp.json();
The free tier has no monthly cap. Get a free API key to drop these snippets into your project.
When you would use this
- Auditing a wallet’s active approvals. Before signing a new transaction, confirm exactly how much an existing spender can already move, instead of trusting a wallet UI’s rough estimate.
- Verifying a revoke worked. After sending a
approve(spender, 0)transaction, check the allowance reads back as zero. - Security reviews and audits. Confirm a contract’s approval scope matches what the documentation claims before recommending it to users.
- Support and incident response. Quickly check whether a specific spender flagged in a phishing report actually has an active approval on a given wallet.
FAQ
What is an allowance (approval)?
An ERC-20 allowance is a permission an owner wallet grants to a spender address, letting the spender move up to a set amount of tokens from the owner’s balance without a separate signature each time. Approvals power almost every DeFi interaction: swaps, lending deposits, and Permit2-style routers all rely on an allowance being in place first.
What does an unlimited approval mean, and why is it a risk?
Many dApps request an approval for the maximum possible integer instead of an exact amount, so you don’t have to re-approve on every transaction. That convenience comes with risk: if the spender contract is compromised or malicious, it can drain the full token balance, not just what you intended to spend. This tool flags allowances above roughly 1e30 as unlimited so you can decide whether to trust the spender or revoke it.
Can this tool revoke an approval?
No. This tool only reads the current allowance, it cannot change it. Revoking an approval requires sending a transaction from the owner wallet, either by calling approve(spender, 0) on the token contract directly or through a dedicated revocation tool like revoke.cash.
Which chains are supported?
The checker currently runs against Ethereum, Base, and BNB Smart Chain. Additional EVM chains are being added on the evmquery backend. If you need a specific chain prioritized, contact support.
What is the difference between allowance and balance?
Balance is how many tokens a wallet owns. Allowance is how many tokens a specific spender is permitted to move out of that wallet on the owner’s behalf. A wallet can have a large balance and zero allowance for a given spender, or a large allowance for a spender it has never actually sent tokens to.
Can I use this tool from my own application?
Yes. The same expression and addresses work against the public REST API. The free tier has no monthly cap, and a single check counts as one read because Multicall3 batches the underlying calls into one round trip. Bring your own API key and the rate limit applied to this page no longer applies.
Limits and accuracy
- This tool reads a single, targeted allowance for the owner and spender you supply. It does not scan a wallet for every approval it has ever granted, that requires indexing historical
Approvalevents across every token contract, which is out of scope for a single on-chain read. - The result reflects the latest block at the time of the read. There is no historical replay.
- The demo is rate limited per browser. If you hit the limit, grab a free API key and the limit goes away.
- Some tokens implement non-standard allowance behavior (for example, requiring the allowance to be reset to zero before it can be changed). This tool reports whatever the contract currently returns, it does not simulate a write.
Related
- Scan ERC-20 balances across many wallets from TypeScript — extend the same pattern to read balances across many wallets in one call
- Multicall3 batching for EVM contract reads — why one expression can replace dozens of
eth_callround trips - Read smart contracts in n8n — wire the same query into a no-code workflow
- evmquery’s free tools — try it yourself