Skip to content

External Assets as ERC-20

Introduction

As presented in the Native Cross-Chain Communication article, appchains deployed through Tanssi can communicate and interoperate with any other appchain in the ecosystem. This multi-chain environment leads to a multi-asset world, where seamless transfer of assets, data, and value across different networks widens the possibilities to build use cases across diverse industries such as finance (DeFi), real-world assets (RWAs), and others.

External assets are tokens native to another blockchain, or, in other words, assets whose reserve chain is not the chain you are interacting with. Tanssi appchains can register external assets to enable their inflow. To do so, it is necessary to establish an XCM channel with the other chain and then register one of its native assets as an external asset. Registered external assets behave, to some extent, the same way as local ones.

The ERC-20 assets precompile allows appchains based on the Tanssi EVM template to access any registered external asset through the standard ERC-20 interface. Consequently, smart contracts deployed to the appchain can interact with such assets as they would with any other regular ERC-20.

The address representing the ERC-20 contract is formed with the first thirty-six positions (eighteen bytes) set to the maximum value and the last four positions (two bytes) replaced with the hexadecimal representation of the registered asset identifier:

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF____

For example, for the asset whose ID is 1, the last four positions must be replaced with 0001, and for an asset with an ID of 10, those four positions must be replaced with 000A.

Note

There can be some unintended consequences when using precompiles. Tanssi's precompiles are derived from Moonbeam's, and as such, please familiarize yourself with Moonbeam's Precompile Security Considerations.

Prerequisites

Tto follow along with the contents in this guide, you'll need:

The examples in this guide are based on the Tanssi demo EVM appchain, which already has open channels to other appchains and registered external assets, as the following picture shows:

  1. The registered external asset (UNIT) which will be used in the following sections
  2. Other available external assets not yet registered

Tanssi EVM demo appchain registered external Assets

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 external assets ERC-20 precompile does not include deposit and withdraw functions and subsequent events expected from a wrapped token contract, such as WETH.

Add Token to an EVM Wallet

If you want to interact with your appchain's registered external assets like you would with an ERC-20, you can add them to your wallet using the precompile address prefix and the asset ID. This section will walk you through adding an external asset to MetaMask.

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

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

    Import Tokens from Tokens Tab in MetaMask

Before continuing, you'll need the token's address, which, considering that in this example the external asset has an ID of 1, will be:

0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0001
  1. Enter the precompile address for the token contract address. 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 12 for the decimal places
  2. Click Next

Add External Asset

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 external asset as a custom ERC-20 token on the Tanssi demo EVM appchain.

Interact with the Solidity Interface via Remix

Remix Set Up

You can interact with the external assets 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 smart contract, you will access the interface through the address of external asset precompile:

  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 (which is 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF0001 in this example) and click At Address
  6. The IERC20 precompile will appear in the list of Deployed Contracts

Access the address

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

Get basic token information

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.

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 (1000000000000)
  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.

And that's it! You've successfully interacted with the external assets 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: October 2, 2024
| Created: February 2, 2024