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 precompiles are 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 an EVM Wallet¶
If you want to interact with your appchain's native token like you would with an ERC-20, you can add a custom token to your EVM-compatible wallet using the precompile address. 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:
- Switch to the Assets tab
- Click on Import tokens
Now, you can create a custom token:
- 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 enterUNIT
for the symbol and18
for the decimal places. Recall that the default number of decimals for Tanssi EVM appchains is18
, the same as Ethereum's token decimals - Click Next
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.
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:
- Get a copy of
ERC20.sol
- Paste the file contents into a Remix file named
IERC20.sol
Compile the Contract¶
Next, you will need to compile the interface in Remix:
- Click on the Compile tab, second from top
- Compile the interface by clicking on Compile 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:
- 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
- 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
- Make sure the correct account is displayed under ACCOUNT
- 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
- Provide the address of the ERC-20 precompile:
0x0000000000000000000000000000000000000800
and click At 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:
- Expand the IERC20 contract under Deployed Contracts
- Click decimals to get the decimal places of your appchain's native protocol token
- Click name to get the name of the token
- Click symbol to get the symbol of the token
- Click totalSupply to obtain the total supply of native tokens on your appchain
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:
- Expand the balanceOf function
- Enter an address you would like to check the balance of for the owner
- Click call
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:
- Expand the approve function
- Enter the address of the spender. You should have created two accounts before starting, so you can use the second account as the spender
- 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
) - Click transact
- MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the 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:
- Expand the allowance function
- Enter your address for the owner
- Enter the address of the spender that you used in the previous section
- Click call
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:
- Expand the transfer function
- Enter the address to send UNIT tokens to
- Enter the amount of UNIT tokens to send. For this example, you can send 1 UNIT token (
1000000000000000000
) - Click transact
- MetaMask will pop up, and you will be prompted to review the transaction details. Click Confirm to send the transaction
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.
Next, you can initiate and send the transfer. To do so, take the following steps:
- Expand the transferFrom function
- Enter your address as the owner in the from field
- Enter the recipient address, which should be the spender's address, in the to field
- Enter the amount of UNIT tokens to send. Again, the spender is currently only allowed to send 1 UNIT token, so enter
1000000000000000000
- Click transact
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.
And that's it! You've successfully interacted with the ERC-20 precompile using MetaMask and Remix!
| Created: February 2, 2024