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⚓︎

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
)
from symbolchain.nem.Network import NetworkTimestamp

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

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:
    # Fetch current network time
    time_path = '/time-sync/network-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())
        network_time = response_json['receiveTimeStamp'] // 1000
        print(f'  Network time: {network_time} s since the nemesis block')

    # Derived fields from network time
    timestamp = NetworkTimestamp(network_time)
    deadline = timestamp.add_hours(2)

    # 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.transaction_factory.create({
        'type': 'mosaic_definition_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'rental_fee_sink': 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        'rental_fee': rental_fee,
        'mosaic_definition': mosaic_definition
    })

    # 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_path = '/transaction/announce'
    print(f'Announcing mosaic definition to {announce_path}')
    announce_request = urllib.request.Request(
        f'{NODE_URL}{announce_path}',
        data=json_payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    with urllib.request.urlopen(announce_request) as response:
        announce_result = json.loads(response.read().decode())
    print(f'  Result: {announce_result["message"]}')

    # Wait for confirmation
    if 'SUCCESS' == announce_result['message']:
        transaction_hash = facade.hash_transaction(transaction)
        status_path = f'/transaction/get?hash={transaction_hash}'
        print(f'Waiting for confirmation from {status_path}')
        is_confirmed = False
        for attempt in range(120):
            try:
                with urllib.request.urlopen(
                    f'{NODE_URL}{status_path}'
                ) as response:
                    confirmed = json.loads(response.read().decode())
                    height = confirmed['meta']['height']
                    print(f'Transaction confirmed in block {height}')
                    is_confirmed = True
                    break
            except urllib.error.HTTPError:
                print('  Transaction status: pending')
            time.sleep(1)
        if not is_confirmed:
            print('Confirmation took too long.')
    else:
        print(f'Transaction rejected: {announce_result["message"]}')

    # 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 urllib.error.URLError as e:
    print(e.reason)

Download source

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

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

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 {
    // Fetch current network time
    const timePath = '/time-sync/network-time';
    console.log('Fetching current network time from', timePath);
    const timeResponse = await fetch(`${NODE_URL}${timePath}`);
    const timeJSON = await timeResponse.json();
    const networkTime = Math.floor(timeJSON.receiveTimeStamp / 1000);
    console.log('  Network time:', networkTime,
        's since the nemesis block');

    // Derived fields from network time
    const timestamp = new NetworkTimestamp(networkTime);
    const deadline = timestamp.addHours(2);

    // 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 = {
        ownerPublicKey: signerKeyPair.publicKey.toString(),
        id: {
            namespaceId: { name: namespaceName },
            name: mosaicName
        },
        description: 'My tutorial mosaic',
        properties: [
            { property: { name: 'divisibility', value: '2' } },
            { property: { name: 'initialSupply', value: '1000' } },
            { property: { name: 'supplyMutable', value: 'true' } },
            { property: { name: 'transferable', value: 'true' } }
        ]
    };

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

    const transaction = facade.transactionFactory.create({
        type: 'mosaic_definition_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        timestamp: timestamp.timestamp,
        deadline: deadline.timestamp,
        rentalFeeSink: 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        rentalFee,
        mosaicDefinition
    });

    // 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 announcePath = '/transaction/announce';
    console.log('Announcing mosaic definition to', announcePath);
    const announceResponse = await fetch(`${NODE_URL}${announcePath}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    const announceResult = await announceResponse.json();
    console.log('  Result:', announceResult.message);

    // Wait for confirmation
    if ('SUCCESS' === announceResult.message) {
        const transactionHash = facade.hashTransaction(transaction)
            .toString();
        const statusPath = `/transaction/get?hash=${transactionHash}`;
        console.log('Waiting for confirmation from', statusPath);

        let isConfirmed = false;
        for (let attempt = 1; 120 >= attempt; ++attempt) {
            const response = await fetch(`${NODE_URL}${statusPath}`);

            if (response.ok) {
                const confirmed = await response.json();
                console.log('Transaction confirmed in block',
                    confirmed.meta.height);
                isConfirmed = true;
                break;
            }
            console.log('  Transaction status: pending');
            await new Promise(resolve => { setTimeout(resolve, 1000); });
        }
        if (!isConfirmed)
            console.warn('Confirmation took too long.');
    } else {
        console.log('Transaction rejected:', announceResult.message);
    }

    // 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

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

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.

Fetching Network Time⚓︎

    # Fetch current network time
    time_path = '/time-sync/network-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())
        network_time = response_json['receiveTimeStamp'] // 1000
        print(f'  Network time: {network_time} s since the nemesis block')

    # Derived fields from network time
    timestamp = NetworkTimestamp(network_time)
    deadline = timestamp.add_hours(2)
    // Fetch current network time
    const timePath = '/time-sync/network-time';
    console.log('Fetching current network time from', timePath);
    const timeResponse = await fetch(`${NODE_URL}${timePath}`);
    const timeJSON = await timeResponse.json();
    const networkTime = Math.floor(timeJSON.receiveTimeStamp / 1000);
    console.log('  Network time:', networkTime,
        's since the nemesis block');

    // Derived fields from network time
    const timestamp = new NetworkTimestamp(networkTime);
    const deadline = timestamp.addHours(2);

Network time is fetched from /time-sync/network-time GET, and the transaction's timestamp and deadline fields are derived from it, following the process described in the Transfer XEM tutorial.

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);

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 = {
        ownerPublicKey: signerKeyPair.publicKey.toString(),
        id: {
            namespaceId: { name: namespaceName },
            name: mosaicName
        },
        description: 'My tutorial mosaic',
        properties: [
            { property: { name: 'divisibility', value: '2' } },
            { property: { name: 'initialSupply', value: '1000' } },
            { property: { name: 'supplyMutable', value: 'true' } },
            { property: { name: 'transferable', value: 'true' } }
        ]
    };

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.transaction_factory.create({
        'type': 'mosaic_definition_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'rental_fee_sink': 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        'rental_fee': rental_fee,
        'mosaic_definition': mosaic_definition
    })
    // Build the mosaic definition transaction
    const rentalFee = calculateMosaicRentalFee();
    console.log('  Mosaic creation fee:',
        `${Number(rentalFee) / 1_000_000} XEM`);

    const transaction = facade.transactionFactory.create({
        type: 'mosaic_definition_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        timestamp: timestamp.timestamp,
        deadline: deadline.timestamp,
        rentalFeeSink: 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        rentalFee,
        mosaicDefinition
    });

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

  • : Mosaic definition transactions use the type MosaicDefinitionTransactionV1.

  • : The account that signs the transaction and pays the fees, which must be the owner of the namespace that will hold the mosaic. It becomes the owner of the created mosaic.

  • and : The values computed in the network time step.

  • : 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.

    # 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`);

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_path = '/transaction/announce'
    print(f'Announcing mosaic definition to {announce_path}')
    announce_request = urllib.request.Request(
        f'{NODE_URL}{announce_path}',
        data=json_payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    with urllib.request.urlopen(announce_request) as response:
        announce_result = json.loads(response.read().decode())
    print(f'  Result: {announce_result["message"]}')
    // 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 announcePath = '/transaction/announce';
    console.log('Announcing mosaic definition to', announcePath);
    const announceResponse = await fetch(`${NODE_URL}${announcePath}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    const announceResult = await announceResponse.json();
    console.log('  Result:', announceResult.message);

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['message']:
        transaction_hash = facade.hash_transaction(transaction)
        status_path = f'/transaction/get?hash={transaction_hash}'
        print(f'Waiting for confirmation from {status_path}')
        is_confirmed = False
        for attempt in range(120):
            try:
                with urllib.request.urlopen(
                    f'{NODE_URL}{status_path}'
                ) as response:
                    confirmed = json.loads(response.read().decode())
                    height = confirmed['meta']['height']
                    print(f'Transaction confirmed in block {height}')
                    is_confirmed = True
                    break
            except urllib.error.HTTPError:
                print('  Transaction status: pending')
            time.sleep(1)
        if not is_confirmed:
            print('Confirmation took too long.')
    else:
        print(f'Transaction rejected: {announce_result["message"]}')
    // Wait for confirmation
    if ('SUCCESS' === announceResult.message) {
        const transactionHash = facade.hashTransaction(transaction)
            .toString();
        const statusPath = `/transaction/get?hash=${transactionHash}`;
        console.log('Waiting for confirmation from', statusPath);

        let isConfirmed = false;
        for (let attempt = 1; 120 >= attempt; ++attempt) {
            const response = await fetch(`${NODE_URL}${statusPath}`);

            if (response.ok) {
                const confirmed = await response.json();
                console.log('Transaction confirmed in block',
                    confirmed.meta.height);
                isConfirmed = true;
                break;
            }
            console.log('  Transaction status: pending');
            await new Promise(resolve => { setTimeout(resolve, 1000); });
        }
        if (!isConfirmed)
            console.warn('Confirmation took too long.');
    } else {
        console.log('Transaction rejected:', announceResult.message);
    }

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);

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
Fetching current network time from /time-sync/network-time
  Network time: 356000940 s since the nemesis block
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
Waiting for confirmation from /transaction/get?hash=E52F569372C6079A7A0A35B40A503F68BAF6FBEE455B84182EDA7AEC9F0608C1
  Transaction status: pending
  Transaction status: pending
  Transaction status: pending
Transaction 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 5): 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 6-7): The creation fee is 10 XEM, while the transaction fee is 0.15 XEM.

  • Verified properties (lines 67-70): 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: