Gear Multiple Token (gMT)
Introduction
A standard interface for programs that manage multiple token types. A single deployed program may include any combination of fungible tokens, non-fungible tokens or other configurations (e.g. semi-fungible tokens).
The idea is simple and seeks to create a program interface that can represent and control any number of fungible and non-fungible token types. In this way, the gMT token can do the same functions as gFT and gNFT token, and even both at the same time. Can be considered as analog of ERC-1155.
This article explains the programming interface, data structure, basic functions and explains their purpose. It can be used as is or modified to suit your own scenarios. Anyone can easily create their own application and run it on the Gear Network. The source code is available on GitHub.
Multiple token implementation
Consider the main functionality of the program
- mint(id, amount, token_metadata) is a function that creates a new token with the given id for the account.
metadata
can include any information about the token: it can be a link to a specific resource, a description of the token, etc. Metadata is available only in case of non-fungible tokens (amount equals 1); - mint_batch(ids, amounts, tokens_metadata) is a function similar to the
mint
function, but it creates several tokens at the same time; - burn(id, amount) is a function that removes the token with the mentioned
id
andamount
from the program; - burn_batch(ids, amounts) is a function similar to the
burn
function, but it removes several tokens at the same time; - balance_of(account, id) is a function that provides information about the amount of tokens the specified
account
has with the givenid
; - balance_of_batch(accounts, ids) is a function similar to the
balance_of
function, but it returns info for multiple accounts and tokens; - transfer_from(from, to, id, amount) is a function that allows to make a token transfer from
token_id
to a quantityamount
; - batch_transfer_from(from, to, ids, amounts) is a function similar to the
transfer_from
function, but it allows the transfer of multiple tokens at once; - approve(account) - is a function that gives the
account
access to use tokens; - revoke_approval(account) - is a function that cancels an account's access to use tokens;
- transform(id, amount, nfts) - is a function that converts user tokens into multiple non-fungible tokens.
The multiple token program contains the following information:
pub struct SimpleMtk {
pub tokens: MtkData,
pub creator: ActorId,
pub supply: HashMap<TokenId, u128>,
}
where the MtkData
are defined as follows:
pub struct MtkData {
pub name: String,
pub symbol: String,
pub base_uri: String,
pub balances: HashMap<TokenId, HashMap<ActorId, u128>>,
pub approvals: HashMap<ActorId, HashSet<ActorId>>,
pub token_metadata: HashMap<TokenId, TokenMetadata>,
pub owners: HashMap<TokenId, ActorId>,
}
name
- multitoken namesymbol
- multitoken symbolbase_uri
- multitoken base URIbalances
- stores the ownership information of all tokensapprovals
- contains information about the approvals that have been madetoken_metadata
- token metadata relative to their id (only non-fungible tokens)owners
- owner of non-fungible tokens according to idcreator
- creator of the multitoken collectionsupply
- counting the amount of tokens issued
Initialization
To initialize a program, it needs to be passed name
, symbol
and base_uri
information:
pub struct InitMtk {
pub name: String,
pub symbol: String,
pub base_uri: String,
}
Action
pub enum MtkAction {
Mint {
id: TokenId,
amount: u128,
token_metadata: Option<TokenMetadata>,
},
Burn {
id: TokenId,
amount: u128,
},
BalanceOf {
account: ActorId,
id: TokenId,
},
BalanceOfBatch {
accounts: Vec<ActorId>,
ids: Vec<TokenId>,
},
MintBatch {
ids: Vec<TokenId>,
amounts: Vec<u128>,
tokens_metadata: Vec<Option<TokenMetadata>>,
},
TransferFrom {
from: ActorId,
to: ActorId,
id: TokenId,
amount: u128,
},
BatchTransferFrom {
from: ActorId,
to: ActorId,
ids: Vec<TokenId>,
amounts: Vec<u128>,
},
BurnBatch {
ids: Vec<TokenId>,
amounts: Vec<u128>,
},
Approve {
account: ActorId,
},
RevokeApproval {
account: ActorId,
},
Transform {
id: TokenId,
amount: u128,
nfts: Vec<BurnToNFT>,
},
}
Event
pub enum MtkEvent {
Transfer {
from: ActorId,
to: ActorId,
ids: Vec<TokenId>,
amounts: Vec<u128>,
},
BalanceOf(Vec<BalanceReply>),
Approval {
from: ActorId,
to: ActorId,
},
RevokeApproval {
from: ActorId,
to: ActorId,
},
}
Program implementation
#[no_mangle]
extern fn handle() {
let action: MtkAction = msg::load().expect("Failed to decode `MtkAction` message.");
let multi_token = unsafe { CONTRACT.as_mut().expect("`SimpleMtk` is not initialized.") };
let reply = match action {
MtkAction::Mint {
id,
amount,
token_metadata,
} => multi_token.mint(&msg::source(), vec![id], vec![amount], vec![token_metadata]),
MtkAction::Burn { id, amount } => multi_token.burn(vec![id], vec![amount]),
MtkAction::BalanceOf { account, id } => multi_token.balance_of(vec![account], vec![id]),
MtkAction::BalanceOfBatch { accounts, ids } => multi_token.balance_of(accounts, ids),
MtkAction::MintBatch {
ids,
amounts,
tokens_metadata,
} => multi_token.mint(&msg::source(), ids, amounts, tokens_metadata),
MtkAction::TransferFrom {
from,
to,
id,
amount,
} => multi_token.transfer_from(&from, &to, vec![id], vec![amount]),
MtkAction::BatchTransferFrom {
from,
to,
ids,
amounts,
} => multi_token.transfer_from(&from, &to, ids, amounts),
MtkAction::BurnBatch { ids, amounts } => multi_token.burn(ids, amounts),
MtkAction::Approve { account } => multi_token.approve(&account),
MtkAction::RevokeApproval { account } => multi_token.revoke_approval(&account),
MtkAction::Transform { id, amount, nfts } => multi_token.transform(id, amount, nfts),
};
msg::reply(reply, 0).expect("Failed to encode or reply with `Result<MtkEvent, MtkError>`.");
}