Skip to content

Creating a Mosaic⚓︎

Mosaics represent assets on the Symbol blockchain, such as currencies, collectibles, or access rights. Unlike tokens on other platforms, Symbol 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 restricted tokens.

This tutorial shows how to create a mosaic and mint its initial supply.

Prerequisites⚓︎

Before you start, make sure to:

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

Full Code⚓︎

import json
import os
import time
import urllib.request

from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.SymbolFacade import SymbolFacade
from symbolchain.symbol.Network import NetworkTimestamp
from symbolchain.symbol.IdGenerator import generate_mosaic_id
from symbolchain.sc import Amount

NODE_URL = os.getenv(
    'NODE_URL', 'https://reference.symboltest.net:3001')
print(f'Using node {NODE_URL}')


# Helper function to announce a transaction
def announce_transaction(payload, label):
    print(f'Announcing {label} to /transactions')
    request = urllib.request.Request(
        f'{NODE_URL}/transactions',
        data=payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='PUT'
    )
    with urllib.request.urlopen(request) as response:
        print(f'  Response: {response.read().decode()}')


# Helper function to wait for transaction confirmation
def wait_for_confirmation(transaction_hash, label):
    print(f'Waiting for {label} confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            url = f'{NODE_URL}/transactionStatus/{transaction_hash}'
            with urllib.request.urlopen(url) as response:
                status = json.loads(response.read().decode())
                print(f'  Transaction status: {status["group"]}')
                if status['group'] == 'confirmed':
                    print(f'{label} confirmed in {attempt} seconds')
                    return
                if status['group'] == 'failed':
                    raise Exception(f'{label} failed: {status["code"]}')
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')
    raise Exception(f'{label} not confirmed after 60 seconds')


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

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

try:
    # Fetch current network time
    time_path = '/node/time'
    print(f'Fetching current network time from {time_path}')
    with urllib.request.urlopen(f'{NODE_URL}{time_path}') as response:
        response_json = json.loads(response.read().decode())
        receive_timestamp = (
            response_json['communicationTimestamps']['receiveTimestamp'])
        timestamp = NetworkTimestamp(int(receive_timestamp))
        print(f'  Network time: {timestamp.timestamp} ms since nemesis')

    # Fetch recommended fees
    fee_path = '/network/fees/transaction'
    print(f'Fetching recommended fees from {fee_path}')
    with urllib.request.urlopen(f'{NODE_URL}{fee_path}') as response:
        response_json = json.loads(response.read().decode())
        median_mult = response_json['medianFeeMultiplier']
        minimum_mult = response_json['minFeeMultiplier']
        fee_mult = max(median_mult, minimum_mult)
        print(f'  Fee multiplier: {fee_mult}')

    # --- CREATING MOSAIC DEFINITION ---
    print('\n--- Creating mosaic definition ---')

    nonce = int(time.time()) & 0xFFFFFFFF
    print(f'Mosaic nonce: {nonce}')

    definition_tx = facade.transaction_factory.create({
        'type': 'mosaic_definition_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'duration': 0,
        'divisibility': 2,
        'nonce': nonce,
        'flags': 'transferable restrictable'
    })
    definition_tx.fee = Amount(fee_mult * definition_tx.size)

    mosaic_id = generate_mosaic_id(signer_address, nonce)
    print(f'Mosaic ID: {mosaic_id} ({hex(mosaic_id)})')

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

    # Announce and wait for confirmation
    definition_hash = facade.hash_transaction(definition_tx)
    print(f'Transaction hash: {definition_hash}')
    announce_transaction(json_payload, 'mosaic definition')
    wait_for_confirmation(definition_hash, 'mosaic definition')

    # --- INCREASING MOSAIC SUPPLY ---
    print('\n--- Increasing mosaic supply ---')

    supply_tx = facade.transaction_factory.create({
        'type': 'mosaic_supply_change_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'mosaic_id': mosaic_id,
        'action': 'increase',
        'delta': 100_00
    })
    supply_tx.fee = Amount(fee_mult * supply_tx.size)

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

    # Announce and wait for confirmation
    supply_hash = facade.hash_transaction(supply_tx)
    print(f'Transaction hash: {supply_hash}')
    announce_transaction(json_payload, 'mosaic supply change')
    wait_for_confirmation(supply_hash, 'mosaic supply change')

    # --- VERIFYING MOSAIC ---
    print('\n--- Verifying mosaic ---')

    mosaic_id_hex = f'{mosaic_id:016x}'
    mosaic_path = f'/mosaics/{mosaic_id_hex}'
    print(f'Fetching mosaic information from {mosaic_path}')
    with urllib.request.urlopen(f'{NODE_URL}{mosaic_path}') as response:
        response_json = json.loads(response.read().decode())
        mosaic_info = response_json['mosaic']
        print('Mosaic information:')
        print(f'  Mosaic ID: {mosaic_info["id"]}')
        print(f'  Supply: {mosaic_info["supply"]}')
        print(f'  Flags: {mosaic_info["flags"]}')
        print(f'  Divisibility: {mosaic_info["divisibility"]}')
        print(f'  Duration: {mosaic_info["duration"]}')

except Exception as e:
    print(e)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    SymbolFacade,
    NetworkTimestamp,
    models,
    generateMosaicId
} from 'symbol-sdk/symbol';

const NODE_URL = process.env.NODE_URL ||
    'https://reference.symboltest.net:3001';
console.log('Using node', NODE_URL);

// Helper function to announce a transaction
async function announceTransaction(payload, label) {
    console.log(`Announcing ${label} to /transactions`);
    const response = await fetch(`${NODE_URL}/transactions`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: payload
    });
    console.log('  Response:', await response.text());
}

// Helper function to wait for transaction confirmation
async function waitForConfirmation(transactionHash, label) {
    console.log(`Waiting for ${label} confirmation...`);
    for (let attempt = 0; attempt < 60; attempt++) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        try {
            const response = await fetch(
                `${NODE_URL}/transactionStatus/${transactionHash}`);
            const status = await response.json();
            console.log('  Transaction status:', status.group);
            if (status.group === 'confirmed') {
                console.log(`${label} confirmed in`, attempt, 'seconds');
                return;
            }
            if (status.group === 'failed') {
                throw new Error(`${label} failed: ${status.code}`);
            }
        } catch (e) {
            if (e.message.includes('failed'))
                throw e;
            console.log('  Transaction status: unknown');
        }
    }
    throw new Error(`${label} not confirmed after 60 seconds`);
}

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

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

try {
    // Fetch current network time
    const timePath = '/node/time';
    console.log('Fetching current network time from', timePath);
    const timeResponse = await fetch(`${NODE_URL}${timePath}`);
    const timeJSON = await timeResponse.json();
    const timestamp = new NetworkTimestamp(
        timeJSON.communicationTimestamps.receiveTimestamp);
    console.log('  Network time:', timestamp.timestamp,
        'ms since nemesis');

    // Fetch recommended fees
    const feePath = '/network/fees/transaction';
    console.log('Fetching recommended fees from', feePath);
    const feeResponse = await fetch(`${NODE_URL}${feePath}`);
    const feeJSON = await feeResponse.json();
    const medianMult = feeJSON.medianFeeMultiplier;
    const minimumMult = feeJSON.minFeeMultiplier;
    const feeMult = Math.max(medianMult, minimumMult);
    console.log('  Fee multiplier:', feeMult);

    // --- CREATING MOSAIC DEFINITION ---
    console.log('\n--- Creating mosaic definition ---');

    const nonce = Math.floor(Date.now() / 1000) & 0x7FFFFFFF;
    console.log('Mosaic nonce:', nonce);

    const definitionTx =
        facade.transactionFactory.create({
            type: 'mosaic_definition_transaction_v1',
            signerPublicKey: signerKeyPair.publicKey.toString(),
            deadline: timestamp.addHours(2).timestamp,
            duration: 0n,
            divisibility: 2,
            nonce: nonce,
            flags: 'transferable restrictable'
        });
    definitionTx.fee = new models.Amount(feeMult * definitionTx.size);

    const mosaicId = generateMosaicId(signerAddress, nonce);
    console.log(`Mosaic ID: ${mosaicId} (0x${mosaicId.toString(16)})`);

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

    // Announce and wait for confirmation
    const definitionHash =
        facade.hashTransaction(definitionTx).toString();
    console.log('Transaction hash:', definitionHash);
    await announceTransaction(defPayload, 'mosaic definition');
    await waitForConfirmation(definitionHash, 'mosaic definition');

    // --- INCREASING MOSAIC SUPPLY ---
    console.log('\n--- Increasing mosaic supply ---');

    const supplyTx =
        facade.transactionFactory.create({
            type: 'mosaic_supply_change_transaction_v1',
            signerPublicKey: signerKeyPair.publicKey.toString(),
            deadline: timestamp.addHours(2).timestamp,
            mosaicId: mosaicId,
            action: 'increase',
            delta: 100_00n
        });
    supplyTx.fee = new models.Amount(feeMult * supplyTx.size);

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

    // Announce and wait for confirmation
    const supplyHash = facade.hashTransaction(supplyTx).toString();
    console.log('Transaction hash:', supplyHash);
    await announceTransaction(supPayload, 'mosaic supply change');
    await waitForConfirmation(supplyHash, 'mosaic supply change');

    // --- VERIFYING MOSAIC ---
    console.log('\n--- Verifying mosaic ---');

    const mosaicIdHex = mosaicId.toString(16).padStart(16, '0');
    const mosaicPath = `/mosaics/${mosaicIdHex}`;
    console.log('Fetching mosaic information from', mosaicPath);
    const mosaicResponse = await fetch(`${NODE_URL}${mosaicPath}`);
    const mosaicJSON = await mosaicResponse.json();
    const mosaicInfo = mosaicJSON.mosaic;
    console.log('Mosaic information:');
    console.log('  Mosaic ID:', mosaicInfo.id);
    console.log('  Supply:', mosaicInfo.supply);
    console.log('  Flags:', mosaicInfo.flags);
    console.log('  Divisibility:', mosaicInfo.divisibility);
    console.log('  Duration:', mosaicInfo.duration);
} catch (e) {
    console.error(e.message);
}

Download source

Code Explanation⚓︎

Creating a mosaic requires announcing two transactions:

  1. A mosaic definition transaction to register the mosaic and its properties.
  2. A mosaic supply change transaction to mint the initial units.

Setting Up the Account⚓︎

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

facade = SymbolFacade('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 SymbolFacade.KeyPair(
    new PrivateKey(SIGNER_PRIVATE_KEY));

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

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.

Fetching Network Time and Fees⚓︎

    # Fetch current network time
    time_path = '/node/time'
    print(f'Fetching current network time from {time_path}')
    with urllib.request.urlopen(f'{NODE_URL}{time_path}') as response:
        response_json = json.loads(response.read().decode())
        receive_timestamp = (
            response_json['communicationTimestamps']['receiveTimestamp'])
        timestamp = NetworkTimestamp(int(receive_timestamp))
        print(f'  Network time: {timestamp.timestamp} ms since nemesis')

    # Fetch recommended fees
    fee_path = '/network/fees/transaction'
    print(f'Fetching recommended fees from {fee_path}')
    with urllib.request.urlopen(f'{NODE_URL}{fee_path}') as response:
        response_json = json.loads(response.read().decode())
        median_mult = response_json['medianFeeMultiplier']
        minimum_mult = response_json['minFeeMultiplier']
        fee_mult = max(median_mult, minimum_mult)
        print(f'  Fee multiplier: {fee_mult}')
    // Fetch current network time
    const timePath = '/node/time';
    console.log('Fetching current network time from', timePath);
    const timeResponse = await fetch(`${NODE_URL}${timePath}`);
    const timeJSON = await timeResponse.json();
    const timestamp = new NetworkTimestamp(
        timeJSON.communicationTimestamps.receiveTimestamp);
    console.log('  Network time:', timestamp.timestamp,
        'ms since nemesis');

    // Fetch recommended fees
    const feePath = '/network/fees/transaction';
    console.log('Fetching recommended fees from', feePath);
    const feeResponse = await fetch(`${NODE_URL}${feePath}`);
    const feeJSON = await feeResponse.json();
    const medianMult = feeJSON.medianFeeMultiplier;
    const minimumMult = feeJSON.minFeeMultiplier;
    const feeMult = Math.max(medianMult, minimumMult);
    console.log('  Fee multiplier:', feeMult);

Network time and recommended fees are fetched from /node/time GET and /network/fees/transaction GET respectively, following the process described in the Transfer Transaction tutorial.

Building the Mosaic Definition Transaction⚓︎

    nonce = int(time.time()) & 0xFFFFFFFF
    print(f'Mosaic nonce: {nonce}')

    definition_tx = facade.transaction_factory.create({
        'type': 'mosaic_definition_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'duration': 0,
        'divisibility': 2,
        'nonce': nonce,
        'flags': 'transferable restrictable'
    })
    definition_tx.fee = Amount(fee_mult * definition_tx.size)

    mosaic_id = generate_mosaic_id(signer_address, nonce)
    print(f'Mosaic ID: {mosaic_id} ({hex(mosaic_id)})')
    const nonce = Math.floor(Date.now() / 1000) & 0x7FFFFFFF;
    console.log('Mosaic nonce:', nonce);

    const definitionTx =
        facade.transactionFactory.create({
            type: 'mosaic_definition_transaction_v1',
            signerPublicKey: signerKeyPair.publicKey.toString(),
            deadline: timestamp.addHours(2).timestamp,
            duration: 0n,
            divisibility: 2,
            nonce: nonce,
            flags: 'transferable restrictable'
        });
    definitionTx.fee = new models.Amount(feeMult * definitionTx.size);

    const mosaicId = generateMosaicId(signerAddress, nonce);
    console.log(`Mosaic ID: ${mosaicId} (0x${mosaicId.toString(16)})`);

The mosaic definition transaction registers a new mosaic on the network with the following properties:

  • Type: Mosaic definition transactions use the type mosaic_definition_transaction_v1.

  • Duration: The number of blocks the mosaic will remain active. A value of 0 means the mosaic never expires. If a duration is provided, the maximum allowed value is approximately 10 years (3,650 days or approximately 10,512,000 blocks with the default 30-second block target).

    Expiring mosaics become unusable

    When an expiring mosaic reaches its duration, it can no longer be transferred or used in transactions. Balances remain in accounts but are effectively frozen. Before setting a duration, consider whether your use case truly requires it.

  • Divisibility: 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. For more details, see Divisibility in the Textbook.

  • Nonce: An arbitrary 32-bit unsigned integer (0 to 4,294,967,295) that acts as a locally unique identifier for mosaics created by the same account. The mosaic ID is derived deterministically from the owner's address and the nonce using , so each unique nonce produces a different mosaic.

    Nonce choice in this tutorial

    This tutorial uses the current timestamp as the nonce to ensure each run creates a unique mosaic. The & 0xFFFFFFFF bitmask truncates the value to fit in 32 bits. In practice, any value works as long as the same account has not already used that nonce.

  • Flags: A space-separated set of behavior restrictions for the mosaic. Multiple flags can be combined in a single string, for example 'transferable restrictable'.

    The available flags are:

    • transferable: the mosaic can be freely sent between any accounts.
    • supply_mutable: the total supply can be changed after creation.
    • restrictable: the owner can apply mosaic restrictions to control which accounts can hold or transfer the mosaic.
    • revokable: the creator can reclaim units from any account.

    In this example, transferable restrictable is used.

Lease fee

In addition to the standard transaction fee, creating a mosaic requires a one-time lease fee paid in XYM.

Unlike the transaction fee, the lease fee is not included in the transaction request. It is deducted automatically by the network from the signer's account when the mosaic definition transaction is confirmed.

The amount of the lease fee can be queried from the /network/fees/rental GET endpoint (effectiveMosaicRentalFee property).

Submitting the Mosaic Definition⚓︎

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

    # Announce and wait for confirmation
    definition_hash = facade.hash_transaction(definition_tx)
    print(f'Transaction hash: {definition_hash}')
    announce_transaction(json_payload, 'mosaic definition')
    wait_for_confirmation(definition_hash, 'mosaic definition')
    // Sign and generate final payload
    const defSignature = facade.signTransaction(
        signerKeyPair, definitionTx);
    const defPayload = facade.transactionFactory.static.attachSignature(
        definitionTx, defSignature);
    console.log('Built mosaic definition transaction:');
    console.dir(definitionTx.toJson(), { colors: true });

    // Announce and wait for confirmation
    const definitionHash =
        facade.hashTransaction(definitionTx).toString();
    console.log('Transaction hash:', definitionHash);
    await announceTransaction(defPayload, 'mosaic definition');
    await waitForConfirmation(definitionHash, 'mosaic definition');

The mosaic definition transaction is signed and announced following the same process as in Creating a Transfer Transaction.

The code then waits for the transaction to be confirmed by polling the /transactionStatus/{hash} GET endpoint until the status changes to confirmed.

Building the Mosaic Supply Change Transaction⚓︎

    supply_tx = facade.transaction_factory.create({
        'type': 'mosaic_supply_change_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'mosaic_id': mosaic_id,
        'action': 'increase',
        'delta': 100_00
    })
    supply_tx.fee = Amount(fee_mult * supply_tx.size)
    const supplyTx =
        facade.transactionFactory.create({
            type: 'mosaic_supply_change_transaction_v1',
            signerPublicKey: signerKeyPair.publicKey.toString(),
            deadline: timestamp.addHours(2).timestamp,
            mosaicId: mosaicId,
            action: 'increase',
            delta: 100_00n
        });
    supplyTx.fee = new models.Amount(feeMult * supplyTx.size);

Once the mosaic definition is confirmed, a second transaction increases the mosaic's supply:

  • Type: Mosaic supply change transactions use the type mosaic_supply_change_transaction_v1.

  • Mosaic ID: The identifier of the mosaic, computed from the signer's address and nonce using .

  • Action: The value increase mints new units directly into the signer's account.

  • Delta: The number of atomic units to add. Since the mosaic has a divisibility of 2, a delta of 10000 results in 100.00 whole units (10000 / 102).

Submitting the Supply Change⚓︎

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

    # Announce and wait for confirmation
    supply_hash = facade.hash_transaction(supply_tx)
    print(f'Transaction hash: {supply_hash}')
    announce_transaction(json_payload, 'mosaic supply change')
    wait_for_confirmation(supply_hash, 'mosaic supply change')
    // Sign and generate final payload
    const supSignature = facade.signTransaction(
        signerKeyPair, supplyTx);
    const supPayload = facade.transactionFactory.static.attachSignature(
        supplyTx, supSignature);
    console.log('Built mosaic supply change transaction:');
    console.dir(supplyTx.toJson(), { colors: true });

    // Announce and wait for confirmation
    const supplyHash = facade.hashTransaction(supplyTx).toString();
    console.log('Transaction hash:', supplyHash);
    await announceTransaction(supPayload, 'mosaic supply change');
    await waitForConfirmation(supplyHash, 'mosaic supply change');

The mosaic supply change transaction is signed and announced following the same process as the mosaic definition transaction.

Combining both transactions

Instead of announcing the definition and supply change as two separate transactions, you can submit them together in a single Complete Aggregate Transaction.

This ensures both operations are confirmed atomically in the same block.

Even without the supply_mutable flag, supply changes are allowed as long as the owner holds the entire supply. Once any units are distributed to other accounts, the supply becomes permanently fixed.

Retrieving the Mosaic⚓︎

    mosaic_id_hex = f'{mosaic_id:016x}'
    mosaic_path = f'/mosaics/{mosaic_id_hex}'
    print(f'Fetching mosaic information from {mosaic_path}')
    with urllib.request.urlopen(f'{NODE_URL}{mosaic_path}') as response:
        response_json = json.loads(response.read().decode())
        mosaic_info = response_json['mosaic']
        print('Mosaic information:')
        print(f'  Mosaic ID: {mosaic_info["id"]}')
        print(f'  Supply: {mosaic_info["supply"]}')
        print(f'  Flags: {mosaic_info["flags"]}')
        print(f'  Divisibility: {mosaic_info["divisibility"]}')
        print(f'  Duration: {mosaic_info["duration"]}')
    const mosaicIdHex = mosaicId.toString(16).padStart(16, '0');
    const mosaicPath = `/mosaics/${mosaicIdHex}`;
    console.log('Fetching mosaic information from', mosaicPath);
    const mosaicResponse = await fetch(`${NODE_URL}${mosaicPath}`);
    const mosaicJSON = await mosaicResponse.json();
    const mosaicInfo = mosaicJSON.mosaic;
    console.log('Mosaic information:');
    console.log('  Mosaic ID:', mosaicInfo.id);
    console.log('  Supply:', mosaicInfo.supply);
    console.log('  Flags:', mosaicInfo.flags);
    console.log('  Divisibility:', mosaicInfo.divisibility);
    console.log('  Duration:', mosaicInfo.duration);

To verify the mosaic was created successfully, the code retrieves it from the network using the /mosaics/{mosaicId} GET endpoint and displays its properties.

A successful response confirms the mosaic exists on the network with the expected supply and divisibility.

Output⚓︎

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

Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Fetching current network time from /node/time
  Network time: 103504012049 ms since nemesis
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100

--- Creating mosaic definition ---
Mosaic nonce: 1770754477
Mosaic ID: 8318126551268698739 (0x736fec06ed1daa73)
Built mosaic definition transaction:
{
  "signature": "6516FB17F162075795E8A4578595DE4745A57B151A6A6ACABF613C1ADF94DC36AF13F661E9FC356723A1F653D2ECC434E514F891A7D16502825431FE5AC17F06",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 16717,
  "fee": "15000",
  "deadline": "103511212049",
  "id": "8318126551268698739",
  "duration": "0",
  "nonce": 1770754477,
  "flags": 6,
  "divisibility": 2
}
Transaction hash: 959C4FDE753700E21959CB23E46EE761937A3AFEAFB40DBD9ADFBEE2D1C85F8B
Announcing mosaic definition to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for mosaic definition confirmation...
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
Mosaic definition confirmed in 12 seconds

--- Increasing mosaic supply ---
Built mosaic supply change transaction:
{
  "signature": "9704D30D6B060CB28A5A8DA3BAC40B7D02D878199C32D57FA70B6EB213735677BF7380F14A1A9B04B7DEA806D55A379CF20BAC3BDA24D16094DF2F97917A5605",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 16973,
  "fee": "14500",
  "deadline": "103511212049",
  "mosaic_id": "8318126551268698739",
  "delta": "10000",
  "action": 1
}
Transaction hash: E4E002C9E4536666E47E4B98DB7307FF5715C4BD8C4579D8AD67C9CBCECC2CD2
Announcing mosaic supply change to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for mosaic supply change confirmation...
  Transaction status: unconfirmed
  Transaction status: confirmed
Mosaic supply change confirmed in 9 seconds

--- Verifying mosaic ---
Fetching mosaic information from /mosaics/736fec06ed1daa73
Mosaic information:
  Mosaic ID: 736FEC06ED1DAA73
  Supply: 10000
  Flags: 6
  Divisibility: 2
  Duration: 0

Some highlights from the output:

  • Mosaic ID (line 10): The nonce is combined with the signer's address to derive the mosaic ID 0x736fec06ed1daa73.

  • Fee (line 18): The transaction fee of 0.015 XYM is calculated as the transaction size multiplied by the fee multiplier. The lease fee is deducted separately by the network when the transaction is confirmed.

  • Mosaic ID (line 20): The id field is automatically computed by the transaction factory from the nonce and the signer's address, matching the value printed on line 10.

  • Mosaic properties (lines 21, 23-24): Flags are stored as a bitmask, where each flag occupies a single bit: supply_mutable (1), transferable (2), restrictable (4), and revokable (8). The value 6 equals transferable (2) + restrictable (4). The divisibility is 2 and the duration 0 means the mosaic never expires.

  • Supply delta (line 46): The delta of 10000 atomic units represents 100.00 whole units given the mosaic's divisibility of 2.

  • Verified properties (lines 60-64): The mosaic is retrieved from the network, confirming the expected supply, flags, divisibility, and duration.

The transaction hashes printed in the output can be used to search for the transactions in the Symbol Testnet Explorer.

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Generate mosaic ID
Define the mosaic
Mint mosaic supply
Retrieve the mosaic /mosaics/{mosaicId} GET

Next Steps⚓︎

Now that you have created a mosaic, you can: