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:
transfer– Send tokens from one address to anotherbalanceOf– Query an account’s token balancetotalSupply– Retrieve the total number of tokens in circulation
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
- Proxy Contract: The user-facing contract with a fixed address. It forwards calls to the logic contract.
- Logic (Implementation) Contract: Contains the actual code. Can be replaced during upgrades.
- Admin Role: Controls the upgrade process, typically restricted to a trusted owner or governance system.
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:
ERC20Upgradeable– Standard token functionality with upgrade supportOwnableUpgradeable– Access control via ownershipERC20PermitUpgradeable– Gasless approvals using EIP-2612UUPSUpgradeable– Built-in upgrade authorization
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
- Basic knowledge of Ethereum and Solidity
- Node.js installed
- A Web3 wallet (e.g., MetaMask)
- Foundry CLI for compiling, testing, and deploying
Install Foundry
Run the following command in your terminal:
curl -L https://foundry.paradigm.xyz | bashAfter installation, initialize a new project:
forge init erc20_upgradeable && cd erc20_upgradeableYour project structure will include:
src/– Smart contractstest/– Test filesscript/– Deployment scriptslib/– External dependenciesfoundry.toml– Configuration file
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.txtConfigure 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-commitUpdate 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_hereWrite 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:
- Token initialization with name and symbol
- Ownership management
- Gas-efficient approvals via
permit - Secure upgrades via
_authorizeUpgrade
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 --ffiSample test (test/MyTokenTest.t.sol) verifies:
- Minting works for owner only
- Proxy can be upgraded to
MyTokenV2
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 --broadcastThen deploy the proxy:
forge script script/deployProxy.s.sol:DeployUUPSProxy --rpc-url sepolia --private-key $PRIVATE_KEY --broadcastAfter 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:
- Name, symbol, decimals
- Genesis block and transaction
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.