ERC-20 Approvals: Setting and Revoking Spending Caps
A practical guide to ERC-20 approvals: setting bounded spending caps, sizing allowances, and revoking token permissions for secure onchain engineering.

If you have built anything with ERC-20 tokens, you have dealt with approvals. You grant a contract permission to spend tokens on your behalf. But how much, and for how long? A bounded authorization sets a cap: the contract can only spend up to a specific amount. This guide covers how ERC-20 approvals work, how to choose a cap, and how to revoke or reduce an allowance.
How ERC-20 Approvals Work
When you call approve on an ERC-20 token, you authorize a spender (another address, typically a contract) to move up to a certain amount of tokens from your wallet. The allowance is stored in the token contract itself. The spender can call transferFrom to move tokens, subject to the cap you set. No tokens are moved during approval. The approval is an onchain record: a permission, not a payment.
The Key Methods
approve(spender, amount): Set or overwrite the allowanceallowance(owner, spender): Check the current allowancetransferFrom(from, to, amount): Spend up to the allowance
You call approve from your wallet. The spender is the contract you want to interact with. The amount is the maximum they can pull.
What Is a Bounded Authorization?
A bounded authorization is an approval with a fixed limit. It is not an unlimited approval. The cap is explicit, and you can see it in the transaction data. Only up to that amount can be spent by the authorized contract. This is the only type of approval used by Reclack.
Why Not Unlimited Approvals?
Unlimited approvals (2^256 - 1) are common, but they carry risk. If the contract has a bug or is compromised, your whole balance is at risk. A bounded approval limits the potential loss to the cap you set, even if the spender is malicious or broken.
Sizing a Spending Cap
Choosing the right cap depends on your use case. If you are authorizing a recurring payment, the cap should cover the largest expected charge within the authorization period. You do not need to include your whole balance or a large multiple. Only what the contract should be able to pull before you review or reauthorize.
Example: Subscription Billing
Suppose you want to allow a contract to collect your monthly SaaS fee in USDC. The fee is $50 per month. You want to set a cap that covers a single payment cycle, giving you time to react if something changes.
Calculate the Cap
- Fee per cycle: 50 USDC
- Number of cycles to cover: 1
- Cap: 50 USDC
You approve 50 USDC for the contract. After one successful transferFrom, the allowance drops to zero. The contract cannot pull more until you approve again.
Example Code: Approving a Bounded Allowance
Assume you use ethers.js and the USDC contract on Base, Ethereum, or Polygon. Here is how to approve a contract to spend 50 USDC from your wallet.
// ethers.js v5
const { ethers } = require("ethers");
const usdcAbi = [
"function approve(address spender, uint256 amount) public returns (bool)"
];
const usdcAddress = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"; // example: USDC on Ethereum
const spender = "0xContractAddressHere"; // the contract to authorize
const cap = ethers.utils.parseUnits("50", 6); // USDC has 6 decimals
async function approveAllowance(signer) {
const usdc = new ethers.Contract(usdcAddress, usdcAbi, signer);
const tx = await usdc.approve(spender, cap);
await tx.wait(); // wait for confirmation
console.log("Approved 50 USDC to spender:", spender);
}
After this transaction is confirmed, the contract can pull up to 50 USDC using transferFrom.
Checking an Allowance
To see how much a contract can still spend, call allowance(owner, spender). This returns the remaining cap. If it is zero, the contract cannot pull more.
const allowance = await usdc.allowance(ownerAddress, spender);
console.log("Current allowance:", ethers.utils.formatUnits(allowance, 6), "USDC");
Revoking or Reducing an Allowance
You can revoke an approval at any time by setting the allowance to zero. You do this by calling approve with amount zero for the same spender. This removes the contract’s ability to spend any more of your tokens.
Sometimes you want to reduce the cap, not remove it entirely. Set a new, lower amount with another approve call. The ERC-20 standard allows you to overwrite the previous value.
Example: Revoking Approval
Here is how to remove all spending power from a contract:
const tx = await usdc.approve(spender, 0);
await tx.wait();
console.log("Revoked allowance for:", spender);
Once confirmed, the contract cannot spend any further USDC from your wallet. You can do this at any time, even if some of the previous cap remains.
Common Approval Patterns and Risks
- Approving only what is needed for the next payment cycle
- Periodically revoking unused allowances
- Never approving unlimited (
MaxUint256) unless you explicitly accept the risk
If a contract fails to pull funds within the cap, it must prompt the user to approve again. If a contract attempts to pull more than the allowance, the transaction reverts and no tokens move.
Handling Approval Failures
Approval can fail for several reasons:
- User rejects the transaction in their wallet
- Insufficient gas for the transaction
- Network congestion or chain reorg
If transferFrom fails, it is because the allowance is too low or zero, or the token balance is insufficient. The contract should surface the exact error to the frontend so the user can re-approve if needed.
Approvals and Non-Custodial Systems
A non-custodial system never takes custody of user funds or keys. Approvals do not give a third party access to your wallet. They only allow a specific contract to pull up to the cap you set, using the rules of the ERC-20 token. At no point does the approver give up control of their wallet or seed phrase.
How We Use Bounded Approvals at Reclack
At Reclack, we require a bounded, explicit ERC-20 approval for every recurring or one-time payment. The user sets and signs the cap in their own wallet, and sees the amount before confirming. We never request unlimited spend. We never ask for a private key or seed phrase. Collection always settles directly from the customer’s wallet to the merchant’s settlement address. If you want to learn more about how we handle stablecoin billing and recurring payments, see reclack.com.
