Skip to main content

Tokens

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


Fungible tokens

Supply policies

When issuing a token you choose one of three supply policies:

PolicyTokenSupply.TypeDescription
Lockable"Lockable"Unlimited minting until LockTokenSupply 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

Issuing a token

import "github.com/mintlayer/go-sdk/wallet"

wc := wallet.New("http://127.0.0.1:3034")

authorityAddr, err := wc.NewAddress(ctx, 0)

result, err := wc.IssueToken(ctx, wallet.IssueTokenParams{
Account: 0,
DestinationAddress: authorityAddr,
Metadata: wallet.TokenMetadata{
TokenTicker: "MYTOKEN",
NumberOfDecimals: 2,
MetadataURI: "https://example.com/token-metadata.json",
TokenSupply: wallet.TokenSupply{Type: "Lockable"},
IsFreezable: true,
},
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("token id: %s\n tx id: %s\n", result.TokenID, result.TxID)

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

For a Fixed supply cap, set the Content field:

TokenSupply: wallet.TokenSupply{
Type: "Fixed",
Content: &wallet.Amount{Atoms: "1000000"}, // hard cap
},

Minting

Wait for the issuance transaction to confirm before minting.

mintResult, err := wc.MintTokens(ctx, wallet.MintParams{
Account: 0,
TokenID: result.TokenID,
Address: recipientAddr,
Amount: wallet.Amount{Atoms: "100000"}, // in smallest token units
})
fmt.Printf("minted tx id: %s\n", mintResult.TxID)

Unminting

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

unmintResult, err := wc.UnmintTokens(ctx, wallet.UnmintParams{
Account: 0,
TokenID: "ttml1...",
Amount: wallet.Amount{Atoms: "50000"},
})

Locking supply

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

lockResult, err := wc.LockTokenSupply(ctx, wallet.LockSupplyParams{
AccountIndex: 0,
TokenID: "ttml1...",
})

Note: the field is AccountIndex, not Account.

Freezing and unfreezing

Freezing prevents all transfers. If IsUnfreezable is true, the authority can unfreeze later.

freezeResult, err := wc.FreezeToken(ctx, wallet.FreezeParams{
Account: 0,
TokenID: "ttml1...",
IsUnfreezable: true,
})

unfreezeResult, err := wc.UnfreezeToken(ctx, wallet.UnfreezeParams{
Account: 0,
TokenID: "ttml1...",
})

Transferring authority

changeResult, err := wc.ChangeTokenAuthority(ctx, wallet.ChangeAuthorityParams{
Account: 0,
TokenID: "ttml1...",
Address: newAuthorityAddr,
})

Sending tokens

sendResult, err := wc.SendToken(ctx, wallet.TokenSendParams{
Account: 0,
TokenID: "ttml1...",
Address: recipientAddr,
Amount: wallet.Amount{Atoms: "10000"},
})

NFTs

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

Issuing an NFT

ownerAddr, err := wc.NewAddress(ctx, 0)

result, err := wc.IssueNFT(ctx, wallet.IssueNFTParams{
Account: 0,
DestinationAddress: ownerAddr,
Metadata: wallet.NFTMetadata{
Name: "My NFT",
Description: "A unique digital collectible",
Ticker: "MYNFT",
MediaHash: "sha256hexhash...",
MediaURI: stringPtr("https://example.com/media.png"),
IconURI: stringPtr("https://example.com/icon.png"),
},
})
fmt.Printf("nft id: %s\n", result.TokenID)

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


Reading token state from the indexer

import "github.com/mintlayer/go-sdk/indexer"

idxClient := indexer.New("http://127.0.0.1:3000")

// Find by ticker
ids, err := idxClient.FindTokensByTicker(ctx, "MYTOKEN", indexer.PageOpts{Items: 10})

// Full token info
token, err := idxClient.GetToken(ctx, "ttml1...")
fmt.Printf("ticker: %s\n", token.TokenTicker)
fmt.Printf("supply: %s\n", token.CirculatingSupply.Decimal)
fmt.Printf("locked: %v\n", token.IsLocked)
fmt.Printf("frozen: %v\n", token.Frozen)

// Transaction history
txs, err := idxClient.GetTokenTransactions(ctx, "ttml1...", indexer.PageOpts{Items: 20})

// NFT
nft, err := idxClient.GetNFT(ctx, "nftid1...")
fmt.Printf("owner: %s\n", nft.Owner)
fmt.Printf("name: %s\n", nft.Metadata.Name)

// Tokens where address is authority
tokenIDs, err := idxClient.GetTokenAuthority(ctx, "mxtc1authority...")

Building token transactions manually

Use the wasm package for full control over token transactions.

Issue a token

import mintlayer "github.com/mintlayer/go-sdk/wasm"

c, _ := mintlayer.New(ctx)

tip, _ := idxClient.GetTip(ctx)

issuanceFee, err := c.FungibleTokenIssuanceFee(tip.BlockHeight, mintlayer.Mainnet)

// Predict the token ID
tokenIDStr, err := c.GetTokenId(encodedInputs, tip.BlockHeight, mintlayer.Mainnet)

issueOutput, err := c.EncodeOutputIssueFungibleToken(
authorityAddr,
"MYTOKEN",
"https://example.com/metadata.json",
2, // decimals
mintlayer.TotalSupplyLockable,
nil, // supplyAmount: only required for TotalSupplyFixed
mintlayer.FreezableYes,
tip.BlockHeight,
mintlayer.Mainnet,
)

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

Mint tokens

// Get the current nonce from the indexer
tokenInfo, err := idxClient.GetToken(ctx, tokenIDStr)

mintInput, err := c.EncodeInputForMintTokens(
tokenIDStr,
mintlayer.NewAmount("100000"),
tokenInfo.NextNonce,
mintlayer.Mainnet,
)

mintOutput, err := c.EncodeOutputTokenTransfer(
mintlayer.NewAmount("100000"),
recipientAddr,
tokenIDStr,
mintlayer.Mainnet,
)

Freeze a token

freezeInput, err := c.EncodeInputForFreezeToken(
tokenIDStr,
mintlayer.TokenUnfreezableYes, // can be unfrozen later
tokenInfo.NextNonce,
mintlayer.Mainnet,
)

Token fees

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

tip, _ := idxClient.GetTip(ctx)

issuanceFee, _ := c.FungibleTokenIssuanceFee(tip.BlockHeight, mintlayer.Mainnet)
mintFee, _ := c.TokenSupplyChangeFee(tip.BlockHeight, mintlayer.Mainnet)
freezeFee, _ := c.TokenFreezeFee(tip.BlockHeight, mintlayer.Mainnet)
authorityFee, _ := c.TokenChangeAuthorityFee(tip.BlockHeight, mintlayer.Mainnet)

These fees must be included as coin inputs in the transaction (or deducted from change).