Skip to content

Changing Mosaic Supply⚓︎

INTERMEDIATE

Mosaics created with a mutable supply can have their total supply increased or decreased after creation.

Only the mosaic creator can change the supply. Supply changes affect only the creator's balance: minted units are added to it, and burned units are removed from it. The balances of all other accounts that hold the mosaic remain unchanged.

This tutorial shows how to change a mosaic's supply by minting and burning units.

Prerequisites⚓︎

Before you start, make sure to:

Full Code⚓︎

The following is the complete code listing for this tutorial. A detailed, step-by-step explanation follows in the next section.

import json
import os
import time
import urllib.request

from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.NemFacade import NemFacade
from symbolchain.nc import Amount
from symbolchain.nem.FeeCalculator import calculate_transaction_fee

NODE_URL = os.getenv('NODE_URL', 'http://libertalia.nemtest.net:7890')
print(f'Using node {NODE_URL}')


# Helper function to announce a transaction
def announce_transaction(payload, label):
    announce_path = '/transaction/announce'
    print(f'Announcing {label} to {announce_path}')
    request = urllib.request.Request(
        f'{NODE_URL}{announce_path}',
        data=payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    with urllib.request.urlopen(request) as announce_response:
        result = json.loads(announce_response.read().decode())
    print(f'  Result: {result["message"]}')
    return result['message']


# Helper function to fetch the current mosaic supply
def fetch_supply(mosaic):
    supply_path = f'/mosaic/supply?mosaicId={mosaic}'
    with urllib.request.urlopen(
        f'{NODE_URL}{supply_path}'
    ) as supply_response:
        supply_info = json.loads(supply_response.read().decode())
    return supply_info['supply']


# Helper function to wait for transaction confirmation
def wait_for_confirmation(tx_hash, label):
    status_path = f'/transaction/get?hash={tx_hash}'
    print(f'Waiting for {label} confirmation from {status_path}')
    is_confirmed = False
    for _ in range(120):
        try:
            with urllib.request.urlopen(
                f'{NODE_URL}{status_path}'
            ) as status_response:
                confirmed = json.loads(status_response.read().decode())
                height = confirmed['meta']['height']
                print(f'{label} confirmed in block {height}')
                is_confirmed = True
                break
        except urllib.error.HTTPError:
            print('  Transaction status: pending')
        time.sleep(1)
    if not is_confirmed:
        print(f'{label} confirmation took too long.')


SIGNER_PRIVATE_KEY = os.getenv(
    'SIGNER_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000')
signer_key_pair = NemFacade.KeyPair(PrivateKey(SIGNER_PRIVATE_KEY))

facade = NemFacade('testnet')
signer_address = facade.network.public_key_to_address(
    signer_key_pair.public_key)
print(f'Signer address: {signer_address}')

namespace_name = os.getenv('NAMESPACE', 'my_namespace')
mosaic_name = os.getenv('MOSAIC', 'token')
mosaic_id = f'{namespace_name}:{mosaic_name}'
print(f'Mosaic ID: {mosaic_id}')

try:
    # --- INCREASING SUPPLY (MINTING) ---
    print('\n--- Increasing supply (minting) ---')

    print(f'Supply before minting: {fetch_supply(mosaic_id)}')

    increase_tx = facade.create_transaction_from_descriptor({
        'type': 'mosaic_supply_change_transaction_v1',
        'mosaic_id': {
            'namespace_id': {'name': namespace_name},
            'name': mosaic_name
        },
        'action': 'increase',
        'delta': 500
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)
    increase_tx.fee = Amount(calculate_transaction_fee(increase_tx))

    signature = facade.sign_transaction(signer_key_pair, increase_tx)
    json_payload = facade.transaction_factory.attach_signature(
        increase_tx, signature)
    print('Built supply increase transaction:')
    print(json.dumps(increase_tx.to_json(), indent=2))
    if 'SUCCESS' == announce_transaction(json_payload, 'supply increase'):
        wait_for_confirmation(
            facade.hash_transaction(increase_tx), 'supply increase')
        print(f'Supply after minting: {fetch_supply(mosaic_id)}')
    else:
        print('Supply increase rejected')

    # --- DECREASING SUPPLY (BURNING) ---
    print('\n--- Decreasing supply (burning) ---')

    decrease_tx = facade.create_transaction_from_descriptor({
        'type': 'mosaic_supply_change_transaction_v1',
        'mosaic_id': {
            'namespace_id': {'name': namespace_name},
            'name': mosaic_name
        },
        'action': 'decrease',
        'delta': 500
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)
    decrease_tx.fee = Amount(calculate_transaction_fee(decrease_tx))

    signature = facade.sign_transaction(signer_key_pair, decrease_tx)
    json_payload = facade.transaction_factory.attach_signature(
        decrease_tx, signature)
    print('Built supply decrease transaction:')
    print(json.dumps(decrease_tx.to_json(), indent=2))
    if 'SUCCESS' == announce_transaction(json_payload, 'supply decrease'):
        wait_for_confirmation(
            facade.hash_transaction(decrease_tx), 'supply decrease')
        print(f'Supply after burning: {fetch_supply(mosaic_id)}')
    else:
        print('Supply decrease rejected')

except Exception as error:
    print(error)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    NemFacade,
    calculateTransactionFee,
    descriptors,
    models
} from 'symbol-sdk/nem';

const NODE_URL = process.env.NODE_URL ||
    'http://libertalia.nemtest.net:7890';
console.log('Using node', NODE_URL);

// Helper function to announce a transaction
async function announceTransaction(payload, label) {
    const announcePath = '/transaction/announce';
    console.log(`Announcing ${label} to ${announcePath}`);
    const response = await fetch(`${NODE_URL}${announcePath}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: payload
    });
    if (!response.ok)
        throw new Error(`HTTP ${response.status}`);
    const result = await response.json();
    console.log('  Result:', result.message);
    return result.message;
}

// Helper function to fetch the current mosaic supply
async function fetchSupply(mosaic) {
    const supplyPath = `/mosaic/supply?mosaicId=${mosaic}`;
    const supplyResponse = await fetch(`${NODE_URL}${supplyPath}`);
    const supplyInfo = await supplyResponse.json();
    return supplyInfo.supply;
}

// Helper function to wait for transaction confirmation
async function waitForConfirmation(transactionHash, label) {
    const statusPath = `/transaction/get?hash=${transactionHash}`;
    console.log(`Waiting for ${label} confirmation from`, statusPath);
    let isConfirmed = false;
    for (let attempt = 1; 120 >= attempt; ++attempt) {
        const response = await fetch(`${NODE_URL}${statusPath}`);
        if (!response.ok) {
            console.log('  Transaction status: pending');
            await new Promise(resolve => { setTimeout(resolve, 1000); });
        } else {
            const confirmed = await response.json();
            console.log(`${label} confirmed in block`,
                confirmed.meta.height);
            isConfirmed = true;
            break;
        }
    }
    if (!isConfirmed)
        console.warn(`${label} confirmation took too long.`);
}

const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const signerKeyPair = new NemFacade.KeyPair(
    new PrivateKey(SIGNER_PRIVATE_KEY));

const facade = new NemFacade('testnet');
const signerAddress = facade.network.publicKeyToAddress(
    signerKeyPair.publicKey);
console.log('Signer address:', signerAddress.toString());

const namespaceName = process.env.NAMESPACE || 'my_namespace';
const mosaicName = process.env.MOSAIC || 'token';
const mosaicId = `${namespaceName}:${mosaicName}`;
console.log('Mosaic ID:', mosaicId);

try {
    // --- INCREASING SUPPLY (MINTING) ---
    console.log('\n--- Increasing supply (minting) ---');

    console.log('Supply before minting:', await fetchSupply(mosaicId));

    const increaseTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicSupplyChangeTransactionV1Descriptor(
            new descriptors.MosaicIdDescriptor(
                new descriptors.NamespaceIdDescriptor(namespaceName),
                mosaicName),
            models.MosaicSupplyChangeAction.INCREASE,
            new models.Amount(500n)),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);
    increaseTx.fee = new models.Amount(
        calculateTransactionFee(increaseTx));

    const increaseSignature = facade.signTransaction(
        signerKeyPair, increaseTx);
    const increasePayload = facade.transactionFactory.static
        .attachSignature(increaseTx, increaseSignature);
    console.log('Built supply increase transaction:');
    console.dir(increaseTx.toJson(), { colors: true });
    const increaseResult = await announceTransaction(
        increasePayload, 'supply increase');
    if ('SUCCESS' === increaseResult) {
        await waitForConfirmation(
            facade.hashTransaction(increaseTx).toString(),
            'supply increase');
        console.log('Supply after minting:', await fetchSupply(mosaicId));
    } else {
        console.log('Supply increase rejected');
    }

    // --- DECREASING SUPPLY (BURNING) ---
    console.log('\n--- Decreasing supply (burning) ---');

    const decreaseTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicSupplyChangeTransactionV1Descriptor(
            new descriptors.MosaicIdDescriptor(
                new descriptors.NamespaceIdDescriptor(namespaceName),
                mosaicName),
            models.MosaicSupplyChangeAction.DECREASE,
            new models.Amount(500n)),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);
    decreaseTx.fee = new models.Amount(
        calculateTransactionFee(decreaseTx));

    const decreaseSignature = facade.signTransaction(
        signerKeyPair, decreaseTx);
    const decreasePayload = facade.transactionFactory.static
        .attachSignature(decreaseTx, decreaseSignature);
    console.log('Built supply decrease transaction:');
    console.dir(decreaseTx.toJson(), { colors: true });
    const decreaseResult = await announceTransaction(
        decreasePayload, 'supply decrease');
    if ('SUCCESS' === decreaseResult) {
        await waitForConfirmation(
            facade.hashTransaction(decreaseTx).toString(),
            'supply decrease');
        console.log('Supply after burning:', await fetchSupply(mosaicId));
    } else {
        console.log('Supply decrease rejected');
    }

} catch (e) {
    console.error(e.message, '| Cause:', e.cause?.code ?? 'unknown');
}

Download source

//JAVA 21+
//DEPS org.symbol:symbol-sdk:3.3.3

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.symbol.sdk.CryptoTypes;
import org.symbol.sdk.facade.NemFacade;
import org.symbol.sdk.nem.FeeCalculator;
import org.symbol.sdk.nem.KeyPair;
import org.symbol.sdk.nem.NemTransactionFactory;
import org.symbol.sdk.nem.descriptors.*;
import org.symbol.sdk.nem.models.Amount;
import org.symbol.sdk.nem.models.MosaicSupplyChangeAction;
import org.symbol.sdk.nem.models.Transaction;

public final class ChangeMosaicSupply {
    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    private static final HttpClient HTTP_CLIENT =
        HttpClient.newHttpClient();

    private final String nodeUrl = System.getenv().getOrDefault(
        "NODE_URL", "http://libertalia.nemtest.net:7890");

    private final NemFacade facade = new NemFacade("testnet");

    private String announceTransaction(
        final String payload,
        final String label
    ) throws IOException, InterruptedException {
        final String announcePath = "/transaction/announce";
        System.out.printf("Announcing %s to %s%n", label, announcePath);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + announcePath))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        if (2 != response.statusCode() / 100)
            throw new IOException("HTTP " + response.statusCode());
        final String result = JSON_MAPPER.readTree(response.body())
            .get("message").asText();
        System.out.printf("  Result: %s%n", result);
        return result;
    }

    private long fetchSupply(
        final String mosaicId
    ) throws IOException, InterruptedException {
        final String path = String.format(
            "/mosaic/supply?mosaicId=%s", mosaicId);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        return JSON_MAPPER.readTree(response.body())
            .get("supply").asLong();
    }

    private void waitForConfirmation(
        final String transactionHash,
        final String label
    ) throws IOException, InterruptedException {
        final String statusPath = String.format(
            "/transaction/get?hash=%s", transactionHash);
        System.out.printf("Waiting for %s confirmation from %s%n",
            label, statusPath);
        boolean isConfirmed = false;
        for (int attempt = 1; 120 >= attempt; ++attempt) {
            final HttpRequest request = HttpRequest.newBuilder(
                URI.create(nodeUrl + statusPath)).GET().build();
            final HttpResponse<String> response = HTTP_CLIENT.send(
                request, BodyHandlers.ofString());
            if (2 != response.statusCode() / 100) {
                System.out.println("  Transaction status: pending");
                Thread.sleep(1000);
            } else {
                final JsonNode confirmed = JSON_MAPPER.readTree(
                    response.body());
                System.out.printf("%s confirmed in block %s%n", label,
                    confirmed.get("meta").get("height").asText());
                isConfirmed = true;
                break;
            }
        }
        if (!isConfirmed)
            System.out.printf("%s confirmation took too long.%n", label);
    }

    public static void main(final String[] args) {
        try {
            new ChangeMosaicSupply().run();
        } catch (final Exception ex) {
            System.out.println(null == ex.getMessage()
                ? ex.toString()
                : ex.getMessage());
        }
    }

    private void run() throws IOException, InterruptedException {
        System.out.printf("Using node %s%n", nodeUrl);


        final String privateKey = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKey));
        System.out.printf("Signer address: %s%n",
            facade.network.publicKeyToAddress(
                signerKeyPair.getPublicKey()));
        final String namespaceName = System.getenv().getOrDefault(
            "NAMESPACE", "my_namespace");
        final String mosaicName = System.getenv().getOrDefault(
            "MOSAIC", "token");
        final String mosaicId = namespaceName + ":" + mosaicName;
        System.out.printf("Mosaic ID: %s%n", mosaicId);

        final MosaicIdDescriptor mosaicIdDescriptor =
            new MosaicIdDescriptor(
                new NamespaceIdDescriptor(namespaceName.getBytes(UTF_8)),
                mosaicName.getBytes(UTF_8));

        // --- INCREASING SUPPLY (MINTING) ---
        System.out.println("\n--- Increasing supply (minting) ---");

        System.out.printf("Supply before minting: %d%n",
            fetchSupply(mosaicId));
        final Transaction increaseTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicSupplyChangeTransactionV1Descriptor(
                    mosaicIdDescriptor,
                    MosaicSupplyChangeAction.INCREASE,
                    new Amount(500)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);
        increaseTx.setFee(new Amount(
            FeeCalculator.calculateTransactionFee(increaseTx)));

        final CryptoTypes.Signature increaseSignature =
            facade.signTransaction(signerKeyPair, increaseTx);
        final String increasePayload =
            NemTransactionFactory.attachSignature(
                increaseTx, increaseSignature);
        System.out.println("Built supply increase transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(increaseTx.toJson()));
        final String increaseResult = announceTransaction(
            increasePayload, "supply increase");
        if ("SUCCESS".equals(increaseResult)) {
            waitForConfirmation(facade.hashTransaction(increaseTx)
                .toString(), "supply increase");
            System.out.printf("Supply after minting: %d%n",
                fetchSupply(mosaicId));
        } else {
            System.out.println("Supply increase rejected");
        }

        // --- DECREASING SUPPLY (BURNING) ---
        System.out.println("\n--- Decreasing supply (burning) ---");

        final Transaction decreaseTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicSupplyChangeTransactionV1Descriptor(
                    mosaicIdDescriptor,
                    MosaicSupplyChangeAction.DECREASE,
                    new Amount(500)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);
        decreaseTx.setFee(new Amount(
            FeeCalculator.calculateTransactionFee(decreaseTx)));

        final CryptoTypes.Signature decreaseSignature =
            facade.signTransaction(signerKeyPair, decreaseTx);
        final String decreasePayload =
            NemTransactionFactory.attachSignature(
                decreaseTx, decreaseSignature);
        System.out.println("Built supply decrease transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(decreaseTx.toJson()));
        final String decreaseResult = announceTransaction(
            decreasePayload, "supply decrease");
        if ("SUCCESS".equals(decreaseResult)) {
            waitForConfirmation(facade.hashTransaction(decreaseTx)
                .toString(), "supply decrease");
            System.out.printf("Supply after burning: %d%n",
                fetchSupply(mosaicId));
        } else {
            System.out.println("Supply decrease rejected");
        }
    }
}

Download source

Code Explanation⚓︎

Changing a mosaic's supply uses the MosaicSupplyChangeTransactionV1 transaction. This tutorial announces two of them: one to mint new units and one to burn them.

Because both transactions are submitted the same way, the snippet defines two helpers, and , which announce a transaction and then poll the network until it is included in a block.

A third helper, , reads the mosaic's current supply from /mosaic/supply GET, so that the effect of each transaction can be observed.

Setting Up the Account and the Mosaic⚓︎

SIGNER_PRIVATE_KEY = os.getenv(
    'SIGNER_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000')
signer_key_pair = NemFacade.KeyPair(PrivateKey(SIGNER_PRIVATE_KEY))

facade = NemFacade('testnet')
signer_address = facade.network.public_key_to_address(
    signer_key_pair.public_key)
print(f'Signer address: {signer_address}')

namespace_name = os.getenv('NAMESPACE', 'my_namespace')
mosaic_name = os.getenv('MOSAIC', 'token')
mosaic_id = f'{namespace_name}:{mosaic_name}'
print(f'Mosaic ID: {mosaic_id}')
const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const signerKeyPair = new NemFacade.KeyPair(
    new PrivateKey(SIGNER_PRIVATE_KEY));

const facade = new NemFacade('testnet');
const signerAddress = facade.network.publicKeyToAddress(
    signerKeyPair.publicKey);
console.log('Signer address:', signerAddress.toString());

const namespaceName = process.env.NAMESPACE || 'my_namespace';
const mosaicName = process.env.MOSAIC || 'token';
const mosaicId = `${namespaceName}:${mosaicName}`;
console.log('Mosaic ID:', mosaicId);
        final String privateKey = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKey));
        System.out.printf("Signer address: %s%n",
            facade.network.publicKeyToAddress(
                signerKeyPair.getPublicKey()));
        final String namespaceName = System.getenv().getOrDefault(
            "NAMESPACE", "my_namespace");
        final String mosaicName = System.getenv().getOrDefault(
            "MOSAIC", "token");
        final String mosaicId = namespaceName + ":" + mosaicName;
        System.out.printf("Mosaic ID: %s%n", mosaicId);

The snippet reads the signer's private key from the SIGNER_PRIVATE_KEY environment variable, which defaults to a test key if not set. The signer must be the creator of the mosaic.

The mosaic to update is read from the NAMESPACE and MOSAIC environment variables, which default to my_namespace:token.

Use a mosaic created by the signer

By default, the code uses the test account referenced by SIGNER_PRIVATE_KEY and a mosaic named my_namespace:token.

If you come from the Creating a Mosaic tutorial, set the SIGNER_PRIVATE_KEY, NAMESPACE, and MOSAIC environment variables to match the account and mosaic you created there, or any other mosaic that the signer owns.

Increasing Supply (Minting)⚓︎

    print(f'Supply before minting: {fetch_supply(mosaic_id)}')

    increase_tx = facade.create_transaction_from_descriptor({
        'type': 'mosaic_supply_change_transaction_v1',
        'mosaic_id': {
            'namespace_id': {'name': namespace_name},
            'name': mosaic_name
        },
        'action': 'increase',
        'delta': 500
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)
    increase_tx.fee = Amount(calculate_transaction_fee(increase_tx))

    signature = facade.sign_transaction(signer_key_pair, increase_tx)
    json_payload = facade.transaction_factory.attach_signature(
        increase_tx, signature)
    print('Built supply increase transaction:')
    print(json.dumps(increase_tx.to_json(), indent=2))
    if 'SUCCESS' == announce_transaction(json_payload, 'supply increase'):
        wait_for_confirmation(
            facade.hash_transaction(increase_tx), 'supply increase')
        print(f'Supply after minting: {fetch_supply(mosaic_id)}')
    else:
        print('Supply increase rejected')
    console.log('Supply before minting:', await fetchSupply(mosaicId));

    const increaseTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicSupplyChangeTransactionV1Descriptor(
            new descriptors.MosaicIdDescriptor(
                new descriptors.NamespaceIdDescriptor(namespaceName),
                mosaicName),
            models.MosaicSupplyChangeAction.INCREASE,
            new models.Amount(500n)),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);
    increaseTx.fee = new models.Amount(
        calculateTransactionFee(increaseTx));

    const increaseSignature = facade.signTransaction(
        signerKeyPair, increaseTx);
    const increasePayload = facade.transactionFactory.static
        .attachSignature(increaseTx, increaseSignature);
    console.log('Built supply increase transaction:');
    console.dir(increaseTx.toJson(), { colors: true });
    const increaseResult = await announceTransaction(
        increasePayload, 'supply increase');
    if ('SUCCESS' === increaseResult) {
        await waitForConfirmation(
            facade.hashTransaction(increaseTx).toString(),
            'supply increase');
        console.log('Supply after minting:', await fetchSupply(mosaicId));
    } else {
        console.log('Supply increase rejected');
    }
        System.out.printf("Supply before minting: %d%n",
            fetchSupply(mosaicId));
        final Transaction increaseTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicSupplyChangeTransactionV1Descriptor(
                    mosaicIdDescriptor,
                    MosaicSupplyChangeAction.INCREASE,
                    new Amount(500)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);
        increaseTx.setFee(new Amount(
            FeeCalculator.calculateTransactionFee(increaseTx)));

        final CryptoTypes.Signature increaseSignature =
            facade.signTransaction(signerKeyPair, increaseTx);
        final String increasePayload =
            NemTransactionFactory.attachSignature(
                increaseTx, increaseSignature);
        System.out.println("Built supply increase transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(increaseTx.toJson()));
        final String increaseResult = announceTransaction(
            increasePayload, "supply increase");
        if ("SUCCESS".equals(increaseResult)) {
            waitForConfirmation(facade.hashTransaction(increaseTx)
                .toString(), "supply increase");
            System.out.printf("Supply after minting: %d%n",
                fetchSupply(mosaicId));
        } else {
            System.out.println("Supply increase rejected");
        }

The snippet first reads the mosaic's supply, so that the newly minted units can be seen once the transaction is confirmed.

To mint new units, the transaction sets:

  • Transaction type: Mosaic supply change transactions use MosaicSupplyChangeTransactionV1.

  • : The fully qualified name of the mosaic to update.

  • : The value increase mints new units.

  • : The number of whole units to add. The resulting total supply cannot exceed the maximum supply.

    The cap is expressed in atomic units

    The maximum supply is fixed at \(9 \cdot 10^{15}\) atomic units, while is expressed in whole units.

    The maximum value of therefore depends on the mosaic's divisibility:

    \[ \text{max\_whole\_units} = \frac{9 \cdot 10^{15}}{10^{\text{divisibility}}} \]

    The mosaic in this tutorial has a divisibility of 2, so one whole unit corresponds to \(100\) atomic units and the supply can grow up to \(9 \cdot 10^{13}\) whole units.

The transaction fee is then calculated and the transaction is signed, announced, and confirmed, following the same process as in the Transfer XEM tutorial. The facade adds the mosaic creator as signer and derives the timestamp and deadline from the two-hour deadline duration.

Mosaic supply change transactions pay a fixed transaction fee of 0.15 XEM, as shown in the fee schedule.

Once confirmed, the supply is read again to show the resulting supply. The minted units are credited to the creator's account.

Decreasing Supply (Burning)⚓︎

    decrease_tx = facade.create_transaction_from_descriptor({
        'type': 'mosaic_supply_change_transaction_v1',
        'mosaic_id': {
            'namespace_id': {'name': namespace_name},
            'name': mosaic_name
        },
        'action': 'decrease',
        'delta': 500
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)
    decrease_tx.fee = Amount(calculate_transaction_fee(decrease_tx))

    signature = facade.sign_transaction(signer_key_pair, decrease_tx)
    json_payload = facade.transaction_factory.attach_signature(
        decrease_tx, signature)
    print('Built supply decrease transaction:')
    print(json.dumps(decrease_tx.to_json(), indent=2))
    if 'SUCCESS' == announce_transaction(json_payload, 'supply decrease'):
        wait_for_confirmation(
            facade.hash_transaction(decrease_tx), 'supply decrease')
        print(f'Supply after burning: {fetch_supply(mosaic_id)}')
    else:
        print('Supply decrease rejected')
    const decreaseTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicSupplyChangeTransactionV1Descriptor(
            new descriptors.MosaicIdDescriptor(
                new descriptors.NamespaceIdDescriptor(namespaceName),
                mosaicName),
            models.MosaicSupplyChangeAction.DECREASE,
            new models.Amount(500n)),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);
    decreaseTx.fee = new models.Amount(
        calculateTransactionFee(decreaseTx));

    const decreaseSignature = facade.signTransaction(
        signerKeyPair, decreaseTx);
    const decreasePayload = facade.transactionFactory.static
        .attachSignature(decreaseTx, decreaseSignature);
    console.log('Built supply decrease transaction:');
    console.dir(decreaseTx.toJson(), { colors: true });
    const decreaseResult = await announceTransaction(
        decreasePayload, 'supply decrease');
    if ('SUCCESS' === decreaseResult) {
        await waitForConfirmation(
            facade.hashTransaction(decreaseTx).toString(),
            'supply decrease');
        console.log('Supply after burning:', await fetchSupply(mosaicId));
    } else {
        console.log('Supply decrease rejected');
    }
        final Transaction decreaseTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicSupplyChangeTransactionV1Descriptor(
                    mosaicIdDescriptor,
                    MosaicSupplyChangeAction.DECREASE,
                    new Amount(500)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);
        decreaseTx.setFee(new Amount(
            FeeCalculator.calculateTransactionFee(decreaseTx)));

        final CryptoTypes.Signature decreaseSignature =
            facade.signTransaction(signerKeyPair, decreaseTx);
        final String decreasePayload =
            NemTransactionFactory.attachSignature(
                decreaseTx, decreaseSignature);
        System.out.println("Built supply decrease transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(decreaseTx.toJson()));
        final String decreaseResult = announceTransaction(
            decreasePayload, "supply decrease");
        if ("SUCCESS".equals(decreaseResult)) {
            waitForConfirmation(facade.hashTransaction(decreaseTx)
                .toString(), "supply decrease");
            System.out.printf("Supply after burning: %d%n",
                fetchSupply(mosaicId));
        } else {
            System.out.println("Supply decrease rejected");
        }

To burn existing units, the same transaction type is used with set to decrease and set to the number of whole units to remove.

The burned units are taken from the creator's account, so only units the creator still holds can be burned. Units already distributed to other accounts remain in their balances, and the transaction fails if the creator's own balance does not cover .

Once confirmed, the supply is read again to show the burned units. Because this tutorial increases and then decreases the supply by the same amount, the final supply matches the value before the changes.

Output⚓︎

The output shown below corresponds to a typical run of the program.

Using node http://libertalia.nemtest.net:7890
Signer address: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
Mosaic ID: my_namespace:token

--- Increasing supply (minting) ---
Supply before minting: 1000
Built supply increase transaction:
{
  "type": 16386,
  "version": 1,
  "network": 152,
  "timestamp": 356366691,
  "signer_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
  "signature": "47A61EFCFFBF72C84F4BC06D45422F62CF4A1173D2F58820A3A9EE7B1EECD85641E3EAAC6260F3D3B90FBD5EB167921F4FFF5952BF17DF583BED6C9355D4ED08",
  "fee": "150000",
  "deadline": 356373891,
  "mosaic_id": {
    "namespace_id": {
      "name": "6d795f6e616d657370616365"
    },
    "name": "746f6b656e"
  },
  "action": 1,
  "delta": "500"
}
Announcing supply increase to /transaction/announce
  Result: SUCCESS
Waiting for supply increase confirmation from /transaction/get?hash=7D9EAF91CF91F699E2BD50F16F3F21872319570D64BECA35F24742F6C8F03993
  Transaction status: pending
  Transaction status: pending
  Transaction status: pending
supply increase confirmed in block 699605
Supply after minting: 1500

--- Decreasing supply (burning) ---
Built supply decrease transaction:
{
  "type": 16386,
  "version": 1,
  "network": 152,
  "timestamp": 356366691,
  "signer_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
  "signature": "F3383125C6849AD2C4AFC020331B44F489F0A62302DF76AD15363350C2E99507CD04F6AD3F4026157FF42719EBFC6A7B49122AAC5696338DBCFE07E56A25E509",
  "fee": "150000",
  "deadline": 356373891,
  "mosaic_id": {
    "namespace_id": {
      "name": "6d795f6e616d657370616365"
    },
    "name": "746f6b656e"
  },
  "action": 2,
  "delta": "500"
}
Announcing supply decrease to /transaction/announce
  Result: SUCCESS
Waiting for supply decrease confirmation from /transaction/get?hash=2E5F803F30B941671C86481B6646B8AD252D05CBF41F77B438D508F1232E99B5
  Transaction status: pending
  Transaction status: pending
  Transaction status: pending
supply decrease confirmed in block 699606
Supply after burning: 1000

Some highlights from the output:

  • Supply before minting (line 6): The mosaic starts with a supply of 1000 whole units.

  • Supply increase (lines 23-24): The increase action with a delta of 500 mints new units into the creator's balance.

  • Supply after minting (line 33): The supply rises to 1500 whole units.

  • Supply decrease (lines 52-53): The decrease action with the same delta burns those units.

  • Supply after burning (line 62): The supply returns to 1000, because the increase and decrease cancel out.

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Mint mosaic supply , MosaicSupplyChangeTransactionV1
Burn mosaic supply , MosaicSupplyChangeTransactionV1
Calculate the transaction fee
Read the mosaic supply /mosaic/supply GET

Next Steps⚓︎

Now that you can change a mosaic's supply, you can: