Skip to main content

Issue a Token

This guide covers the full MLS-01 token lifecycle in Rust: issuing, minting, transferring, and managing supply and authority. The same workflow exists for the command line (which explains the underlying concepts: token id, authority address, reserve vs circulating supply), Go, and JavaScript.

Issuing via the wallet daemon

use mintlayer_sdk::wallet::{self, IssueTokenParams, TokenMetadata, TokenSupply, TxOptions};

let c = wallet::Client::new("http://127.0.0.1:3034");
let authority = c.new_address(0).await?;

let issue = c.issue_token(IssueTokenParams {
account: 0,
destination_address: authority.clone(), // becomes the token authority
metadata: TokenMetadata {
token_ticker: "MYTOKEN".into(),
number_of_decimals: 2,
metadata_uri: "https://example.com/token".into(),
token_supply: TokenSupply::Lockable,
is_freezable: false,
},
options: TxOptions::default(),
}).await?;
println!("token id: {} (tx {})", issue.token_id, issue.tx_id);

Supply policies: TokenSupply::Lockable (unlimited minting until locked), TokenSupply::Unlimited, or TokenSupply::Fixed(Amount) for a hard cap. The destination address becomes the token authority: the key that controls all later token operations. Keep it secure.

Metadata

Publish metadata at the metadata URI following the Token Metadata Standards (MLS-01 schema) so wallets and explorers can render your token. The URI can be updated later by the authority.

Minting, unminting and supply locking

Wait for the issuance transaction to confirm before minting.

use mintlayer_sdk::wallet::{Amount, MintParams, TxOptions, UnmintParams};

c.mint_tokens(MintParams {
account: 0,
token_id: issue.token_id.clone(),
address: "mtc1q_recipient...".into(),
amount: Amount::from_atoms(100_000),
options: TxOptions::default(),
}).await?;

// Removes tokens from circulation (the tx burns them).
c.unmint_tokens(UnmintParams {
account: 0,
token_id: issue.token_id.clone(),
amount: Amount::from_atoms(50_000),
options: TxOptions::default(),
}).await?;

// Irreversible: fixes the supply at the current circulating amount.
// Note the field is account_index, not account.
c.lock_token_supply(wallet::LockSupplyParams {
account_index: 0,
token_id: issue.token_id.clone(),
options: TxOptions::default(),
}).await?;

Freezing and authority change

use mintlayer_sdk::wallet::{FreezeParams, TxOptions, UnfreezeParams};

// is_unfreezable: true means the authority can unfreeze later.
c.freeze_token(FreezeParams {
account: 0, token_id: issue.token_id.clone(),
is_unfreezable: true, options: TxOptions::default(),
}).await?;

c.unfreeze_token(UnfreezeParams {
account: 0, token_id: issue.token_id.clone(), options: TxOptions::default(),
}).await?;

c.change_token_authority(wallet::ChangeAuthorityParams {
account: 0,
token_id: issue.token_id.clone(),
address: "mtc1q_new_authority...".into(),
options: TxOptions::default(),
}).await?;

Reading token state

use mintlayer_sdk::indexer::{Client, PageOpts};

let idx = Client::new("http://127.0.0.1:3000");
let token = idx.token(&issue.token_id).await?;
println!("ticker {} supply {} locked {}", token.token_ticker,
token.circulating_supply.decimal, token.is_locked);
let txs = idx.token_transactions(&issue.token_id, PageOpts::default()).await?;

Full-custody issuance

Without the wallet daemon, build the issuance output with the crypto module and pair it with a coin input covering the issuance fee. get_token_id predicts the token id from the inputs; parameters are validated against the chain config. See Tokens and NFTs in the SDK reference for the complete flow including minting via nonce inputs.