Skip to content

Native Token ERC-20 Precompile

Introduction

The native token ERC-20 precompiled contract on Tanssi EVM appchains allows developers to interact with the native protocol token through an ERC-20 interface. Although your appchain's native token is not an ERC-20 token, now you can interact with it as if it was a vanilla ERC-20.

One of the main benefits of this precompile is that it removes the necessity of having a wrapped representation of the protocol token as an ERC-20 smart contract, such as WETH on Ethereum. Furthermore, it minimizes the need for multiple wrapped representations of the same protocol token. Consequently, dApps that need to interact with the protocol token via an ERC-20 interface can do so without needing a separate smart contract.

Under the hood, the ERC-20 precompile executes specific Substrate actions related to the Substrate balances module, which is coded in Rust. The balances module provides functionality for handling the various types of balances.

This guide will show you how to interact with UNIT tokens, the native protocol tokens for Snap appchains on the Tanssi Dancebox TestNet, via the ERC-20 precompile. You can follow along and adapt this guide to interacting with your own appchain.

The precompile is located at the following address:

0x0000000000000000000000000000000000000800

Note

There can be some unintended consequences when using precompiles. Tanssi's Native Token ERC-20 Precompile is derived from Moonbeam's, and as such, please familiarize yourself with Moonbeam's Precompile Security Considerations.

The ERC-20 Solidity Interface

The ERC20.sol interface on Tanssi EVM appchains 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 a token contract must implement to be interoperable with different applications.

ERC20.sol
/ SPDX-License-Identifier: GPL-3.0-only
pragma solidity >=0.8.3;

/// @dev The IERC20 contract's address.
address constant IERC20_ADDRESS = 0x0000000000000000000000000000000000000800;

/// @dev The IERC20 contract's instance.
IERC20 constant IERC20_CONTRACT = IERC20(IERC20_ADDRESS);

/// @title ERC20 interface
/// @dev see https://github.com/ethereum/EIPs/issues/20
/// @dev copied from https://github.com/OpenZeppelin/openzeppelin-contracts
/// @custom:address 0x0000000000000000000000000000000000000800
interface IERC20 {
    /// @dev Returns the name of the token.
    /// @custom:selector 06fdde03
    function name() external view returns (string memory);

    /// @dev Returns the symbol of the token.
    /// @custom:selector 95d89b41
    function symbol() external view returns (string memory);

    /// @dev Returns the decimals places of the token.
    /// @custom:selector 313ce567
    function decimals() external view returns (uint8);

    /// @dev Total number of tokens in existence
    /// @custom:selector 18160ddd
    function totalSupply() external view returns (uint256);

    /// @dev Gets the balance of the specified address.
    /// @custom:selector 70a08231
    /// @param owner The address to query the balance of.
    /// @return An uint256 representing the amount owned by the passed address.
    function balanceOf(address owner) external view returns (uint256);

    /// @dev Function to check the amount of tokens that an owner allowed to a spender.
    /// @custom:selector dd62ed3e
    /// @param owner address The address which owns the funds.
    /// @param spender address The address which will spend the funds.
    /// @return A uint256 specifying the amount of tokens still available for the spender.
    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    /// @dev Transfer token for a specified address
    /// @custom:selector a9059cbb
    /// @param to The address to transfer to.
    /// @param value The amount to be transferred.
    /// @return true if the transfer was succesful, revert otherwise.
    function transfer(address to, uint256 value) external returns (bool);

    /// @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
    /// Beware that changing an allowance with this method brings the risk that someone may use both the old
    /// and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
    /// race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
    /// https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
    /// @custom:selector 095ea7b3
    /// @param spender The address which will spend the funds.
    /// @param value The amount of tokens to be spent.
    /// @return true, this cannot fail
    function approve(address spender, uint256 value) external returns (bool);

    /// @dev Transfer tokens from one address to another
    /// @custom:selector 23b872dd
    /// @param from address The address which you want to send tokens from
    /// @param to address The address which you want to transfer to
    /// @param value uint256 the amount of tokens to be transferred
    /// @return true if the transfer was succesful, revert otherwise.
    function transferFrom(
        address from,
        address to,
        uint256 value
    ) external returns (bool);

    /// @dev Event emited when a transfer has been performed.
    /// @custom:selector ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
    /// @param from address The address sending the tokens
    /// @param to address The address receiving the tokens.
    /// @param value uint256 The amount of tokens transfered.
    event Transfer(address indexed from, address indexed to, uint256 value);

    /// @dev Event emited when an approval has been registered.
    /// @custom:selector 8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925
    /// @param owner address Owner of the tokens.
    /// @param spender address Allowed spender.
    /// @param value uint256 Amount of tokens approved.
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
}

/// @title Native currency wrapper interface.
/// @dev Allow compatibility with dApps expecting this precompile to be
/// a WETH-like contract.
interface WrappedNativeCurrency {
    /// @dev Provide compatibility for contracts that expect wETH design.
    /// Returns funds to sender as this precompile tokens and the native tokens are the same.
    /// @custom:selector d0e30db0
    function deposit() external payable;

    /// @dev Provide compatibility for contracts that expect wETH design.
    /// Does nothing.
    /// @custom:selector 2e1a7d4d
    /// @param value uint256 The amount to withdraw/unwrap.
    function withdraw(uint256 value) external;

    /// @dev Event emited when deposit() has been called.
    /// @custom:selector e1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c
    /// @param owner address Owner of the tokens
    /// @param value uint256 The amount of tokens "wrapped".
    event Deposit(address indexed owner, uint256 value);

    /// @dev Event emited when withdraw(uint256) has been called.
    /// @custom:selector 7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65
    /// @param owner address Owner of the tokens
    /// @param value uint256 The amount of tokens "unwrapped".
    event Withdrawal(address indexed owner, uint256 value);
}

Note

The ERC-20 precompile does not include deposit and withdraw functions and subsequent events expected from a wrapped token contract, such as WETH.

Interact with the Solidity Interface

Checking Prerequisites

To follow along with this tutorial, you will need to have your wallet configured to work with your EVM appchain and an account funded with native tokens. You can add your EVM appchain to MetaMask with one click on the Tanssi dApp. Or, you can configure MetaMask for Tanssi with the demo EVM appchain.

Add Token to MetaMask

If you want to interact with your appchain's native token like you would with an ERC-20 in MetaMask, you can add a custom token to your wallet using the precompile address.

To get started, open up MetaMask and make sure you are connected to your appchain and:

  1. Switch to the Assets tab
  2. Click on Import tokens

Import Tokens from Assets Tab in MetaMask

Now, you can create a custom token:

  1. Enter the precompile address for the token contract address - 0x0000000000000000000000000000000000000800. When you enter the address, the Token Symbol and Token Decimal fields should automatically populate. If they do not, you can enter UNIT for the symbol and 18 for the decimal places. Recall that the default number of decimals for Tanssi EVM appchains is 18, the same as Ethereum's token decimals
  2. Click Next

Add Custom Token

MetaMask will prompt you to confirm the import. You can review the token details and click Import Tokens to import UNIT tokens into your wallet.

Confirm and Import Tokens

And that's it! You've successfully added the UNIT token as a custom ERC-20 token on your Tanssi EVM appchain.

Remix Set Up

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

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

Compile the Contract

Next, you will need to compile the interface in Remix:

  1. Click on the Compile tab, second from top
  2. Compile the interface by clicking on Compile IERC20.sol

Compiling IERC20.sol

When compilation is completed, you will see a green checkmark next to the Compile tab.

Access the Contract

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

  1. Click on the Deploy and Run tab directly below the Compile tab in Remix. Please note that the precompiled contracts are already accessible at their respective addresses. Therefore, there is no deployment step
  2. Make sure Injected Web3 is selected in the ENVIRONMENT dropdown. Once you select Injected Web3, you may be prompted by MetaMask to connect your account to Remix if it's not already connected
  3. Make sure the correct account is displayed under ACCOUNT
  4. Ensure IERC20 - IERC20.sol is selected in the CONTRACT dropdown. Given that it is a precompiled contract, there is no deployment step. Instead, you are going to provide the address of the precompile in the At Address field
  5. Provide the address of the ERC-20 precompile: 0x0000000000000000000000000000000000000800 and click At Address

Access the address

The IERC20 precompile will appear in the list of Deployed Contracts.

Get Basic Token Information

The ERC-20 interface lets you quickly obtain token information, including the token's total supply, name, symbol, and decimal places. You can retrieve this information by following these steps:

  1. Expand the IERC20 contract under Deployed Contracts
  2. Click decimals to get the decimal places of your appchain's native protocol token
  3. Click name to get the name of the token
  4. Click symbol to get the symbol of the token
  5. Click totalSupply to obtain the total supply of native tokens on your appchain

Total Supply

The results of each function call are displayed under the respective functions.

Get Account Balance

You can check the balance of any address on your appchain by calling the balanceOf function and passing in an address:

  1. Expand the balanceOf function
  2. Enter an address you would like to check the balance of for the owner
  3. Click call

Get Balance of an Account

Your balance will be displayed under the balanceOf function.

Approve a Spend

To approve a token spend allowance, you'll need to provide an address for the spender and the number of tokens the spender is allowed to spend. The spender can be an externally owned account (EOA) or a smart contract. For this example, you can approve the spender with an allowance of 1 UNIT token. To get started, please follow these steps:

  1. Expand the approve function
  2. Enter the address of the spender. You should have created two accounts before starting, so you can use the second account as the spender
  3. Enter the amount of tokens the spender can spend for the value. For this example, you can allow the spender to spend 1 UNIT token in Wei units (1000000000000000000)
  4. Click transact
  5. MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction

Confirm Approve Transaction

After the transaction is confirmed, you'll notice that the balance of your account has stayed the same. This is because you have only approved the allowance for the given amount, and the spender hasn't spent the funds. In the next section, you will use the allowance function to verify that the spender can spend 1 UNIT token on your behalf.

Get Allowance of Spender

To check that the spender received the allowance approved in the Approve a Spend section, you can:

  1. Expand the allowance function
  2. Enter your address for the owner
  3. Enter the address of the spender that you used in the previous section
  4. Click call

Get Allowance of Spender

Once the call is complete, the allowance of the spender will be displayed, which should be equivalent to 1 UNIT token (1000000000000000000).

Send Transfer

To send tokens from your account directly to another account, you can call the transfer function by following these steps:

  1. Expand the transfer function
  2. Enter the address to send UNIT tokens to
  3. Enter the amount of UNIT tokens to send. For this example, you can send 1 UNIT token (1000000000000000000)
  4. Click transact
  5. MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction

Send Standard Transfer

Once the transaction is complete, you can check your balance using the balanceOf function or by looking at MetaMask. You'll notice that your balance has decreased by 1 UNIT token. You can also use the balanceOf function to ensure that the recipients balance has increased by 1 UNIT token as expected.

Send Transfer From Specific Account

So far, you have approved an allowance of 1 UNIT token for the spender and sent 1 UNIT token via the standard transfer function. The transferFrom function varies from the standard transfer function as it allows you to define the address to which you want to send the tokens. So you can specify an address with an allowance or your address as long as you have funds. For this example, you will use the spender's account to initiate a transfer of the allowed funds from the owner to the spender. The spender can send the funds to any account, but you can send the funds from the owner to the spender for this example.

First, you need to switch to the spender's account in MetaMask. Once you switch to the spender's account, you'll notice that the selected address in Remix under the Accounts tab is now the spender's.

Switch accounts Remix

Next, you can initiate and send the transfer. To do so, take the following steps:

  1. Expand the transferFrom function
  2. Enter your address as the owner in the from field
  3. Enter the recipient address, which should be the spender's address, in the to field
  4. Enter the amount of UNIT tokens to send. Again, the spender is currently only allowed to send 1 UNIT token, so enter 1000000000000000000
  5. Click transact

Send Standard Transfer

Once the transaction is complete, you can check the balance of the owner and spender using the balanceOf function. The spender's balance should have increased by 1 UNIT token, and their allowance should now be depleted. To verify that the spender no longer has an allowance, you can call the allowance function by passing in the owner and spender's addresses. You should receive a result of 0.

Zero Allowance

And that's it! You've successfully interacted with the ERC-20 precompile using MetaMask and Remix!

The information presented herein has been provided by third parties and is made available solely for general information purposes. Tanssi does not endorse any project listed and described on the Tanssi Doc Website (https://docs.tanssi.network/). Tanssi Foundation does not warrant the accuracy, completeness or usefulness of this information. Any reliance you place on such information is strictly at your own risk. Tanssi Foundation disclaims all liability and responsibility arising from any reliance placed on this information by you or by anyone who may be informed of any of its contents. All statements and/or opinions expressed in these materials are solely the responsibility of the person or entity providing those materials and do not necessarily represent the opinion of Tanssi Foundation. The information should not be construed as professional or financial advice of any kind. Advice from a suitably qualified professional should always be sought in relation to any particular matter or circumstance. The information herein may link to or integrate with other websites operated or content provided by third parties, and such other websites may link to this website. Tanssi Foundation has no control over any such other websites or their content and will have no liability arising out of or related to such websites or their content. The existence of any such link does not constitute an endorsement of such websites, the content of the websites, or the operators of the websites. These links are being provided to you only as a convenience and you release and hold Tanssi Foundation harmless from any and all liability arising from your use of this information or the information provided by any third-party website or service.
Last update: May 10, 2024
| Created: February 2, 2024