(Note that the feature is working in progress)
With standardized smart contracts for new tokens, e.g., ERC20 tokens, every person could easily create their tokens by paying the native token (e.g., ETH) on a blockchain. However, compared to native tokens, these smart-contract-based tokens have several fundamental limitations (without loss of generality, we use ETH as the native token and an ERC20 token as a smart-contract-based token):
Performing a smart contract transaction paid by a smart-contract-based token is complicated
Paying a decentralized service or application via an ERC20 token is one major use of the token. For example,
- A user may pay QSP (an ERC20 token) to buy Quantstamp audit services (https://quantstamp.com/qsp-network/instructions),
- A crowd-sale smart contract may offer new tokens by accepting payments with another ERC20 token.
Unlike paying such services via ETH, where a user can pay the smart contract directly and complete the rest transactions atomically, a common mistake of performing such transaction via an ERC20 token is that a user directly sends the ERC20 token to the smart contract address. This only modifies the balances of the addresses in the ERC20 wallet, and it does not complete the rest of transactions (e.g., services) as the exchange of payment. If the mistake happens, a user may need to ask for a refund, or the owner of the smart contract may need to manually complete the rest transactions, both of which can be painful.
The correct way to perform a smart contract transaction by paying an ERC20 token generally has the following steps:
- Call approve() method of the ERC20 smart contract and provide the target smart contract address as spender and the amount to be approved.
- Call the target smart contract method (e.g., requestAudit() in QSP) to complete the transaction paid by the ERC20.
The two-step payment is pretty inconvenient compared to one-step payment via ETH. In addition, paying via an ERC20 token introduces an attack called the allowance double-spend exploit if multiple payments are performed to the same target smart contract.
Performing a smart contract transaction paid by the smart-contract-based token needs native token as gas
To perform a smart contract transaction, a user has to pay both ERC20 token and ETH as gas. This means that a user must make sure it has sufficient balances of both tokens before performing the transaction.
Enable Multi-Native-Token on a Single Blockchain
Based on the aforementioned concerns, we believe supporting multiple native-tokens on a single blockchain will be very critical and beneficial to both dApps and users:
- Any user is able to create native tokens as long as the user pays sufficient gas represented by existing native tokens; and
- A user could use a single-step payment to transfer any native token and complete the rest of transactions (services) atomically; and
- A user could use any native token as gas for a transaction.
In the following, we will explain how to enable these features in details.
Introduction to Token Id
Each native token is associated with a token id starting from 0, where the native token with token id 0 is called genesis native token (e.g., ETH or QKC). For each user account (consider account-based blockchain model), instead of storing a single integer value in its balance field, the balance field is a token id => balance mapping (most likely implemented by Patricia Trie) to contain all the balances of all native tokens of the user.
Transactions using Different Native Tokens
Besides common fields like gasPrice, startGas, value, etc, each transaction contains additional two fields
- gasTokenId: an integer field to indicate which native token is used to pay gas; and
- transferTokenId: an integer field to indicate which native token is transfer to the target address.
A miner is free to include any valid transactions in a block even each transactions have different gasTokenId’s. However, to maximize the miner’s economic benefits, it should include the transactions by ranking their values, which can be done by connecting to an external oracle source of pricing (e.g., an exchange) and determining the value of the transaction (e.g., gasPrice * gasUsed * tokenFiatPrice).
Creating New Native Token
Any user is able to create a new native token with the following steps:
- Submit a native token creation transaction, which includes gasPrice, gasTokenId, total supply, and genesis address as parameters; and
- When the transaction is executed (by being included in a block), the transaction will a), bump up a global integer called token id count and return the value as the new token id; b), set the balance of the genesis address of the token id as total supply; c), return success and the new token id.
VM Support for Multi-Native Token
Several OP codes will be added to VM to support multiple native tokens:
- GAS_TOKEN_ID, which returns the integer of current token id used to pay gas and is immutable in a transaction;
- TRANSFER_TOKEN_ID, which returns the integer of current token id used to transfer value;
- XCALL, XDELEGATECALL, etc, which need an extra parameter to change TRANSFER_TOKEN_ID of the subroutine.
Most of the smart contract may not be care about GAS_TOKEN_ID. However, it may revert the transaction if TRANSFER_TOKEN_ID does not match (e.g., must be the token id for payment). In addition, with XCALL, the smart contract is able to transfer value in a different value, which enables applications such as crowdsale and decentralized exchange.
Other Thoughts
There are a couple enhancements that can be made:
- Use string as a token id, e.g., apple, orange.
- The format of native-token aware address. We could enhance the address by adding token information. E.g., apple:0xabc, which means pay to address 0xabc using token apple with genesis token as gas.
- We may also need special transactions to a) issue new tokens from an owner; b) change the owner of a native token. This allows an owner to create new native tokens for inflation model or fix total supply of a native token.
Summary
In this article, we illustrate how a blockchain system can support multi-native tokens so that a user could create a new native token and use it as payment and gas. Multiple native token support could greatly enhance existing dApps, and we believe it should be an important feature of next-generation of blockchain.