Token Approval Management System Development (Revoke)
We've encountered a situation: a user has hundreds of unused token approvals, one of the protocols gets hacked — and the wallet is drained. Without an approval management system, any compromised protocol can drain your funds. Our team develops custom solutions for viewing and revoking token approvals — from a simple single-chain interface to a multi-chain system with risk-scoring and batch revocation. Such development takes from 2 to 5 days depending on complexity, and we do it turnkey: from data analysis to deployment. Contact us for a project evaluation.
Technical Basics: How Approvals Work
ERC-20 Allowance
The ERC-20 standard defines allowance(owner, spender) — how many tokens the spender can spend on behalf of the owner. It is set via approve(spender, amount). A value of type(uint256).max (2^256-1) means "unlimited" — most protocols require this for convenience.
Problem: allowance has no expiration. There is no automatic revocation mechanism. If a protocol is compromised a year after your approve — the allowance is still active.
ERC-721 and ERC-1155 Approvals
For NFTs, there are two types of approvals:
-
approve(operator, tokenId)— permission for a specific token -
setApprovalForAll(operator, true)— full access to the entire collection
setApprovalForAll is used by OpenSea, blur.io, and other marketplaces. This is the most dangerous type — one hacked marketplace with an active setApprovalForAll equals a lost collection.
EIP-2612: Permit
permit(owner, spender, value, deadline, v, r, s) — signature instead of a transaction. It does not create a permanent allowance, works once with a specific deadline. Well-designed dApps use permit instead of approve.
But permit has a nuance: if DAI, USDC, or another token supports permit — the allowance through permit can still be viewed via allowance(). They are indistinguishable from regular approve.
Reading Approval Data
Via Approval Event
A direct call to allowance(owner, spender) requires knowing the spender address. To get all active approvals for a wallet — you need to read events:
import { createPublicClient, http, parseAbi } from 'viem'; const ERC20_ABI = parseAbi([ 'event Approval(address indexed owner, address indexed spender, uint256 value)', 'function allowance(address owner, address spender) view returns (uint256)', 'function symbol() view returns (string)', 'function decimals() view returns (uint8)', ]); async function getTokenApprovals(ownerAddress: `0x${string}`) { const client = createPublicClient({ chain: mainnet, transport: http(RPC_URL) }); const approvalLogs = await client.getLogs({ event: ERC20_ABI[0], args: { owner: ownerAddress }, fromBlock: 0n, toBlock: 'latest' }); const latestApprovals = new Map<string, typeof approvalLogs[0]>(); for (const log of approvalLogs) { const key = `${log.address}-${log.args.spender}`; latestApprovals.set(key, log); } const results = await Promise.all( Array.from(latestApprovals.values()).map(async (log) => { const [allowance, symbol, decimals] = await Promise.all([ client.readContract({ address: log.address, abi: ERC20_ABI, functionName: 'allowance', args: [ownerAddress, log.args.spender!] }), client.readContract({ address: log.address, abi: ERC20_ABI, functionName: 'symbol' }), client.readContract({ address: log.address, abi: ERC20_ABI, functionName: 'decimals' }), ]); return { tokenAddress: log.address, spenderAddress: log.args.spender!, allowance, symbol, decimals, isUnlimited: allowance === BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'), }; }) ); return results.filter(r => r.allowance > 0n); } Problem with Historical Data
getLogs with fromBlock: 0n is slow and expensive for public RPCs. Solutions:
- The Graph: index Approval events via a subgraph, instant GraphQL queries
- Etherscan/Alchemy API: ready endpoints for token approvals (
alchemy_getTokenAllowances) - Incremental indexing: track the last indexed block, request only new events on each update
For production systems, The Graph subgraph is the optimal solution. One query returns all active approvals with metadata.
Why Batch Revoke is a Complex Task?
Revoking 10 approvals via ERC-20 requires 10 separate transactions, each with the user's signature. This is unacceptable for UX. Multicall doesn't help because approve is an operation on behalf of the user, requiring a signature. Solution — a transaction queue with automatic continuation after confirmation. The user signs each one but sees progress "Revoking 3 of 8…". Alternatively, using Permit2 batch revoke, if the user has migrated their approvals to Permit2 — this reduces gas costs by up to 70%.
How Token Revocation is Done?
ERC-20 Revoke
Revoke = approve(spender, 0). One transaction per token+spender pair.
async function revokeERC20Approval( tokenAddress: `0x${string}`, spenderAddress: `0x${string}` ) { const { writeContract } = useWriteContract(); writeContract({ address: tokenAddress, abi: erc20Abi, functionName: 'approve', args: [spenderAddress, 0n] }); } ERC-721 / ERC-1155 Revoke
setApprovalForAll(operator, false) — revoke full access to the collection. More critical, so highlighted in red in the UI. approve(operator, tokenId) followed by revoke is less critical — access to a specific token.
UI Design of the System
The main component is a table with sorting and filtering:
| Token | Spender | Allowance | Risk | Action |
|---|---|---|---|---|
| USDC | Uniswap V3 | Unlimited | Medium | Revoke |
| WETH | Old Protocol (deprecated) | Unlimited | High | Revoke |
| DAI | Aave V3 | 1,000 DAI | Low | Revoke |
Risk scoring is an important UX feature. Spender addresses are identified via:
- Etherscan Labels API
- DefiLlama protocol database
- Custom whitelist of known protocols
Verified protocol = medium risk (approval exists, but protocol is reliable). Unknown contract = high risk. Deprecated/dead contract = critical risk.
Filters: by network, by type (ERC-20 / NFT), by risk level, only unlimited approvals.
Comparison of Data Retrieval Approaches
| Approach | Speed | Complexity | Gas for Queries |
|---|---|---|---|
| RPC Events | Slow | Low | High (many calls) |
| The Graph | 10x faster | Medium (subgraph needed) | Zero (GraphQL) |
| Alchemy API | Fast | Low (ready endpoint) | Subscription based |
What's Included in Our Work
- UI interface development (React + wagmi + TanStack Table) with approval table
- Integration with chosen data source (RPC, The Graph, Alchemy)
- Implementation of batch revocation via transaction queue
- Multi-chain support (Ethereum, Arbitrum, Polygon, Base, BNB Chain)
- Risk-scoring based on whitelist and external APIs
- API documentation and user instructions
- Deployment on your domain or white-label
Company in Numbers
Over 5 years of experience in blockchain development, 30+ implemented projects, including DeFi protocols and NFT marketplaces. Our team consists of senior engineers with deep knowledge of Solidity, Rust (Solana), and TypeScript.
Estimated Timeframes
Basic system for one network (ERC-20, RPC Events) — from 2 days. Multi-chain with NFT and The Graph — from 5 days. Exact cost is calculated individually — contact us for a consultation.







