Skip to content

Overview of XC-20s

Cross-Chain Assets Precompiled Contracts Banner

Introduction

The Cross-Consensus Message (XCM) format defines how messages can be sent between interoperable blockchains. This format opens the door to transfer messages and assets (Substrate assets) between Moonbeam/Moonriver and the relay chain or other parachains in the Polkadot/Kusama ecosystems.

Substrate assets are natively interoperable. However, developers need to tap into the Substrate API to interact with them, making the developer experience unideal, especially for those from the Ethereum world. Consequently, to help developers tap into the native interoperability that Polkadot/Kusama offers, Moonbeam introduced the concept of XC-20s.

XC-20s are a unique asset class on Moonbeam. It combines the power of Substrate assets (native interoperability) but allows users and developers to interact with them through a familiar ERC-20 interface via a precompile contract (Ethereum API). Moreover, developers can integrate XC-20s with regular Ethereum development frameworks or dApps.

Moonbeam XC-20 XCM Integration With Polkadot

Types of XC-20s

There are two types of XC-20s: external and mintable.

External XC-20s are native cross-chain assets that are transferred from another parachain or the relay chain to Moonbeam. Consequently, the actual tokens reside in Moonbeam's sovereign account in each of these chains. External XC-20s will all have xc prepended to their name to distinguish them as native cross-chain assets.

Mintable XC-20s are also cross-chain assets, however, they are minted and burned directly on Moonbeam and can be transferred to other parachains. Since mintable XC-20s are created on Moonbeam and are not native to another parachain or relay chain, the name, symbol, and decimals for the asset are all completely configurable. As such, they will not neccessarily have xc prepended to the asset name or symbol.

Both type of XC-20s are Substrate assets at their core and at a low-level are interacted with via the Substrate API. However, Moonbeam provides an ERC-20 interface to interact with these assets so that no knowledge of Substrate is required. From a user's perspective, both types of XC-20s are interacted with in the same way. The only difference here is that mintable XC-20s include an extension of the ERC-20 interface with some additional functionality for managing the assets, such as minting and burning.

Cross-chain transfer of XC-20s are done using the X-Tokens pallet. The instructions for transferring mintable and external XC-20s are slightly different depending on the multilocation of the given asset.

XC-20s vs ERC-20

Although XC-20s and ERC-20s are very similar, some distinct differences are to be aware of.

First and foremost, XC-20s are Substrate-based assets, and as such, they are also subject to be directly impacted by Substrate features such as governance. In addition, XC-20s transactions done via the Substrate API won't be visible from EVM-based block explorers such as Moonscan. Only transactions done via the Ethereum API are visible through such explorers.

Nevertheless, XC-20s can be interacted with through an ERC-20 interface, so they have the additional benefit of being accessible from both the Substrate and Ethereum APIs. This ultimately provides greater flexibility for developers when working with these types of assets and allows seamless integrations with EVM-based smart contracts such as DEXs, lending platforms, among others.

The ERC-20 Solidity Interface

The ERC20.sol interface on Moonbeam follows the EIP-20 Token Standard which is the standard API interface for tokens within smart contracts. The standard defines the required functions and events that a token contract must implement to be interoperable with different applications.

The interface includes the following functions:

  • name() — read-only function that returns the name of the token
  • symbol() — read-only function that returns the symbol of the token
  • decimals() — read-only function that returns the decimals of the token
  • totalSupply() — read-only function that returns the total number of tokens in existence
  • balanceOf(address who) — read-only function that returns the balance of the specified address
  • allowance(address owner, address spender) — read-only function that checks and returns the amount of tokens that an owner is allowed to a spender
  • transfer(address to, uint256 value) — transfers a given amount of tokens to a specified address and returns true if the transfer was successful
  • approve(address spender, uint256 value) — approves the provided address to spend a specified amount of tokens on behalf of msg.sender. Returns true if successful
  • transferFrom(address from, address to, uint256 value) — transfers tokens from one given address to another given address and returns true if successful

Note

The ERC-20 standard does not specify the implications of multiple calls to approve. Changing an allowance with this function numerous times enables a possible attack vector. To avoid incorrect or unintended transaction ordering, you can first reduce the spender allowance to 0 and then set the desired allowance afterward. For more details on the attack vector, you can check out the ERC-20 API: An Attack Vector on Approve/TransferFrom Methods overview

The interface also includes the following required events:

  • Transfer(address indexed from, address indexed to, uint256 value) - emitted when a transfer has been performed
  • Approval(address indexed owner, address indexed spender, uint256 value) - emitted when an approval has been registered

Mintable XC-20s also include additional functions that only the owner of the token contract or a designated account is allowed to call. Please check out the Mintable XC-20 page for more details on the additional functions and the designated roles available.

The ERC-20 Permit Solidity Interface

The Permit.sol interface on Moonbeam follows the EIP-2612 standard which extends the ERC-20 interface with the permit function. Permits are signed messages that can be used to change an account's ERC-20 allowance.

The standard ERC-20 approve function is limited in it's design as the allowance can only be modified by the sender of the transaction, the msg.sender. This can be seen in OpenZeppelin's implentation of the ERC-20 interface, that sets the owner through the msgSender function, which ultimately sets it to the msg.sender.

Instead of signing the approve transaction, a user can sign a message and that signature can be used to call the permit function to modify the allowance. As such, it allows for gas-less token transfers. In addition, users no longer need to send two transactions to approve and transfer tokens. To see an example of the permit function, you can check out OpenZeppelin's implentation of the ERC-20 Permit extension.

The Permit.sol interface includes the following functions:

  • permit(address owner, address spender, uint256, value, uint256, deadline, uint8 v, bytes32 r, bytes32 s) - consumes an approval permit which can be called by anyone
  • nonces(address owner) - returns the current nonce for the given owner
  • DOMAIN_SEPARATOR() - returns the EIP-712 domain separator which is used to avoid replay attacks. It follows the EIP-2612 implementation

The DOMAIN_SEPARATOR() is defined in the EIP-712 standard, and is calculated as:

keccak256(PERMIT_DOMAIN, name, version, chain_id, address)

The parameters of the hash can be broken down as follows:

  • PERMIT_DOMAIN - is the keccak256 of EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)
  • name - is the token name but with the following considerations:
    • If the token has a name defined, the name for the domain is XC20: <name>, where <name> is the token name
    • If the token has no name defined, the name for the domain is XC20: No name
  • version - is the version of the signing domain. For this case version is set to 1
  • chainId - is the chain ID of the network
  • verifyingContract - is the XC-20 address

Note

Prior to runtime upgrade 1600, the name field does not follow the standard EIP-2612 implementation.

The calculation of the domain separator can be seen in Moonbeam's EIP-2612 implementation, with a practical example shown in OpenZeppelin's EIP712 contract.

Aside from the domain separator, the hashStruct guarantees that the signature can only be used for the permit function with the given function arguments. It uses a given nonce to ensure the signature is not subject to a replay attack. The calculation of the hash struct can be seen in Moonbeam's EIP-2612 implementation, with a practical example shown in OpenZeppelin's ERC20Permit contract.

The domain separator and the hash struct can be used to build the final hash of the fully encoded message. A practical example is shown in OpenZeppelin's EIP712 contract.

With the final hash and the v, r, and s values, the signature can be verified and recovered. If successfully verified, the nonce will increase by one and the allowance will be updated.

Interact with the Precompile Using Remix

Regardless if the asset is external or minted, they can be interacted with in the same way. However, if you are the owner of a mintable token contract or a desginated account with specific capabilities, there are some additional functions that you can interact with. For more information on how to interact with mintable XC-20 specific functions, please check out the Interact with Mintable XC-20 Specific Functions section of the Mintable XC-20 page.

Checking Prerequisites

To approve a spend or transfer XC-20s via the XC-20 precompile, you will need:

This guide will cover how to interact with the ERC20.sol interface. You can adapt the following instructions and use the Permit.sol interface.

Add & Compile the Interface

You can interact with the XC-20 precompile using Remix. First, you will need to add the ERC-20 interface to Remix:

  1. Get a copy of ERC20.sol
  2. Paste the file contents into a Remix file named IERC20.sol

Load the interface in Remix

Once you have the ERC-20 interface loaded in Remix, you will need to compile it:

  1. Click on the Compile tab, second from top
  2. Compile the IERC20.sol file

Compiling IERC20.sol

If the interface was compiled successfully, you will see a green checkmark next to the Compile tab.

Access the Precompile

Instead of deploying the ERC-20 precompile, you will access the interface given the address of the XC-20 precompile:

  1. Click on the Deploy and Run tab directly below the Compile tab in Remix. Please note the precompiled contract is already deployed
  2. Make sure Injected Web3 is selected in the ENVIRONMENT dropdown. Once you select Injected Web3, you might be prompted by MetaMask to connect your account to Remix
  3. Make sure the correct account is displayed under ACCOUNT
  4. Ensure IERC20 - IERC20.sol is selected in the CONTRACT dropdown. Since this is a precompiled contract, there is no need to deploy any code. Instead you are going to provide the address of the precompile in the At Address field
  5. Provide the address of the XC-20 precompile calculated in the Calculate External XC-20 Precompile Addresses or the Calculate Mintable XC-20 Precompile Addresses instructions. For this example you can use 0xFFFFFFFF1FCACBD218EDC0EBA20FC2308C778080, and click At Address

Access the address

Note

Optionally, you can checksum the XC-20 precompile address by going to your search engine of choice and searching for a tool to checksum the address. Once the address has been checksummed, you can use it in the At Address field instead.

The IERC20 precompile for the XC-20 will appear in the list of Deployed Contracts. Now you can feel free to call any of the standard ERC-20 functions to get information about the XC-20 or transfer the XC-20.

Interact with the precompile functions

To learn how to interact with each of the functions, you can check out the ERC-20 Precompile guide and modify it for interacting with the XC-20 precompile.