Back to Blog
February 4, 20267 min read

Unlimited Token Approvals: The Silent Wallet Killer

Three months ago, you swapped some tokens on a decentralized exchange. The transaction went through perfectly. You got your tokens. Everything seemed fine. What you probably did not realize is that you also signed an unlimited token approval, and right now, that contract still has permission to move every single one of those tokens out of your wallet without asking you again. This is not a bug. It is how the ERC-20 standard was designed, and it is silently draining wallets across the entire DeFi ecosystem.

How Token Approvals Work

The ERC-20 token standard requires a two-step process for third-party contracts to move your tokens. First, you must approve a spender contract by calling the approve() function on the token contract. Then, the spender can call transferFrom() to move tokens on your behalf. This is how every DEX, lending protocol, and yield farm operates. You cannot use DeFi without granting approvals.

Here is what a standard approval looks like at the smart contract level:

Solidity - ERC-20 approve()
// The ERC-20 approve function
function approve(
    address spender,
    uint256 amount
) external returns (bool);

// Safe approval: only approve what you need
USDC.approve(uniswapRouter, 1000 * 1e6);
// Approves exactly 1,000 USDC

// Dangerous approval: approve unlimited amount
USDC.approve(uniswapRouter, type(uint256).max);
// Approves 115,792,089,237,316,195,423,570,985,008,
// 687,907,853,269,984,665,640,564,039,457.584007913
// USDC — effectively unlimited

The critical difference is the second argument: the amount. When a dApp requests type(uint256).max as the approval amount, it is asking for permission to spend an unlimited quantity of that token. Most dApps do this by default because it saves users gas on future transactions. They only need to approve once, and the contract can move tokens indefinitely. Convenient, yes. Safe, absolutely not.

Why Unlimited Approvals Are Dangerous

When you grant an unlimited approval, you are trusting that the smart contract will never be exploited, that the team behind it will never turn malicious, and that no vulnerability will ever be discovered in the contract code. That is a lot of trust to place in a piece of software, especially considering that even audited contracts get hacked regularly.

Here is what an attacker can do once they have a malicious contract approved:

Solidity - Drainer contract example
// Once you approved their contract, the attacker
// can call this at any time — no further permission needed

function drain(address victim) external onlyOwner {
    uint256 balance = USDC.balanceOf(victim);
    USDC.transferFrom(victim, attackerWallet, balance);
    // Your entire USDC balance is now gone
}

// The attacker can also target multiple tokens:
function drainAll(address victim) external onlyOwner {
    for (uint i = 0; i < tokens.length; i++) {
        uint256 bal = tokens[i].balanceOf(victim);
        if (bal > 0) {
            tokens[i].transferFrom(
                victim, attackerWallet, bal
            );
        }
    }
}

This is not theoretical. In March 2025, a vulnerability in a widely-used DEX aggregator contract allowed an attacker to exploit existing unlimited approvals. Users who had approved the contract months or even years earlier had their tokens drained. Over $12 million was stolen from wallets that had long forgotten they ever interacted with the protocol. The users did nothing wrong at the time of the attack. They simply had stale approvals sitting on-chain.

The Unlimited Approval Problem in Numbers

According to on-chain analysis, the average active Ethereum wallet has 15 outstanding token approvals. Over 60% of those are unlimited approvals. Most users have no idea these approvals exist, and fewer than 5% have ever revoked an approval. Every single one of these approvals is a potential attack vector that persists until explicitly revoked.

The Permit2 Problem

Uniswap introduced Permit2 as an improvement to the approval system. Instead of granting unlimited approvals to every protocol individually, you grant a single unlimited approval to the Permit2 contract, and then use time-limited, amount-limited permits for each protocol. In theory, this is safer. In practice, it has created a new attack surface.

Permit2 signatures are off-chain, meaning they do not require gas and do not appear as on-chain transactions. Phishing sites can request Permit2 signatures disguised as login or verification steps. Because the user sees no gas fee and no transaction, they assume the signature is harmless. In reality, they are signing a permit that allows the attacker's contract to spend their tokens through the Permit2 system.

Permit2 signature structure
// What you see: "Sign this message to verify"
// What you are actually signing:

PermitSingle {
    PermitDetails {
        token:      0xA0b8...eB48  // USDC
        amount:     type(uint160).max  // unlimited
        expiration: 1735689600  // far future date
        nonce:      0
    }
    spender:    0x3eD8...1fA7  // attacker contract
    sigDeadline: 1709251200
}

// After signing, the attacker calls:
permit2.permitTransferFrom(
    permit, transferDetails, owner, signature
);
// Your tokens are gone. No on-chain approval needed.

How to Check and Revoke Your Approvals

The first step is to audit your existing approvals. You can do this using Etherscan's Token Approval Checker, Revoke.cash, or the WTF Am I Signing extension which shows your active approvals in one dashboard. Look for any approvals with the maximum uint256 value, any approvals to contracts you do not recognize, and any approvals to contracts you no longer use.

To revoke an approval, you call the same approve() function but with an amount of zero. This overwrites the previous approval and removes the contract's permission to move your tokens. Revoking an approval costs a small amount of gas, typically between $0.50 and $3.00 on Ethereum mainnet, but it is one of the cheapest forms of security insurance you can buy.

Revoking an approval
// Revoke by setting approval to zero
USDC.approve(suspiciousContract, 0);

// Or use exact amounts instead of unlimited:
// Before your swap, approve only what you need
USDC.approve(uniswapRouter, swapAmount);

// After the swap, the approval is consumed
// No leftover permission remains

Best Practices for Token Approvals

The safest approach is to never grant unlimited approvals. Always specify the exact amount you need for the current transaction. Yes, this means you will need to approve again the next time you use the same protocol, and you will pay a small gas fee for the additional approval transaction. That gas fee is your insurance premium against having your entire token balance drained.

Set a calendar reminder to audit your approvals once a month. Revoke any approvals for contracts you no longer use, protocols that have been deprecated, or contracts you do not recognize. Think of it like checking your credit card statements: a routine hygiene task that prevents catastrophic losses.

Use a transaction decoder like WTF Am I Signing that flags unlimited approvals in real time. When a dApp requests an unlimited approval, the extension will warn you before you sign and give you the option to modify the amount to match only what you need. Prevention is always better than revocation.

Finally, consider using a dedicated hot wallet for DeFi interactions. Keep only the tokens you actively need in this wallet, and store the rest in a hardware wallet or a separate address. Even if your hot wallet approvals are compromised, the attacker can only access the limited funds in that wallet, not your entire portfolio.

Unlimited token approvals are one of the most overlooked risks in DeFi. They are invisible, persistent, and devastating when exploited. Take ten minutes today to check your approvals. It might be the most valuable ten minutes you spend all year.

Back to Blog