Overview of XC-20s¶
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.
Types of XC-20s¶
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
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
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
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
- If the token has a name defined, the name for the domain is
- version - is the version of the signing domain. For this case version is set to
- chainId - is the chain ID of the network
- verifyingContract - is the XC-20 address
Prior to runtime upgrade 1600, the name field does not follow the standard EIP-2612 implementation.
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
With the final hash and the
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.
To approve a spend or transfer XC-20s via the XC-20 precompile, you will need:
- MetaMask installed and connected to the Moonbase Alpha TestNet
- Create or have two accounts on Moonbase Alpha
- At least one of the accounts will need to be funded with
DEVtokens. You can get DEV tokens for testing on Moonbase Alpha once every 24 hours from the Moonbase Alpha Faucet
- The precompile address of the XC-20 you want to interact with. The instructions for calculating the precompile address are slightly different depending on whether the XC-20 is external and imported in or minted directly on Moonbeam:
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:
- Get a copy of ERC20.sol
- Paste the file contents into a Remix file named IERC20.sol
Once you have the ERC-20 interface loaded in Remix, you will need to compile it:
- Click on the Compile tab, second from top
- Compile the IERC20.sol file
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:
- Click on the Deploy and Run tab directly below the Compile tab in Remix. Please note the precompiled contract is already deployed
- 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
- Make sure the correct account is displayed under ACCOUNT
- 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
- 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
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.
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.