Posted in

How to revoke an ERC20 token approval using Ethers?

When I first started working as an Ethers provider—helping teams build and interact with Ethereum-based tools—one of the most common pain points I encountered was user confusion around ERC20 token approvals. I can’t tell you how many times I sat down with a developer who’d just spent hours integrating a new DeFi protocol, only to realize they’d granted unlimited approval to a contract that’s since been shut down. Or a retail user who accidentally approved a phishing contract and had no idea how to reverse it. That’s why I made it part of my mission to demystify this process: revoking an ERC20 token approval using Ethers doesn’t have to be complicated, even if you’re new to Ethereum development. Ethers

First, let’s make sure we’re on the same page about what an ERC20 approval is. When you interact with an ERC20 token (like USDC, DAI, or UNI), you’re essentially giving another address—say, a DeFi exchange, a lending protocol, or even a malicious actor—permission to spend your tokens up to a certain amount. Unlimited approvals (where you set the spending limit to uint256.max, the maximum value for a 256-bit integer) are convenient for one-time setup, but they’re a huge security risk: if that contract gets compromised, an attacker can drain all your tokens. That’s where revoking comes in.

As someone who works with Ethers every day, I can tell you there are two main ways to revoke an approval using Ethers.js: either by interacting directly with the ERC20 token contract, or by using a utility method that simplifies the process. Let’s walk through both, step by step, with real-world examples that I’ve used in my own client projects. I’ll also include tips I’ve picked up over the years to avoid common mistakes.

Before we dive into code, let’s cover the prerequisites. If you’re using Ethers, you’ll need to have the library installed—for most modern projects, that’s npm install ethers (or yarn add ethers if you prefer Yarn). You’ll also need a wallet instance: this can be a browser wallet like MetaMask, a hardware wallet like Ledger, or a local wallet for testing. For the sake of these examples, I’ll assume you’re working in a Node.js environment or a front-end React app (since that’s where most of my clients use Ethers).

The first method is the most straightforward: directly calling the approve function on the ERC20 token contract with a spending limit of zero. Wait, that’s it? Yep. The ERC20 standard specifies that the approve function takes two parameters: the spender address, and the amount to approve. To revoke the approval, you just set the amount to 0 (zero). That’s been the standard way to do it since ERC20 was first proposed, and it works across all compliant tokens.

Let me show you what that code looks like. First, you’ll need to connect to the Ethereum network (I’ll use Sepolia testnet for this example, since it’s free and easy to test). Then, you’ll create an instance of the ERC20 token contract using its address and the Ethers contract abstraction. Here’s the code:

const { ethers } = require("ethers");

// Initialize provider (use Sepolia testnet; replace with Mainnet URL for production)
const provider = new ethers.providers.JsonRpcProvider("https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID");

// Your wallet (connect to MetaMask for front-end use; this is a local example)
const privateKey = "YOUR_PRIVATE_KEY"; // Never hardcode this in production!
const wallet = new ethers.Wallet(privateKey, provider);

// ERC20 token details (example: Sepolia USDC)
const tokenAddress = "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238";
const spenderAddress = "0x..."; // The address you want to revoke approval for

// ERC20 ABI (only need the approve function)
const erc20Abi = [
  "function approve(address spender, uint256 amount) external returns (bool)"
];

// Create contract instance
const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, wallet);

// Revoke approval by setting amount to 0
async function revokeApproval() {
  try {
    const tx = await tokenContract.approve(spenderAddress, 0);
    await tx.wait();
    console.log("Approval revoked! Transaction hash:", tx.hash);
  } catch (error) {
    console.error("Error revoking approval:", error);
  }
}

revokeApproval();

I’ve used this exact code for a client who ran a DeFi portfolio tracker—they needed a way for users to quickly revoke approvals from the tools they’d connected. The nice thing about this method is that it’s compatible with every ERC20 token, no exceptions. But there’s a caveat: some older contracts have a bug called the “approve race condition,” where setting an approval from a non-zero value to a zero value might not work as expected. If you’re dealing with a legacy token, you might need to first set the approval to a tiny non-zero value (like 1 wei) before setting it to zero. That’s a trick I learned after a client’s users ran into issues with an old ERC20 token on Ethereum Classic.

The second method is using Ethers’s decreaseAllowance function. Wait, the ERC20 standard also has a decreaseAllowance function, right? This one is designed to lower the spending limit instead of setting it directly, which avoids the race condition I just mentioned. And since we want to bring the limit down to zero, this works perfectly for revocation. Let’s adjust the code to use decreaseAllowance:

async function revokeApprovalWithDecrease() {
  try {
    // To revoke, we decrease the current allowance by its full amount
    const currentAllowance = await tokenContract.allowance(await wallet.getAddress(), spenderAddress);
    const tx = await tokenContract.decreaseAllowance(spenderAddress, currentAllowance);
    await tx.wait();
    console.log("Approval revoked via decreaseAllowance! Transaction hash:", tx.hash);
  } catch (error) {
    console.error("Error revoking approval:", error);
  }
}

This method is actually more secure for legacy tokens because it doesn’t trigger the race condition. I’ve recommended this method to several enterprise clients, especially those working with multi-chain deployments where different ERC20 implementations might have edge cases. That said, you still need to make sure the token is ERC20-compliant—some custom tokens don’t implement decreaseAllowance correctly, so it’s good to test with a small amount first if you’re unsure.

Now, if you’re working in a front-end app (like a React or Vue dApp) instead of a Node.js script, you’ll need to adjust how you connect to the wallet. Most users will be using MetaMask, which injects a window.ethereum provider into the browser. Here’s a simplified front-end example I use when helping teams build user-facing tools:

import { ethers } from "ethers";

// Connect to MetaMask (user initiates this action)
async function connectWallet() {
  if (window.ethereum) {
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    await provider.send("eth_requestAccounts", []); // Request user's accounts
    const signer = provider.getSigner();
    return signer;
  } else {
    alert("Please install MetaMask!");
  }
}

// Revoke approval in a front-end context
async function handleRevoke(tokenAddress, spenderAddress) {
  const signer = await connectWallet();
  const erc20Abi = ["function approve(address spender, uint256 amount) external returns (bool)"];
  const tokenContract = new ethers.Contract(tokenAddress, erc20Abi, signer);
  
  try {
    const tx = await tokenContract.approve(spenderAddress, 0);
    await tx.wait();
    alert("Approval successfully revoked! View transaction: https://etherscan.io/tx/" + tx.hash);
  } catch (error) {
    console.error("Revocation failed:", error);
    alert("Failed to revoke approval. Please try again.");
  }
}

I’ve built this exact component for a DeFi dashboard client—they wanted a one-click “revoke all approvals” button for their users, and this code works seamlessly with MetaMask, WalletConnect, and other browser wallets. One thing to note here: you should always confirm with the user before sending a transaction, and display the spender address and token symbol in the UI so the user knows exactly what they’re revoking. That’s a critical security step—phishing attacks often trick users into revoking the wrong address, so clear UI is non-negotiable.

Now, let’s talk about a common mistake I see developers make: using ethers.utils.parseUnits incorrectly. When you’re setting an approval amount, you need to use the token’s decimals to convert a human-readable amount (like 1 USDC) to the smallest unit (1 * 10^6, since USDC has 6 decimals). If you set the amount to zero, you don’t need to worry about that—0 is just 0, regardless of decimals. But if you were using decreaseAllowance with a non-zero amount, you’d need to make sure you’re parsing it correctly. For example, if the user wants to lower the allowance by 50 USDC, you’d do ethers.utils.parseUnits("50", 6) for USDC. That’s a detail I missed early on, and it led to a client’s transaction failing because they were passing "50" instead of the parsed value.

Another tip I’ve picked up over years as an Ethers provider: always check the current allowance before revoking. You can do that with the allowance function on the ERC20 contract, which takes two parameters: the owner address (your wallet) and the spender address. If the allowance is already zero, there’s no need to send a transaction—you can inform the user that there’s nothing to revoke. That saves users gas fees, which is a big deal on Ethereum Mainnet where gas prices can swing wildly.

What if you want to revoke approvals for multiple spenders at once? Sending a separate transaction for each can be expensive, so some users use a multi-call contract (like the Gnosis Safe’s multi-call, or a custom contract) to batch revoke approvals. As an Ethers provider, I’ve helped several clients integrate multi-call with Ethers—here’s a high-level example of how that works:

  1. Deploy a multi-call contract that accepts a list of (token, spender) pairs.
  2. For each pair, generate the approve transaction data for the ERC20 token.
  3. Use Ethers’s Contract to interact with the multi-call contract, sending all the transaction data in one call.

This reduces the number of transactions and saves gas. I’d recommend this for power users or dashboards that handle hundreds of approvals, but for most retail users, a single transaction per revocation is more user-friendly.

Let’s also address a question I get all the time: can I revoke an approval after it’s already been used? The short answer is yes—approvals are one-time use for most protocols (though some might cache allowances, so it’s good to check with the protocol). Once a spender has used your approved amount, revoking the future allowance doesn’t affect the tokens they’ve already spent. That’s an important distinction to explain to users who are worried about past activity.

As someone who works with Ethers daily, I can’t stress enough how important it is to test revocation transactions on a testnet (like Sepolia or Goerli) before deploying to Mainnet. I’ve had clients who skipped testing and accidentally revoked the wrong approval on Mainnet—they had to send a second transaction to reapprove, which cost them unnecessary gas. Testing on testnet lets you catch edge cases, like legacy token bugs, without risking real funds.

If you’re a developer building tools around ERC20 approvals, or a user who just wants to take control of your token permissions, using Ethers makes this process straightforward. But if you’re not comfortable building this yourself, or you need help integrating revocation functionality into your app, I’m here to help. As an Ethers provider, I specialize in building secure, user-friendly tools for Ethereum and multi-chain applications. Whether you need a custom component for your dApp, help debugging a revocation transaction, or advice on best practices for token approvals, we can work together to find a solution that fits your needs. Get in touch with our team to discuss your requirements and start building or optimizing your Ethers-based token approval workflow.

Ketones References:

  • Ethers.js Documentation (https://docs.ethers.org/)
  • ERC20 Token Standard (https://eips.ethereum.org/EIPS/eip-20)
  • Ethereum.org Guide to Token Approvals (https://ethereum.org/en/developers/docs/standards/tokens/erc-20/)

Shandong Xima Supply Chain Management Co., Ltd.
As one of the most professional ethers manufacturers in China, we offer a wide range of products with superior quality. Please feel free to buy bulk ethers in stock here and get free sample from our factory. We also accept customized orders.
Address: No. 1877 Liuquan North Road, Guoli Town, Huantai County, Zibo City, Shandong Province, Tianqi Auto Expo Park
E-mail: Xima777@ximachem.com
WebSite: https://www.ximachemical.com/