Build
Contract Standards
Universal Token

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.

yarn add @zetachain/standard-contracts@v1.0.0-rc2

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.

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 {}
}

For details on deployment, gas fees and revert handling, refer to the Universal NFT page.