The Universal Token standard enables ERC-20 fungible tokens to be minted on any chain and seamlessly transferred between connected chains.
When transferring tokens between chains, a token is burned on the source chain. The token's amount is sent in a message to the token contract on the destination chain, where a corresponding token is minted.
The Universal Token standard works the same way as Universal NFT.
Installing from NPM
yarn add @zetachain/standard-contracts@v1.0.0-rc2
Starting from Scratch
Contract on ZetaChain:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@zetachain/standard-contracts/contracts/token/contracts/zetachain/UniversalToken.sol";
contract ZetaChainUniversalToken is UniversalToken {}
Contract on an EVM chain:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import "@zetachain/standard-contracts/contracts/token/contracts/evm/UniversalToken.sol";
contract EVMUniversalToken is UniversalToken {}
UniversalToken
is an upgradeable contract that uses OpenZeppelin
UUPSUpgradeable (opens in a new tab).
Instead of a constructor
it uses the initialize
function.
Starting from an OpenZeppelin Template
If you already have an ERC-20 contract you want make universal or you prefer to start from an OpenZeppelin template, you can import Universal Token functionality with just a few lines of code:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {RevertContext, RevertOptions} from "@zetachain/protocol-contracts/contracts/Revert.sol";
import "@zetachain/protocol-contracts/contracts/zevm/interfaces/UniversalContract.sol";
import "@zetachain/protocol-contracts/contracts/zevm/interfaces/IGatewayZEVM.sol";
import "@zetachain/protocol-contracts/contracts/zevm/GatewayZEVM.sol";
import {SwapHelperLib} from "@zetachain/toolkit/contracts/SwapHelperLib.sol";
import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
// Import the Universal Token core contract
import "@zetachain/standard-contracts/contracts/token/contracts/zetachain/UniversalTokenCore.sol";
contract UniversalToken is
Initializable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
ERC20PausableUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable,
UniversalTokenCore // Inherit the Universal Token core contract
{
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
address initialOwner,
string memory name,
string memory symbol,
address payable gatewayAddress, // Include EVM gateway address
uint256 gas, // Set gas limit for universal Token transfers
address uniswapRouterAddress // Uniswap v2 router address for gas token swaps
) public initializer {
__ERC20_init(name, symbol);
__ERC20Burnable_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
__UniversalTokenCore_init(gatewayAddress, gas, uniswapRouterAddress); // Initialize the Universal Token core contract
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
// The following functions are overrides required by Solidity.
function _update(
address from,
address to,
uint256 value
) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) {
super._update(from, to, value);
}
receive() external payable {}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import {ERC20PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
// Import the Universal Token core contract
import "@zetachain/standard-contracts/contracts/token/contracts/evm/UniversalTokenCore.sol";
contract UniversalToken is
Initializable,
ERC20Upgradeable,
ERC20BurnableUpgradeable,
ERC20PausableUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable,
UniversalTokenCore // Inherit the Universal Token core contract
{
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(
address initialOwner,
string memory name,
string memory symbol,
address payable gatewayAddress, // Include EVM gateway address
uint256 gas // Set gas limit for universal Token transfers
) public initializer {
__ERC20_init(name, symbol);
__ERC20Burnable_init();
__ERC20Pausable_init();
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
__UniversalTokenCore_init(gatewayAddress, address(this), gas); // Initialize the Universal Token core contract
}
function pause() public onlyOwner {
_pause();
}
function unpause() public onlyOwner {
_unpause();
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
// The following functions are overrides required by Solidity.
function _update(
address from,
address to,
uint256 value
) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) {
super._update(from, to, value);
}
receive() external payable {}
}
Learn More
For details on deployment, gas fees and revert handling, refer to the Universal NFT page.