Skip to content

Creating a Mosaic⚓︎

INTERMEDIATE

Mosaics represent assets on the NEM blockchain, such as currencies, collectibles, or access rights. Unlike tokens on other platforms, NEM mosaics are supported directly at the protocol level and require no additional coding to use.

Their properties are configurable to support various use cases, from simple currencies to tokens with custom supply and transfer rules.

Every mosaic belongs to a registered namespace, which provides the first half of its fully qualified name, such as my_namespace:token. A namespace must therefore be registered before a mosaic can be created.

This tutorial shows how to create a mosaic under an existing namespace and configure its initial properties.

Prerequisites⚓︎

Before you start, make sure to:

Additionally, review the Transfer XEM tutorial to understand how transactions are announced and confirmed.

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_mosaic_rental_fee,
    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 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}')

try:
    # Build the mosaic ID
    namespace_name = os.getenv('NAMESPACE', 'my_namespace')
    mosaic_name = os.getenv('MOSAIC', f'token_{int(time.time())}')
    mosaic_id = f'{namespace_name}:{mosaic_name}'
    print(f'Creating mosaic: {mosaic_id}')

    # Define the mosaic
    mosaic_definition = {
        'owner_public_key': signer_key_pair.public_key,
        'id': {
            'namespace_id': {'name': namespace_name},
            'name': mosaic_name
        },
        'description': 'My tutorial mosaic',
        'properties': [
            {'property_': {
                'name': b'divisibility', 'value': b'2'}},
            {'property_': {
                'name': b'initialSupply', 'value': b'1000'}},
            {'property_': {
                'name': b'supplyMutable', 'value': b'true'}},
            {'property_': {
                'name': b'transferable', 'value': b'true'}}
        ]
    }

    # Build the mosaic definition transaction
    rental_fee = calculate_mosaic_rental_fee()
    print(f'  Mosaic creation fee: {rental_fee / 1_000_000} XEM')

    transaction = facade.create_transaction_from_descriptor({
        'type': 'mosaic_definition_transaction_v1',
        'rental_fee_sink': 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        'rental_fee': rental_fee,
        'mosaic_definition': mosaic_definition
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)

    # Calculate and attach the transaction fee
    fee = calculate_transaction_fee(transaction)
    transaction.fee = Amount(fee)
    print(f'  Transaction fee: {fee / 1_000_000} XEM')

    # Sign and generate final payload
    signature = facade.sign_transaction(signer_key_pair, transaction)
    json_payload = facade.transaction_factory.attach_signature(
        transaction, signature)
    print('Built mosaic definition transaction:')
    print(json.dumps(transaction.to_json(), indent=2))

    # Announce the transaction
    announce_result = announce_transaction(
        json_payload, 'mosaic definition')

    # Wait for confirmation
    if 'SUCCESS' == announce_result:
        transaction_hash = facade.hash_transaction(transaction)
        print(f'Transaction hash: {transaction_hash}')
        wait_for_confirmation(transaction_hash, 'mosaic definition')
    else:
        print(f'Transaction rejected: {announce_result}')

    # Retrieve the mosaic
    definition_path = f'/mosaic/definition?mosaicId={mosaic_id}'
    print(f'Fetching mosaic information from {definition_path}')
    with urllib.request.urlopen(
        f'{NODE_URL}{definition_path}'
    ) as response:
        mosaic_info = json.loads(response.read().decode())
        properties = {
            prop['name']: prop['value']
            for prop in mosaic_info['properties']
        }
        print('Mosaic information:')
        print(f'  Creator: {mosaic_info["creator"]}')
        print(f'  Divisibility: {properties["divisibility"]}')
        print(f'  Initial supply: {properties["initialSupply"]}')
        print(f'  Supply mutable: {properties["supplyMutable"]}')
        print(f'  Transferable: {properties["transferable"]}')

except Exception as error:
    print(error)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    Address,
    NemFacade,
    calculateMosaicRentalFee,
    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 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());

try {
    // Build the mosaic ID
    const namespaceName = process.env.NAMESPACE || 'my_namespace';
    const mosaicName = process.env.MOSAIC ||
        `token_${Math.floor(Date.now() / 1000)}`;
    const mosaicId = `${namespaceName}:${mosaicName}`;
    console.log('Creating mosaic:', mosaicId);

    // Define the mosaic
    const mosaicDefinition = new descriptors.MosaicDefinitionDescriptor(
        signerKeyPair.publicKey,
        new descriptors.MosaicIdDescriptor(
            new descriptors.NamespaceIdDescriptor(namespaceName),
            mosaicName),
        'My tutorial mosaic',
        [
            ['divisibility', '2'],
            ['initialSupply', '1000'],
            ['supplyMutable', 'true'],
            ['transferable', 'true']
        ].map(([name, value]) =>
            new descriptors.SizePrefixedMosaicPropertyDescriptor(
                new descriptors.MosaicPropertyDescriptor(
                    name, value))));

    // Build the mosaic definition transaction
    const rentalFee = calculateMosaicRentalFee();
    console.log('  Mosaic creation fee:',
        `${Number(rentalFee) / 1_000_000} XEM`);

    const transaction = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicDefinitionTransactionV1Descriptor(
            mosaicDefinition,
            new Address('TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC'),
            new models.Amount(rentalFee)),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);

    // Calculate and attach the transaction fee
    const fee = calculateTransactionFee(transaction);
    transaction.fee = new models.Amount(fee);
    console.log('  Transaction fee:', `${Number(fee) / 1_000_000} XEM`);

    // Sign and generate final payload
    const signature = facade.signTransaction(signerKeyPair, transaction);
    const jsonPayload = facade.transactionFactory.static.attachSignature(
        transaction, signature);
    console.log('Built mosaic definition transaction:');
    console.dir(transaction.toJson(), { colors: true });

    // Announce the transaction
    const announceResult = await announceTransaction(
        jsonPayload, 'mosaic definition');

    // Wait for confirmation
    if ('SUCCESS' === announceResult) {
        const transactionHash = facade.hashTransaction(transaction)
            .toString();
        console.log('Transaction hash:', transactionHash);
        await waitForConfirmation(transactionHash, 'mosaic definition');
    } else {
        console.log('Transaction rejected:', announceResult);
    }

    // Retrieve the mosaic
    const definitionPath = `/mosaic/definition?mosaicId=${mosaicId}`;
    console.log('Fetching mosaic information from', definitionPath);
    const definitionResponse = await fetch(
        `${NODE_URL}${definitionPath}`);
    const mosaicInfo = await definitionResponse.json();
    const properties = Object.fromEntries(
        mosaicInfo.properties.map(prop => [prop.name, prop.value]));
    console.log('Mosaic information:');
    console.log('  Creator:', mosaicInfo.creator);
    console.log('  Divisibility:', properties.divisibility);
    console.log('  Initial supply:', properties.initialSupply);
    console.log('  Supply mutable:', properties.supplyMutable);
    console.log('  Transferable:', properties.transferable);

} 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 java.util.List;
import java.util.Map;

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.Address;
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.Transaction;

public final class CreateMosaic {
    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 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 CreateMosaic().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()));

        // Build the mosaic ID
        final String namespaceName = System.getenv().getOrDefault(
            "NAMESPACE", "my_namespace");
        final String mosaicName = System.getenv().getOrDefault(
            "MOSAIC", "token_" + System.currentTimeMillis() / 1000);
        final String mosaicId = namespaceName + ":" + mosaicName;
        System.out.printf("Creating mosaic: %s%n", mosaicId);

        // Define the mosaic
        final MosaicDefinitionDescriptor mosaicDefinition =
            new MosaicDefinitionDescriptor(
                signerKeyPair.getPublicKey(),
                new MosaicIdDescriptor(
                    new NamespaceIdDescriptor(
                        namespaceName.getBytes(UTF_8)),
                    mosaicName.getBytes(UTF_8)),
                "My tutorial mosaic".getBytes(UTF_8),
                List.of(
                    Map.entry("divisibility", "2"),
                    Map.entry("initialSupply", "1000"),
                    Map.entry("supplyMutable", "true"),
                    Map.entry("transferable", "true"))
                    .stream()
                    .map(property ->
                        new SizePrefixedMosaicPropertyDescriptor(
                            new MosaicPropertyDescriptor(
                                property.getKey().getBytes(UTF_8),
                                property.getValue().getBytes(UTF_8))))
                    .toList(),
                null);

        // Build the mosaic definition transaction
        final long rentalFee = FeeCalculator.calculateMosaicRentalFee();
        System.out.printf("  Mosaic creation fee: %s XEM%n",
            (double) rentalFee / 1_000_000);
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new MosaicDefinitionTransactionV1Descriptor(
                    mosaicDefinition,
                    new Address(
                        "TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC"),
                    new Amount(rentalFee)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);

        // Calculate and attach the transaction fee
        final long fee = FeeCalculator.calculateTransactionFee(
            transaction);
        transaction.setFee(new Amount(fee));
        System.out.printf("  Transaction fee: %s XEM%n",
            (double) fee / 1_000_000);

        // Sign and generate final payload
        final CryptoTypes.Signature signature = facade.signTransaction(
            signerKeyPair, transaction);
        final String payload = NemTransactionFactory.attachSignature(
            transaction, signature);
        System.out.println("Built mosaic definition transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(transaction.toJson()));

        // Announce the transaction
        final String result = announceTransaction(
            payload, "mosaic definition");

        // Wait for confirmation
        if ("SUCCESS".equals(result)) {
            final String transactionHash = facade.hashTransaction(
                transaction).toString();
            System.out.printf("Transaction hash: %s%n", transactionHash);
            waitForConfirmation(
                transactionHash, "mosaic definition");
        } else {
            System.out.printf("Transaction rejected: %s%n", result);
        }

        // Retrieve the mosaic
        final String path = "/mosaic/definition?mosaicId=" + mosaicId;
        System.out.printf("Fetching mosaic information from %s%n", path);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        final JsonNode info = JSON_MAPPER.readTree(response.body());
        System.out.println("Mosaic information:");
        System.out.printf("  Creator: %s%n",
            info.get("creator").asText());
        for (final JsonNode item : info.get("properties"))
            System.out.printf("  %s: %s%n",
                item.get("name").asText(), item.get("value").asText());

    }
}

Download source

Code Explanation⚓︎

Setting Up the Account⚓︎

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}')
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());
        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()));

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's address is derived from the public key. This account will own the created mosaic and must also own the namespace that will hold it.

Choosing the Mosaic Name⚓︎

    # Build the mosaic ID
    namespace_name = os.getenv('NAMESPACE', 'my_namespace')
    mosaic_name = os.getenv('MOSAIC', f'token_{int(time.time())}')
    mosaic_id = f'{namespace_name}:{mosaic_name}'
    print(f'Creating mosaic: {mosaic_id}')
    // Build the mosaic ID
    const namespaceName = process.env.NAMESPACE || 'my_namespace';
    const mosaicName = process.env.MOSAIC ||
        `token_${Math.floor(Date.now() / 1000)}`;
    const mosaicId = `${namespaceName}:${mosaicName}`;
    console.log('Creating mosaic:', mosaicId);
        // Build the mosaic ID
        final String namespaceName = System.getenv().getOrDefault(
            "NAMESPACE", "my_namespace");
        final String mosaicName = System.getenv().getOrDefault(
            "MOSAIC", "token_" + System.currentTimeMillis() / 1000);
        final String mosaicId = namespaceName + ":" + mosaicName;
        System.out.printf("Creating mosaic: %s%n", mosaicId);

The mosaic ID is assembled from an existing namespace and a mosaic name. See Name in the Textbook for the naming rules.

To avoid collisions across multiple runs of the tutorial, a timestamp is added to the mosaic name. In practice, however, programs would use a fixed name for their mosaics. You can force the tutorial to use fixed names through the NAMESPACE and MOSAIC environment variables.

Use a namespace owned by the signer

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

If you come from the Registering a Root Namespace tutorial, set the SIGNER_PRIVATE_KEY and NAMESPACE environment variables to match the account and namespace you created there, or any other namespace that the signer owns.

Defining the Mosaic⚓︎

    # Define the mosaic
    mosaic_definition = {
        'owner_public_key': signer_key_pair.public_key,
        'id': {
            'namespace_id': {'name': namespace_name},
            'name': mosaic_name
        },
        'description': 'My tutorial mosaic',
        'properties': [
            {'property_': {
                'name': b'divisibility', 'value': b'2'}},
            {'property_': {
                'name': b'initialSupply', 'value': b'1000'}},
            {'property_': {
                'name': b'supplyMutable', 'value': b'true'}},
            {'property_': {
                'name': b'transferable', 'value': b'true'}}
        ]
    }
    // Define the mosaic
    const mosaicDefinition = new descriptors.MosaicDefinitionDescriptor(
        signerKeyPair.publicKey,
        new descriptors.MosaicIdDescriptor(
            new descriptors.NamespaceIdDescriptor(namespaceName),
            mosaicName),
        'My tutorial mosaic',
        [
            ['divisibility', '2'],
            ['initialSupply', '1000'],
            ['supplyMutable', 'true'],
            ['transferable', 'true']
        ].map(([name, value]) =>
            new descriptors.SizePrefixedMosaicPropertyDescriptor(
                new descriptors.MosaicPropertyDescriptor(
                    name, value))));
        // Define the mosaic
        final MosaicDefinitionDescriptor mosaicDefinition =
            new MosaicDefinitionDescriptor(
                signerKeyPair.getPublicKey(),
                new MosaicIdDescriptor(
                    new NamespaceIdDescriptor(
                        namespaceName.getBytes(UTF_8)),
                    mosaicName.getBytes(UTF_8)),
                "My tutorial mosaic".getBytes(UTF_8),
                List.of(
                    Map.entry("divisibility", "2"),
                    Map.entry("initialSupply", "1000"),
                    Map.entry("supplyMutable", "true"),
                    Map.entry("transferable", "true"))
                    .stream()
                    .map(property ->
                        new SizePrefixedMosaicPropertyDescriptor(
                            new MosaicPropertyDescriptor(
                                property.getKey().getBytes(UTF_8),
                                property.getValue().getBytes(UTF_8))))
                    .toList(),
                null);

The mosaic definition describes the asset itself, separately from the transaction that registers it:

  • : The public key of the account creating the mosaic, which must match . The network rejects transactions where the two differ.

  • : The mosaic identifier, formed from the namespace and the mosaic name.

  • : Text describing the mosaic.

  • : A set of key-value pairs that configure the mosaic behavior:

    • : The number of decimal places the mosaic supports. For example, a value of 2 means each whole unit can be divided into 100 (102) atomic units. See Divisibility in the Textbook.
    • : The number of whole units minted to the creator when the mosaic is defined. See Initial Supply in the Textbook.
    • : Whether the total supply can be changed after creation. See Supply Mutability in the Textbook.
    • : Whether the mosaic can be sent between any two accounts other than the creator. See Transferability in the Textbook.

    In this example, the mosaic is divisible to two decimal places and starts with a supply of 1000.00 whole units. Its supply can be changed after creation, and its units can be freely transferred between accounts.

Optional levy

A mosaic definition can also include an optional levy. For more information, see the Creating a Mosaic with a Levy tutorial.

Building the Mosaic Definition Transaction⚓︎

    # Build the mosaic definition transaction
    rental_fee = calculate_mosaic_rental_fee()
    print(f'  Mosaic creation fee: {rental_fee / 1_000_000} XEM')

    transaction = facade.create_transaction_from_descriptor({
        'type': 'mosaic_definition_transaction_v1',
        'rental_fee_sink': 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        'rental_fee': rental_fee,
        'mosaic_definition': mosaic_definition
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)
    // Build the mosaic definition transaction
    const rentalFee = calculateMosaicRentalFee();
    console.log('  Mosaic creation fee:',
        `${Number(rentalFee) / 1_000_000} XEM`);

    const transaction = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicDefinitionTransactionV1Descriptor(
            mosaicDefinition,
            new Address('TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC'),
            new models.Amount(rentalFee)),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);
        // Build the mosaic definition transaction
        final long rentalFee = FeeCalculator.calculateMosaicRentalFee();
        System.out.printf("  Mosaic creation fee: %s XEM%n",
            (double) rentalFee / 1_000_000);
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new MosaicDefinitionTransactionV1Descriptor(
                    mosaicDefinition,
                    new Address(
                        "TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC"),
                    new Amount(rentalFee)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);

The mosaic definition transaction registers the mosaic on the network, specifying:

  • Transaction type: Mosaic definition transactions use MosaicDefinitionTransactionV1.

  • : The special account that collects mosaic creation fees. Each network has a fixed sink address:

    • mainnet: NBMOSAICOD4F54EE5CDMR23CCBGOAM2XSIUX6TRS
    • testnet: TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC

    The network rejects transactions that send the creation fee to any other address.

  • : The creation fee, which is 10 XEM. The SDK's helper returns the required amount.

    The network rejects transactions that pay less than this fee. Larger amounts are accepted, but the entire amount is transferred to the sink account.

  • : The mosaic definition built in the previous step.

The facade adds the signer, which must own the namespace and becomes the mosaic owner, and derives the timestamp and deadline from the two-hour deadline duration.

    # Calculate and attach the transaction fee
    fee = calculate_transaction_fee(transaction)
    transaction.fee = Amount(fee)
    print(f'  Transaction fee: {fee / 1_000_000} XEM')
    // Calculate and attach the transaction fee
    const fee = calculateTransactionFee(transaction);
    transaction.fee = new models.Amount(fee);
    console.log('  Transaction fee:', `${Number(fee) / 1_000_000} XEM`);
        // Calculate and attach the transaction fee
        final long fee = FeeCalculator.calculateTransactionFee(
            transaction);
        transaction.setFee(new Amount(fee));
        System.out.printf("  Transaction fee: %s XEM%n",
            (double) fee / 1_000_000);

Finally, the transaction fee is calculated with and attached to the transaction. Unlike the creation fee, the transaction fee is paid to the harvester account. Mosaic definition transactions pay a fixed transaction fee of 0.15 XEM, as shown in the fee schedule.

Submitting the Mosaic Definition⚓︎

    # Sign and generate final payload
    signature = facade.sign_transaction(signer_key_pair, transaction)
    json_payload = facade.transaction_factory.attach_signature(
        transaction, signature)
    print('Built mosaic definition transaction:')
    print(json.dumps(transaction.to_json(), indent=2))

    # Announce the transaction
    announce_result = announce_transaction(
        json_payload, 'mosaic definition')
    // Sign and generate final payload
    const signature = facade.signTransaction(signerKeyPair, transaction);
    const jsonPayload = facade.transactionFactory.static.attachSignature(
        transaction, signature);
    console.log('Built mosaic definition transaction:');
    console.dir(transaction.toJson(), { colors: true });

    // Announce the transaction
    const announceResult = await announceTransaction(
        jsonPayload, 'mosaic definition');
        // Sign and generate final payload
        final CryptoTypes.Signature signature = facade.signTransaction(
            signerKeyPair, transaction);
        final String payload = NemTransactionFactory.attachSignature(
            transaction, signature);
        System.out.println("Built mosaic definition transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(transaction.toJson()));

        // Announce the transaction
        final String result = announceTransaction(
            payload, "mosaic definition");

The mosaic definition transaction is signed and announced following the same process as in the Transfer XEM tutorial.

    # Wait for confirmation
    if 'SUCCESS' == announce_result:
        transaction_hash = facade.hash_transaction(transaction)
        print(f'Transaction hash: {transaction_hash}')
        wait_for_confirmation(transaction_hash, 'mosaic definition')
    else:
        print(f'Transaction rejected: {announce_result}')
    // Wait for confirmation
    if ('SUCCESS' === announceResult) {
        const transactionHash = facade.hashTransaction(transaction)
            .toString();
        console.log('Transaction hash:', transactionHash);
        await waitForConfirmation(transactionHash, 'mosaic definition');
    } else {
        console.log('Transaction rejected:', announceResult);
    }
        // Wait for confirmation
        if ("SUCCESS".equals(result)) {
            final String transactionHash = facade.hashTransaction(
                transaction).toString();
            System.out.printf("Transaction hash: %s%n", transactionHash);
            waitForConfirmation(
                transactionHash, "mosaic definition");
        } else {
            System.out.printf("Transaction rejected: %s%n", result);
        }

The code then waits for the transaction to be confirmed by polling the /transaction/get GET endpoint until the transaction is included in a block.

Retrieving the Mosaic⚓︎

    # Retrieve the mosaic
    definition_path = f'/mosaic/definition?mosaicId={mosaic_id}'
    print(f'Fetching mosaic information from {definition_path}')
    with urllib.request.urlopen(
        f'{NODE_URL}{definition_path}'
    ) as response:
        mosaic_info = json.loads(response.read().decode())
        properties = {
            prop['name']: prop['value']
            for prop in mosaic_info['properties']
        }
        print('Mosaic information:')
        print(f'  Creator: {mosaic_info["creator"]}')
        print(f'  Divisibility: {properties["divisibility"]}')
        print(f'  Initial supply: {properties["initialSupply"]}')
        print(f'  Supply mutable: {properties["supplyMutable"]}')
        print(f'  Transferable: {properties["transferable"]}')
    // Retrieve the mosaic
    const definitionPath = `/mosaic/definition?mosaicId=${mosaicId}`;
    console.log('Fetching mosaic information from', definitionPath);
    const definitionResponse = await fetch(
        `${NODE_URL}${definitionPath}`);
    const mosaicInfo = await definitionResponse.json();
    const properties = Object.fromEntries(
        mosaicInfo.properties.map(prop => [prop.name, prop.value]));
    console.log('Mosaic information:');
    console.log('  Creator:', mosaicInfo.creator);
    console.log('  Divisibility:', properties.divisibility);
    console.log('  Initial supply:', properties.initialSupply);
    console.log('  Supply mutable:', properties.supplyMutable);
    console.log('  Transferable:', properties.transferable);
        // Retrieve the mosaic
        final String path = "/mosaic/definition?mosaicId=" + mosaicId;
        System.out.printf("Fetching mosaic information from %s%n", path);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        final JsonNode info = JSON_MAPPER.readTree(response.body());
        System.out.println("Mosaic information:");
        System.out.printf("  Creator: %s%n",
            info.get("creator").asText());
        for (final JsonNode item : info.get("properties"))
            System.out.printf("  %s: %s%n",
                item.get("name").asText(), item.get("value").asText());

To verify the mosaic was created successfully, the code retrieves its definition from the /mosaic/definition GET endpoint and displays its properties.

A successful response confirms the mosaic exists on the network with the expected properties.

Mosaic lifetime

A mosaic has no duration of its own and becomes inactive when its parent namespace expires. Extending the root namespace keeps its mosaics usable. See Lifetime in the Textbook.

Output⚓︎

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

Using node http://libertalia.nemtest.net:7890
Signer address: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
Creating mosaic: my_namespace:token_1783588525
  Mosaic creation fee: 10.0 XEM
  Transaction fee: 0.15 XEM
Built mosaic definition transaction:
{
  "type": 16385,
  "version": 1,
  "network": 152,
  "timestamp": 356000940,
  "signer_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
  "signature": "A874D12F42F1339F1DD86E1FF484C66711E24DEC215DD1279F59BC11B9E5BB65F1F912A6F498A1B7EFADFEFFE5815F8BE0C561D1DDC8952510176A3667C4850C",
  "fee": "150000",
  "deadline": 356008140,
  "mosaic_definition": {
    "owner_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
    "id": {
      "namespace_id": {
        "name": "6d795f6e616d657370616365"
      },
      "name": "746f6b656e5f31373833353838353235"
    },
    "description": "4d79207475746f7269616c206d6f73616963",
    "properties": [
      {
        "property": {
          "name": "64697669736962696c697479",
          "value": "32"
        }
      },
      {
        "property": {
          "name": "696e697469616c537570706c79",
          "value": "31303030"
        }
      },
      {
        "property": {
          "name": "737570706c794d757461626c65",
          "value": "74727565"
        }
      },
      {
        "property": {
          "name": "7472616e7366657261626c65",
          "value": "74727565"
        }
      }
    ]
  },
  "rental_fee_sink": "54424D4F534149434F443446353445453543444D523233434342474F414D3258534A4252354F4C43",
  "rental_fee": "10000000"
}
Announcing mosaic definition to /transaction/announce
  Result: SUCCESS
Transaction hash: E52F569372C6079A7A0A35B40A503F68BAF6FBEE455B84182EDA7AEC9F0608C1
Waiting for mosaic definition confirmation from /transaction/get?hash=E52F569372C6079A7A0A35B40A503F68BAF6FBEE455B84182EDA7AEC9F0608C1
  Transaction status: pending
  Transaction status: pending
  Transaction status: pending
mosaic definition confirmed in block 693562
Fetching mosaic information from /mosaic/definition?mosaicId=my_namespace:token_1783588525
Mosaic information:
  Creator: 462ee976890916e54fa825d26bdd0235f5eb5b6a143c199ab0ae5ee9328e08ce
  Divisibility: 2
  Initial supply: 1000
  Supply mutable: true
  Transferable: true

Some highlights from the output:

  • Mosaic ID (line 3): The mosaic is identified by its fully qualified name, combining the namespace my_namespace and a timestamped mosaic name. Search for this name in the NEM testnet explorer to view the mosaic details.

  • Creation fee and transaction fee (lines 4-5): The creation fee is 10 XEM, while the transaction fee is 0.15 XEM.

  • Verified properties (lines 66-69): The mosaic is retrieved from the network, confirming the expected divisibility, the initial supply of 1000, and that the mosaic is both supply mutable and transferable.

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Define the mosaic , MosaicDefinitionTransactionV1
Calculate the creation fee
Retrieve the mosaic /mosaic/definition GET

Next Steps⚓︎

Now that you have created a mosaic, you can: