How to Create and Deploy an Upgradeable ERC20 Token

·

Creating and deploying an upgradeable ERC20 token is a critical skill for modern blockchain developers. As decentralized applications evolve, the ability to modify and enhance smart contracts after deployment becomes essential. Traditional immutable contracts can become obsolete or require costly migrations, leading to user friction and trust issues. With upgradeable contracts, you maintain continuity—preserving token addresses, balances, and integrations—while improving functionality.

In this comprehensive guide, you’ll learn how to build and deploy an upgradeable ERC20 token using OpenZeppelin’s battle-tested libraries and Foundry’s powerful development framework. You'll also connect to the Ethereum network via QuickNode endpoints for fast and reliable blockchain interaction.


Understanding ERC20 Tokens

The ERC20 token standard defines a common set of rules for fungible tokens on Ethereum and EVM-compatible blockchains. "ERC" stands for Ethereum Request for Comments, with "20" being its unique identifier. Before ERC20, every token had a custom structure, making integration with wallets, exchanges, and dApps complex and inconsistent.

ERC20 solved this by standardizing key functions such as:

This uniformity enables seamless interoperability across the ecosystem. Developers can treat ERC20 tokens like a shared interface, ensuring predictable behavior regardless of the underlying implementation.

👉 Discover how to securely manage your token deployment process.


What Is an Upgradeable ERC20 Token?

An upgradeable ERC20 token allows developers to update its logic after deployment without changing the contract address or losing user data. This flexibility is achieved through a proxy pattern that separates storage from business logic.

Core Components of Upgradeability

This architecture ensures that token balances, allowances, and other state variables remain intact across upgrades.

Types of Proxy Patterns

There are several approaches to implementing upgradeable contracts:

Transparent Proxy

Uses separate addresses for admin and user roles to prevent accidental execution of administrative functions during regular interactions.

UUPS (Universal Upgradeable Proxy Standard)

More gas-efficient, as the upgrade logic resides within the implementation contract itself. Requires careful design but reduces overhead.

Beacon Proxy

Centralizes upgrade control through a "Beacon" contract that multiple proxies reference. Ideal for managing many identical upgradeable instances.

For this guide, we’ll focus on the UUPS Proxy pattern, leveraging OpenZeppelin’s secure implementation.


Why Use OpenZeppelin?

OpenZeppelin offers rigorously audited, reusable smart contract components that follow best security practices. Their upgradeable contract suite includes:

By building on OpenZeppelin’s foundation, you minimize risks associated with custom implementations and benefit from community-vetted code.


Setting Up Your Development Environment

Before writing code, ensure your environment is ready.

Prerequisites

Install Foundry

Run the following command in your terminal:

curl -L https://foundry.paradigm.xyz | bash

After installation, initialize a new project:

forge init erc20_upgradeable && cd erc20_upgradeable

Your project structure will include:

Create necessary files:

echo > src/MyToken.sol && echo > src/MyTokenV2.sol && echo > test/MyTokenTest.t.sol && echo > script/deployToken.s.sol && echo > script/deployProxy.s.sol && echo > remappings.txt

Configure Project Dependencies

Install required OpenZeppelin packages:

forge install OpenZeppelin/openzeppelin-contracts --no-commit
forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit
forge install OpenZeppelin/openzeppelin-contracts-upgradeable --no-commit

Update remappings.txt:

@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/
@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/

Edit foundry.toml:

build_info = true
extra_output = ["storageLayout"]

[rpc_endpoints]
sepolia = "YOUR_QUICKNODE_HTTP_URL"

Replace YOUR_QUICKNODE_HTTP_URL with your actual endpoint from QuickNode.

Set your private key as an environment variable:

export PRIVATE_KEY=your_private_key_here

Write the Upgradeable ERC20 Contract

Navigate to src/MyToken.sol and add:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";

contract MyToken is Initializable, ERC20Upgradeable, OwnableUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable {
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }

    function initialize(address initialOwner) initializer public {
        __ERC20_init("MyToken", "MTK");
        __Ownable_init(initialOwner);
        __ERC20Permit_init("MyToken");
        __UUPSUpgradeable_init();
        _mint(msg.sender, 1_000_000 * 10 ** decimals());
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function _authorizeUpgrade(address newImplementation)
        internal
        override
        onlyOwner
    {}
}

This contract supports:

Create src/MyTokenV2.sol for future upgrades:

/// @custom:oz-upgrades-from MyToken
contract MyTokenV2 is Initializable, ERC20Upgradeable, OwnableUpgradeable, ERC20PermitUpgradeable, UUPSUpgradeable {
    // Same structure with updated name/symbol if needed
}

Compile and Test the Contract

Run tests using Foundry:

forge build && forge test --ffi

Sample test (test/MyTokenTest.t.sol) verifies:

Expected output:

[PASS] testERC20Functionality()
[PASS] testUpgradeability()

Deploy the Token

Deploy the implementation:

forge script script/deployToken.s.sol --rpc-url sepolia --private-key $PRIVATE_KEY --broadcast

Then deploy the proxy:

forge script script/deployProxy.s.sol:DeployUUPSProxy --rpc-url sepolia --private-key $PRIVATE_KEY --broadcast

After deployment, verify your contract on Etherscan by adding --verify --etherscan-api-key YOUR_KEY.


Frequently Asked Questions

Q: Why use upgradeable contracts?
A: They allow bug fixes, feature additions, and optimizations without disrupting users or requiring token migration.

Q: Are upgradeable contracts safe?
A: When built with audited libraries like OpenZeppelin and proper access controls, they are secure. However, admin privileges must be carefully managed.

Q: Can anyone upgrade the contract?
A: No. Only the designated admin (usually the owner) can trigger upgrades via _authorizeUpgrade.

Q: What happens to token balances during an upgrade?
A: Balances remain unchanged because the proxy contract retains all storage data.

Q: Is the UUPS pattern more efficient than Transparent Proxy?
A: Yes. UUPS saves gas by embedding upgrade logic in the implementation, reducing deployment costs.

👉 Learn how to monitor your deployed contract in real time.


Fetch Token Data Using APIs

Once deployed, use blockchain APIs to retrieve metadata. For example, with QuickNode’s Token API:

curl YOUR_ENDPOINT \
-X POST \
-H "Content-Type: application/json" \
--data '{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "qn_getTokenMetadataByContractAddress",
  "params": [{ "contract": "YOUR_TOKEN_ADDRESS" }]
}'

Response includes:


Final Thoughts

You’ve now built, tested, and deployed an upgradeable ERC20 token using modern tooling. This foundation empowers you to create flexible, maintainable blockchain applications. Whether adding new features or fixing vulnerabilities, upgradeability ensures long-term viability.

Keep exploring—consider integrating governance mechanisms or multi-sig controls for enhanced security.

👉 Secure your next blockchain project with advanced tools and insights.