Summary
A standard interface for distributed tokens.
Abstract
The following proposal defines the standard interface designed to implement fungible and non-fungible tokens in TON blockchain within smart contracts, as well as UTXO-like cash systems. This proposal provides basic functionality to mint and transfer tokens between token wallets.
Motivation
The standard interface allows minting tokens in TON blockchain and creating crypto wallets that can be used to transfer tokens between contracts. The suggested standard differs considerably from Ethereum ERC20 and other smart contract token standards with single registry due to its distributed nature related to TON blockchain particularities. Given that TON has a storage fee, using an existing ERC20 standard design would cause excessive maintenance costs. Also, ERC20 is somewhat incompatible with the sharding architecture. Therefore, a Distributed Token standard is preferable.
The ERC20 sharding implementation (with an idea to simply shard its registry) has drawbacks mainly related to complicated and expansive management. TIP-3 is fully distributed and implies separate storage of each user’s balance. Somewhat similar to UTXO design, it does not require sending a balance remaining after a transfer to another output. Anyway, the suggested design allows simulating UTXO if needed.
Specification
Glossary
Root Token Wallet (RTW) - a smart contract that mints token and deploys TON Token Wallets.
TON Token Wallet (TTW) - a smart contract deployed by RTW that stores some tokens; can send tokens to any other contract but can accept tokens only from other TTW contracts.
Root Public Key (RPK) - the public key of RTW that is a part of RTW initial data.
Wallet Public Key (WPK) - the public key of TTW that is a part of TTW initial data.
Token owner - an off-chain party that deploys RTW contract and owns the RTW keypair.
Token wallet owner - an off-chain party that owns the WPK keypair.
NFT - non-fungible token.
UTXO - unspent transaction output.
Description
Instead of a single classic ERC20 smart contract that stores all balances there are token wallets and each stores only one token balance. TTW accept messages with tokens only from other TTW contracts after a special verification check performed by TTW.
Root token wallet
The RTW is deployed by a token owner. The RTW stores TTW contract code. The token owner can ask the RTW to deploy a TTW contract with the defined WPK and to transfer some tokens to it. In that case the RTW sends an internal ‘constructor’ message with code and initial data of a TTW to the pre-calculated TTW address. Optionally, the message’s body can contain an encoded call to the grant
function.
The RTW contains total amount of tokens (totalSupply
), number of granted tokens (totalGranted
) and optionally some token minting logic. (e.g. mint
function).
TON token wallet
Initial data of a TTW contains 0 token balance, RPK, WPK and the RTW address.
A TTW contract can be deployed not only by the RTW contract. Anyone who has the TTW code, the RPK, and the RTW address can calculate the token wallet address and to deploy the wallet, but this TTW will have 0 tokens at the start.
When the ‘constructor’ message is received, the TTW can load the StateInit
structure from the inbound message to store it in its persistent storage for later use. Otherwise, TTW can store TTW’s code as part of initial data.
The TTW contract receives external messages with transfer requests signed by the private key of token wallet keypair.
Note that the TTW cannot transfer more tokens than it has.
All TTW outbound internal messages must have the bounce
flag set to true
to prevent transferring tokens to undefined contracts or to receive tokens back in case of receiver contract error. If the TTW receives a bounced message (flag bounced == true
), it must process it properly: decode the function ID from body and check if it is a token transfer function. If yes, then decode the token value from body and increase its own token balance by this value.
The TTW contract does not use setcode
primitive and does not have function for arbitrary funds transfer except tokens.
Verification check
To verify that the sender contract is another token wallet, the TTW takes the following vertification steps:
- loads the
StateInit
structure from persistent storage with the TTW code and initial data; - rewrites WPK in Data with sender WPK;
- rebuilds the
StateInit
structure with TTW code and updated data; - calculates the representation hash from a cell where
StateInit
is placed;
Verification is succeeded if source address is equal to calculated hash. If not TTW should throw an exception.
After a verification, the TTW contract is sure that the sender contract is a TTW contract; now it must increase its token balance by value encoded in the inbound message body. Also, it can be sure that the sender decreases its token balance by the value of transferred tokens because it trusts its own code and it uses this code to calculate the sender address.
Gas
The sender TTW pays for gas used by the receiver TTW by attaching necessary amount of funds in value
field of the internal message.
Token Protocol Diagram
UTXO extension
NOTE: For fungible token wallets.
UTXO-based wallet allows to identify tokens as coins. The set of all UTXOs represents total token supply. Each coin has an owner and amount of tokens it represents. So one coin is one wallet. This model allows, for example, to process transactions with coins in parallel.
UTXO can be implemented using current proposal for TON token wallet but with some additional features.
Allowance interface should be completely disabled for UTXO wallet.
A TTW cannot transfer tokens to an existing wallet.
A TTW can only receive tokens once, at the deploy message processing.
Every token transfer results in creation of at least 2 new wallets. The first holds the transferred amount of tokens and the second holds the remaining token balance. After the transfer the original wallet must have a zero token balance. Zero-balance destination wallets should not be created.
UTXO Root contract doesn’t have grant
method, it can create wallets using deployWallet
only.
Zero tokens in deployWallet
should not be accepted.
To transfer tokens, a TTW has to perform the following steps within a single transaction:
-
calculate the address of a new TTW #1 using the public key provided by the destination wallet owner.
Note: see the
**verification check
** for the address calculation algorithm. -
deploy the new TTW #1 at the address calculated at step 1.
-
send some tokens to it in an internal deploy (internalTransfer) message.
-
calculate the address of a new TTW #2 using the new public key given by current wallet owner.
-
deploy the new TTW #2 to the address calculated at step 4 and send the remaining tokens to it in a deploy (internalTransfer) message.
Initial data of a UTXO wallet contains an additional utxoFlag
boolean flag set to false
. When a wallet receives tokens, it is switched to true
.
Functions
NOTE: All functions that can be called by external messages must check that an off-chain caller is a wallet owner by verifying a message body signature with the WPK.
TON Token wallet
There are 2 interfaces for fungible and non-fungible token wallets. Both of them must implement a Metadata interface.
Metadata interface
getName
OPTIONAL
Returns the name of the token, e.g. “MyToken”.
function getName() public (bytes name) (get-method)
getSymbol
OPTIONAL
Returns the token symbol, e.g. “GRM”.
function getSymbol() public (bytes symbol) (get-method)
getDecimals
OPTIONAL
Returns the number of decimals the token uses; e.g. 8, means to divide the token amount by 100,000,000 to get its user representation.
Examples:
token balance = 1500, decimals = 3, user representation = (1500 / 10^3) = 1.5 tokens.
token balance = 1500, decimals = 0, user representation = (1500 / 10^0) = 1500 tokens.
function getDecimals() public (uint8 decimals) (get-method)
getBalance
Returns number of tokens owned by a wallet.
function getBalance() public (uint256 balance) (get-method)
getWalletKey
Returns the wallet WPK.
function getWalletKey() public (uint256 walletKey) (get-method)
getRootAddress
Returns address of the root token wallet.
function getRootAddress() public (address rootAddress) (get-method)
Fungible interface
Wallet stores tokens as a uint256
integer in persistent data.
transfer
Called by an external message only.
Sends tokens to another token wallet. The function must call internalTransfer
function of destination wallet.
NOTE: the function must complete successfully if the token balance is less than the transfer value.
NOTE: zero-value transfers must be treated as normal transfers. Transfer to zero address is not allowed.
// dest - Destination token wallet address.
// tokens - Number of tokens to transfer.
// grams - Number of nanograms to transfer with internal message.
// Must be enough to pay for gas used by destination wallet.
function transfer(address dest, uint256 tokens, uint128 grams) public void
internalTransfer
Called by an internal message only. Receives tokens from other token wallets. The function must NOT call accept
or other buygas
primitives.
NOTE: the function must do a verification check that sender is a TTW using senderKey
.
// pubkey - WPK of sender contract.
// tokens - Amount of tokens to transfer.
function internalTransfer(uint256 senderKey, uint256 tokens) public void
accept
Called by an internal message only. Receives tokens from the RTW.
NOTE: the function must check that the message is sent by the RTW.
function accept(uint256 tokens) public void
approve
NOTE: see doc to understand why this interface is used https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit#
Called by an external message only.
Allows the spender
wallet to withdraw tokens from the wallet multiple times, up to the tokens
amount. If current spender
allowance is equal to remainingTokens
, then overwrite it with tokens
, otherwise, do nothing.
function approve(address spender, uint256 remainingTokens, uint256 tokens) public
allowance
Returns the amount of tokens the spender
is still allowed to withdraw from the wallet.
function allowance() public (address spender, uint256 remainingTokens) (get-method)
transferFrom
Called by an external message only; allows transferring tokens from the dest
wallet to to
the wallet. The function must call the internalTransferFrom
function of the dest
contract and attach certain grams
value to internal message.
NOTE: grams
value must be enough to pay for 2 contract calls: internalTransferFrom
function of dest
and internalTransfer
function of to
.
function transferFrom(address dest, address to, uint256 tokens, uint128 grams) public
internalTransferFrom
Called by an internal message only; transfers the tokens
amount from the wallet to the to
contract.
The function must throw
unless the message sender has a permission to withdraw such amount of tokens granted via the approve
function. The function must decrease the allowed number of tokens by the tokens
value and call the internalTransfer
function of the to
contract with tokens
as an argument. The function must throw
, if the current allowed amount of tokens is less than tokens
.
NOTE: transfers of 0 values must be treated as normal transfers.
function internalTransferFrom(address to, uint256 tokens) public
Non-fungible interface
Wallet stores tokens as dictionary of token ids and token count as uint256
integer. Token Id is an uint256
integer.
transfer
Sends tokens to another token wallet; initiated by an external message only. The function must call the internalTransfer function of the destination wallet**.**
NOTE: The function must complete successfully, if a token with a defined ID is not owned by the wallet.
NOTE: Zero token Ids and transfers to zero addresses not allowed.
function transfer(address dest, uint256 tokenId, uint128 grams) public void
internalTransfer
Called by other token wallet contracts to send tokens. Initiated by an internal message only. The function MUST NOT call accept
or other buygas
primitives.
function internalTransfer(uint256 pubkey, uint256 tokenId) public void
accept
Allows receiving tokens from the RTW.
NOTE: the function must check that the message is sent by the RTW.
function accept(uint256 tokenId) public void
approve
Changes or reaffirms the approved token wallet for an NFT. An approved wallet can withdraw tokens by ID with the internalTransferFrom
function.
function approve(address approved, uint256 tokenId) public
getTokenByIndex
Allows enumerating tokens owned by wallet. The function returns the token ID by its index.
function getTokenByIndex(uint256 index) public (uint256 tokenId) (get-method)
Index is ≥ 0
and < getBalance()
.
getApproved
Returns the approved address for a single NFT. The function returns zero if there is no approved address and throws exception if there is no token with tokenId
.
function getApproved(uint256 tokenId) public (address approved) (get-method)
transferFrom
Called by an external message only; allows transferring tokens from the dest
wallet to the to
address. The function must call the internalTransferFrom
function of dest
.
NOTE: grams
value must be enough to pay for 2 contract calls: internalTransferFrom
function of dest
and internalTransfer
function of to
.
function transferFrom(address dest, address to, uint256 tokenId, uint128 grams) public
internalTransferFrom
Called by an internal message only; transfers tokens with tokenId
from the wallet to the to
contract.
The function must throw
unless the message sender has a permission to withdraw token granted via approve
function. The function must remove token with tokenId
from wallet and call internalTransfer
function of the to
contract with tokenId
as argument.
The function must throw
, if the message sender is not approved for token withdrawal with tokenId
.
function internalTransferFrom(address to, uint256 tokenId) public
Extended interface
disapprove
Called by an external message only; cancels the permission to send tokens given to an approved wallet. The function must set the approved address and amount (or token Id) to 0.
function disapprove() public
Root Token Contract
When deployed, the RTW stores all tokens in the totalSupply
variable and the number of granted tokens — totalGranted
— is set to 0. totalGranted
must be below or equal to totalSupply
. Later, the RTW can mint
more tokens.
Metadata interface
getName
Returns the name of the token - e.g. “MyToken”.
function getName() public bytes (get-method)
getSymbol
Returns the token symbol. E.g. “GRM”.
function getSymbol() public bytes (get-method)
getDecimals
Returns the number of decimals the token uses; e.g. 8, means to divide the token amount by 1,000,000,00 to get its user representation.
function getDecimals() public uint8 (get-method)
getRootKey
Returns the RTW public key.
function getRootKey() public (uint256 rootKey) (get-method)
getTotalSupply
Returns the total number of minted tokens.
function getTotalSupply() public (uint256 totalSupply) (get-method)
getWalletCode
Returns code of token wallet (as tree of cells).
function getWalletCode() public (cell walletCode) (get-method)
getWalletAddress
Calculates wallet address with defined public key.
NOTE: see **verification check
** for details.
getWalletAddress(int8 workchainId, uint256 pubkey) public (address walletAddress) (get-method)
Root Token interface
deployWallet
Allows deploying the token wallet in a specified workchain and sending some tokens to it.
// Fungible version
function deployWallet(int8 workchainId, uint256 pubkey, uint256 tokens, uint128 grams) public (address walletAddress)
// Non-Fungible version
function deployWallet(int8 workchainId, uint256 pubkey, tokenId token, uint128 grams) public (address walletAddress)
grant
Called by an external message only; sends tokens to the TTW. The function must call the accept
function of the token wallet and increase the totalGranted
value.
// Fungible version
function grant(address dest, uint256 tokens, uint128 grams) public void
// Non-fungible version
function grant(address dest, uint256 tokenId, uint128 grams) public void
getTokenByIndex
Allows enumerating minted but not granted tokens in root wallet. The function returns the token ID by its index.
function getTokenByIndex(uint256 index) public (uint256 tokenId) (get-method)
mint
Called by an external message only; emits tokens and increases totalSupply
.
NOTE: the logic about how to check that token with defined tokenId
is already minted is not regulated in this specification. It is a matter of token implementation.
NOTE for NFT: the function returns id of minted token otherwise throws exception.
// Fungible version
function mint(uint256 tokens) public void
// Non-fungible version
function mint(uint256 tokenId) public (uint256 mintedId)
Implementation
// ==================================== Fungible ================================= //
struct allowance_info {
lazy<MsgAddressInt> spender;
TokensType remainingTokens;
};
// ===== TON Token wallet ===== //
__interface ITONTokenWallet {
// expected offchain constructor execution
__attribute__((internal, external, dyn_chain_parse))
void constructor(bytes name, bytes symbol, uint8 decimals,
uint256 root_public_key, uint256 wallet_public_key,
lazy<MsgAddressInt> root_address, cell code) = 1;
__attribute__((external, noaccept, dyn_chain_parse))
void transfer(lazy<MsgAddressInt> dest, TokensType tokens, WalletGramsType grams) = 2;
// Receive tokens from root
__attribute__((internal, noaccept))
void accept(TokensType tokens) = 3;
// Receive tokens from other wallet
__attribute__((internal, noaccept))
void internalTransfer(TokensType tokens, uint256 pubkey) = 4;
// getters
__attribute__((getter))
bytes getName() = 5;
__attribute__((getter))
bytes getSymbol() = 6;
__attribute__((getter))
uint8 getDecimals() = 7;
__attribute__((getter))
TokensType getBalance() = 8;
__attribute__((getter))
uint256 getWalletKey() = 9;
__attribute__((getter))
lazy<MsgAddressInt> getRootAddress() = 10;
__attribute__((getter))
allowance_info allowance() = 11;
// allowance interface
__attribute__((external, noaccept, dyn_chain_parse))
void approve(lazy<MsgAddressInt> spender, TokensType remainingTokens, TokensType tokens) = 12;
__attribute__((external, noaccept, dyn_chain_parse))
void transferFrom(lazy<MsgAddressInt> dest, lazy<MsgAddressInt> to, TokensType tokens,
WalletGramsType grams) = 13;
__attribute__((internal))
void internalTransferFrom(lazy<MsgAddressInt> to, TokensType tokens) = 14;
__attribute__((external, noaccept))
void disapprove() = 15;
};
struct DTONTokenWallet {
bytes name_;
bytes symbol_;
uint8 decimals_;
TokensType balance_;
uint256 root_public_key_;
uint256 wallet_public_key_;
lazy<MsgAddressInt> root_address_;
cell code_;
std::optional<allowance_info> allowance_;
};
struct ETONTokenWallet {
};
// ===== Root Token Contract ===== //
__interface IRootTokenContract {
// expected offchain constructor execution
__attribute__((internal, external, dyn_chain_parse))
void constructor(bytes name, bytes symbol, uint8 decimals,
uint256 root_public_key, cell wallet_code, TokensType total_supply) = 1;
__attribute__((external, noaccept, dyn_chain_parse))
lazy<MsgAddressInt> deployWallet(int8 workchain_id, uint256 pubkey, TokensType tokens, WalletGramsType grams) = 2;
__attribute__((external, noaccept, dyn_chain_parse))
void grant(lazy<MsgAddressInt> dest, TokensType tokens, WalletGramsType grams) = 3;
__attribute__((external, noaccept))
void mint(TokensType tokens) = 4;
__attribute__((getter))
bytes getName() = 5;
__attribute__((getter))
bytes getSymbol() = 6;
__attribute__((getter))
uint8 getDecimals() = 7;
__attribute__((getter))
uint256 getRootKey() = 8;
__attribute__((getter))
TokensType getTotalSupply() = 9;
__attribute__((getter))
TokensType getTotalGranted() = 10;
__attribute__((getter))
cell getWalletCode() = 11;
__attribute__((getter))
lazy<MsgAddressInt> getWalletAddress(int8 workchain_id, uint256 pubkey) = 12;
};
struct DRootTokenContract {
bytes name_;
bytes symbol_;
uint8 decimals_;
uint256 root_public_key_;
TokensType total_supply_;
TokensType total_granted_;
cell wallet_code_;
};
struct ERootTokenContract {
};
// ==================================== UTXO ================================= //
// ===== TON Token wallet (UTXO) ===== //
__interface ITONTokenWallet {
// expected offchain constructor execution
__attribute__((internal, external, dyn_chain_parse))
void constructor(bytes name, bytes symbol, uint8 decimals,
uint256 root_public_key, uint256 wallet_public_key,
lazy<MsgAddressInt> root_address, cell code) = 1;
// tokens and grams_dest will be sent to a new deployed {workchain_dest, pubkey_dest} wallet
// and the rest of tokens and the rest of gas will be sent to new {workchain_rest, pubkey_rest} wallet
__attribute__((external, noaccept, dyn_chain_parse))
void transferUTXO(int8 workchain_dest, uint256 pubkey_dest, int8 workchain_rest, uint256 pubkey_rest,
TokensType tokens, WalletGramsType grams_dest) = 2;
// Receive tokens from root
__attribute__((internal, noaccept))
void accept(TokensType tokens) = 3;
// Receive tokens from other wallet
__attribute__((internal, noaccept))
void internalTransfer(TokensType tokens, uint256 pubkey) = 4;
// getters
__attribute__((getter))
bytes getName() = 5;
__attribute__((getter))
bytes getSymbol() = 6;
__attribute__((getter))
uint8 getDecimals() = 7;
__attribute__((getter))
TokensType getBalance() = 8;
__attribute__((getter))
uint256 getWalletKey() = 9;
__attribute__((getter))
lazy<MsgAddressInt> getRootAddress() = 10;
};
struct DTONTokenWallet {
bool_t utxo_received_;
bytes name_;
bytes symbol_;
uint8 decimals_;
TokensType balance_;
uint256 root_public_key_;
uint256 wallet_public_key_;
lazy<MsgAddressInt> root_address_;
cell code_;
};
struct ETONTokenWallet {
};
// ===== Root Token Contract (UTXO) ===== //
__interface IRootTokenContract {
// expected offchain constructor execution
__attribute__((internal, external, dyn_chain_parse))
void constructor(bytes name, bytes symbol, uint8 decimals,
uint256 root_public_key, cell wallet_code, TokensType total_supply) = 1;
__attribute__((external, noaccept, dyn_chain_parse))
lazy<MsgAddressInt> deployWallet(int8 workchain_id, uint256 pubkey, TokensType tokens, WalletGramsType grams) = 2;
__attribute__((external, noaccept))
void mint(TokensType tokens) = 3;
__attribute__((getter))
bytes getName() = 4;
__attribute__((getter))
bytes getSymbol() = 5;
__attribute__((getter))
uint8 getDecimals() = 6;
__attribute__((getter))
uint256 getRootKey() = 7;
__attribute__((getter))
TokensType getTotalSupply() = 8;
__attribute__((getter))
TokensType getTotalGranted() = 9;
__attribute__((getter))
cell getWalletCode() = 10;
__attribute__((getter))
lazy<MsgAddressInt> getWalletAddress(int8 workchain_id, uint256 pubkey) = 11;
};
struct DRootTokenContract {
bytes name_;
bytes symbol_;
uint8 decimals_;
uint256 root_public_key_;
TokensType total_supply_;
TokensType total_granted_;
cell wallet_code_;
};
struct ERootTokenContract {
};
// ==================================== Non-fungible ================================= //
struct allowance_info {
lazy<MsgAddressInt> spender;
TokenId allowedToken;
};
// ===== TON Token wallet (Non-fungible) ===== //
__interface ITONTokenWallet {
// expected offchain constructor execution
__attribute__((internal, external, dyn_chain_parse))
void constructor(bytes name, bytes symbol, uint8 decimals,
uint256 root_public_key, uint256 wallet_public_key,
lazy<MsgAddressInt> root_address, cell code) = 1;
__attribute__((external, noaccept, dyn_chain_parse))
void transfer(lazy<MsgAddressInt> dest, TokenId tokenId, WalletGramsType grams) = 2;
// Receive tokens from root
__attribute__((internal, noaccept))
void accept(TokenId tokenId) = 3;
// Receive tokens from other wallet
__attribute__((internal, noaccept))
void internalTransfer(TokenId tokenId, uint256 pubkey) = 4;
// getters
__attribute__((getter))
bytes getName() = 5;
__attribute__((getter))
bytes getSymbol() = 6;
__attribute__((getter))
uint8 getDecimals() = 7;
__attribute__((getter))
TokensType getBalance() = 8;
__attribute__((getter))
uint256 getWalletKey() = 9;
__attribute__((getter))
lazy<MsgAddressInt> getRootAddress() = 10;
__attribute__((getter))
allowance_info allowance() = 11;
__attribute__((getter))
TokenId getTokenByIndex(TokensType index) = 12;
__attribute__((getter))
lazy<MsgAddressInt> getApproved(TokenId tokenId) = 13;
// allowance interface
__attribute__((external, noaccept, dyn_chain_parse))
void approve(lazy<MsgAddressInt> spender, TokenId tokenId) = 14;
__attribute__((external, noaccept, dyn_chain_parse))
void transferFrom(lazy<MsgAddressInt> dest, lazy<MsgAddressInt> to, TokenId tokenId,
WalletGramsType grams) = 15;
__attribute__((internal))
void internalTransferFrom(lazy<MsgAddressInt> to, TokenId tokenId) = 16;
__attribute__((external, noaccept))
void disapprove() = 17;
};
struct DTONTokenWallet {
bytes name_;
bytes symbol_;
uint8 decimals_;
TokensType balance_;
uint256 root_public_key_;
uint256 wallet_public_key_;
lazy<MsgAddressInt> root_address_;
cell code_;
std::optional<allowance_info> allowance_;
dict_set<TokenId> tokens_;
};
struct ETONTokenWallet {
};
// ===== Root Token Contract (Non-fungible) ===== //
__interface IRootTokenContract {
// expected offchain constructor execution
__attribute__((internal, external, dyn_chain_parse))
void constructor(bytes name, bytes symbol, uint8 decimals,
uint256 root_public_key, cell wallet_code) = 1;
__attribute__((external, noaccept, dyn_chain_parse))
lazy<MsgAddressInt> deployWallet(int8 workchain_id, uint256 pubkey, TokenId tokenId, WalletGramsType grams) = 2;
__attribute__((external, noaccept, dyn_chain_parse))
void grant(lazy<MsgAddressInt> dest, TokenId tokenId, WalletGramsType grams) = 3;
__attribute__((external, noaccept))
TokenId mint(TokenId tokenId) = 4;
__attribute__((getter))
bytes getName() = 5;
__attribute__((getter))
bytes getSymbol() = 6;
__attribute__((getter))
uint8 getDecimals() = 7;
__attribute__((getter))
uint256 getRootKey() = 8;
__attribute__((getter))
TokensType getTotalSupply() = 9;
__attribute__((getter))
TokensType getTotalGranted() = 10;
__attribute__((getter))
cell getWalletCode() = 11;
__attribute__((getter))
TokenId getLastMintedToken() = 12
__attribute__((getter))
lazy<MsgAddressInt> getWalletAddress(int8 workchain_id, uint256 pubkey) = 13;
};
struct DRootTokenContract {
bytes name_;
bytes symbol_;
uint8 decimals_;
uint256 root_public_key_;
TokensType total_supply_;
TokensType total_granted_;
cell wallet_code_;
TokenId last_minted_token_;
dict_set<TokenId> tokens_;
};
struct ERootTokenContract {
};
Limitations:
It is impossible to provide “ownerOf” function
function ownerOf(uint256 _tokenId) external view returns (address);
for non fungible tokens since there is no single registry of such tokens. It is impossible to recognise from its ID who it belongs to.
Note: we believe “it’s a feature, not a limitation”.