Skip to main content

Tokens and NFTs

Mintlayer supports on-chain fungible tokens and NFTs. The wallet daemon manages the full lifecycle. The mintlayer.wasm module provides low-level encoding for manual transaction flows.


Fungible tokens (wallet daemon)

Supply policies

When issuing a token you choose one of three supply policies (TokenSupply dataclass, wire field type):

PolicyTokenSupply.typeDescription
Lockable"Lockable"Unlimited minting until lock_token_supply is called, after which the supply is frozen permanently
Unlimited"Unlimited"Minting is always allowed with no cap
Fixed"Fixed"A cap is set at issuance; supply cannot exceed it

For a Fixed cap, set the content field to an Amount:

TokenSupply(type="Fixed", content=Amount(atoms="1000000"))  # hard cap

content is omitted from the wire for the other two policies.

Issuing a token

from mintlayer.wallet import (
Amount,
Client,
IssueTokenParams,
TokenMetadata,
TokenSupply,
)

wc = Client("http://127.0.0.1:3034")

authority_addr = wc.new_address(0)

result = wc.issue_token(
IssueTokenParams(
account=0,
destination_address=authority_addr,
metadata=TokenMetadata(
token_ticker="MYTOKEN",
number_of_decimals=2,
metadata_uri="https://example.com/token-metadata.json",
token_supply=TokenSupply(type="Lockable"),
is_freezable=True,
),
)
)
print(f"token id: {result.token_id}\ntx id: {result.tx_id}")

The destination_address becomes the authority address: the key that controls future token operations (minting, freezing, authority transfer). Keep it secure.

Minting

Wait for the issuance transaction to confirm before minting.

from mintlayer.wallet import MintParams

mint_result = wc.mint_tokens(
MintParams(
account=0,
token_id=result.token_id,
address=recipient_addr,
amount=Amount(atoms="100000"), # in smallest token units
)
)
print(f"minted tx id: {mint_result.tx_id}")

Unminting

Returns tokens to an unminted state (removes them from circulation without burning).

from mintlayer.wallet import UnmintParams

unmint_result = wc.unmint_tokens(
UnmintParams(
account=0,
token_id="ttml1...",
amount=Amount(atoms="50000"),
)
)

Locking supply

After locking, the supply policy becomes Fixed at the current circulating supply. This operation is irreversible.

from mintlayer.wallet import LockSupplyParams

lock_result = wc.lock_token_supply(
LockSupplyParams(
account_index=0,
token_id="ttml1...",
)
)

Quirk: the field is account_index, not account — the daemon route expects the wire key account_index (every other token method uses account).

Freezing and unfreezing

Freezing prevents all transfers. If is_unfreezable is True, the authority can unfreeze later.

from mintlayer.wallet import FreezeParams, UnfreezeParams

freeze_result = wc.freeze_token(
FreezeParams(
account=0,
token_id="ttml1...",
is_unfreezable=True,
)
)

unfreeze_result = wc.unfreeze_token(
UnfreezeParams(
account=0,
token_id="ttml1...",
)
)

Transferring authority

from mintlayer.wallet import ChangeAuthorityParams

change_result = wc.change_token_authority(
ChangeAuthorityParams(
account=0,
token_id="ttml1...",
address=new_authority_addr,
)
)

Sending tokens

from mintlayer.wallet import TokenSendParams

send_result = wc.send_token(
TokenSendParams(
account=0,
token_id="ttml1...",
address=recipient_addr,
amount=Amount(atoms="10000"),
)
)

send_token is an alias of token_send on the transactions mixin — both call the daemon's token_send route.


NFTs (wallet daemon)

NFTs are non-fungible tokens. Each has unique on-chain metadata.

Issuing an NFT

from mintlayer.wallet import IssueNFTParams, NFTMetadata

owner_addr = wc.new_address(0)

result = wc.issue_nft(
IssueNFTParams(
account=0,
destination_address=owner_addr,
metadata=NFTMetadata(
name="My NFT",
description="A unique digital collectible",
ticker="MYNFT",
media_hash="sha256hexhash...",
media_uri="https://example.com/media.png",
icon_uri="https://example.com/icon.png",
# creator and additional_metadata_uri default to None
),
)
)
print(f"nft id: {result.token_id}")

NFTs cannot be minted after issuance: each issuance transaction creates exactly one NFT.


Reading token state from the indexer

from mintlayer.indexer import Client, PageOpts

idx = Client("http://127.0.0.1:3000")

# Find by ticker
ids = idx.find_tokens_by_ticker("MYTOKEN", PageOpts(items=10))

# Full token info
token = idx.get_token("ttml1...")
print(f"ticker: {token.token_ticker}")
print(f"supply: {token.circulating_supply.decimal}")
print(f"locked: {token.is_locked}")
print(f"frozen: {token.frozen}")

# Transaction history
txs = idx.get_token_transactions("ttml1...", PageOpts(items=20))

# NFT
nft = idx.get_nft("nftid1...")
print(f"owner: {nft.owner}")
print(f"name: {nft.metadata.name}")

# Tokens where address is authority
token_ids = idx.get_token_authority("mtc1qauthority...")

Building token transactions manually

Use the mintlayer.wasm module for full control over token transactions.

Issue a token

from mintlayer.wasm import Amount, Client, FreezableToken, Network, TotalSupply

c = Client()
try:
tip = idx.get_tip()

issuance_fee = c.fungible_token_issuance_fee(tip.block_height, Network.MAINNET)

# Predict the token ID
token_id_str = c.get_token_id(encoded_inputs, tip.block_height, Network.MAINNET)

issue_output = c.encode_output_issue_fungible_token(
authority_addr,
"MYTOKEN",
"https://example.com/metadata.json",
2, # decimals
TotalSupply.LOCKABLE,
None, # supply_amount: only required for TotalSupply.FIXED
FreezableToken.YES,
tip.block_height,
Network.MAINNET,
)
finally:
c.close()

For a capped supply, pass the cap as the supply_amount:

issue_output = c.encode_output_issue_fungible_token(
authority_addr,
"MYTOKEN",
"https://example.com/metadata.json",
2,
TotalSupply.FIXED,
Amount.from_atoms("1000000"), # required exactly for TotalSupply.FIXED
FreezableToken.NO,
tip.block_height,
Network.MAINNET,
)

Include the issuance fee as a separate output or subtract it from an input UTXO.

Issue an NFT

issue_nft_output = c.encode_output_issue_nft(
token_id=token_id_str, # derived with get_token_id (same scheme as FTs)
authority=owner_addr,
name="My NFT",
ticker="MYNFT",
description="A unique digital collectible",
media_hash=bytes.fromhex("..."), # 32-byte sha256 of the media
creator=bytes.fromhex("..."), # or None
media_uri="https://example.com/media.png",
icon_uri=None,
additional_metadata_uri=None,
current_block_height=tip.block_height,
network=Network.MAINNET,
)

Mint tokens

# Get the current nonce from the indexer
token_info = idx.get_token(token_id_str)

mint_input = c.encode_input_for_mint_tokens(
token_id_str,
Amount.from_atoms("100000"),
token_info.next_nonce,
Network.MAINNET,
)

mint_output = c.encode_output_token_transfer(
Amount.from_atoms("100000"),
recipient_addr,
token_id_str,
Network.MAINNET,
)

Freeze a token

from mintlayer.wasm import TokenUnfreezable

freeze_input = c.encode_input_for_freeze_token(
token_id_str,
TokenUnfreezable.YES, # can be unfrozen later
token_info.next_nonce,
Network.MAINNET,
)

Assemble, sign, and submit manual token transactions exactly as in transactions.md.


Token fees

Protocol fees apply to many token operations. Query them from the WASM client before building transactions:

tip = idx.get_tip()

issuance_fee = c.fungible_token_issuance_fee(tip.block_height, Network.MAINNET)
nft_fee = c.nft_issuance_fee(tip.block_height, Network.MAINNET)
mint_fee = c.token_supply_change_fee(tip.block_height, Network.MAINNET)
freeze_fee = c.token_freeze_fee(tip.block_height, Network.MAINNET)
authority_fee = c.token_change_authority_fee(tip.block_height, Network.MAINNET)

These fees must be included as coin inputs in the transaction (or deducted from change). The data_deposit_fee applies to DataDeposit outputs.