Skip to content

Modifying a Mosaic Definition⚓︎

INTERMEDIATE

As long as no units of a mosaic exist on the network, its creator can still change its definition properties (flags, divisibility, and duration) by sending a second definition transaction with the same identifier.

For example, adding a flag that was not set initially, changing its divisibility, or extending its duration before distribution.

Zero supply requirement

A mosaic definition can only be modified when its total supply is 0. No units of the mosaic can exist anywhere on the network, otherwise the transaction fails with Failure_Mosaic_Modification_Disallowed.

This tutorial shows how to modify an existing mosaic's flags. To change the mosaic's supply instead of its definition, see Changing Mosaic Supply.

Consider creating a new mosaic instead

Creating a new mosaic is easier than modifying an existing one. Understanding how modification works is still important to know when a mosaic's definition can no longer be edited.

Prerequisites⚓︎

Before you start, make sure to:

Additionally, review the Transfer transaction 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.SymbolFacade import SymbolFacade
from symbolchain.symbol.IdGenerator import generate_mosaic_id

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 announce_response:
        print(f'  Response: {announce_response.read().decode()}')


# Helper function to wait for transaction confirmation
def wait_for_confirmation(tx_hash, label):
    print(f'Waiting for {label} confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            url = f'{NODE_URL}/transactionStatus/{tx_hash}'
            with urllib.request.urlopen(url) as confirm_response:
                status = json.loads(confirm_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 RuntimeError(
                        f'{label} failed: {status["code"]}')
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')
    raise TimeoutError(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 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_multiplier = response_json['medianFeeMultiplier']
        minimum_multiplier = response_json['minFeeMultiplier']
        fee_multiplier = max(median_multiplier, minimum_multiplier)
        print(f'  Fee multiplier: {fee_multiplier}')

    # Build the modification transaction
    MOSAIC_NONCE = int(os.getenv('MOSAIC_NONCE', '0'))
    print(f'Mosaic nonce: {MOSAIC_NONCE}')

    mosaic_id = generate_mosaic_id(signer_address, MOSAIC_NONCE)
    print(f'Mosaic ID: {mosaic_id} (0x{mosaic_id:016X})')

    modify_tx = facade.create_transaction_from_descriptor(
        {
            'type': 'mosaic_definition_transaction_v1',
            'duration': 0,
            'divisibility': 0,
            'nonce': MOSAIC_NONCE,
            'flags': 'revokable'
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)

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

    modify_hash = facade.hash_transaction(modify_tx)
    print(f'Transaction hash: {modify_hash}')

    # Announce transaction
    announce_transaction(json_payload, 'mosaic modification')

    # Wait for confirmation
    wait_for_confirmation(modify_hash, 'mosaic modification')

    # Retrieve 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'  Divisibility: {mosaic_info["divisibility"]}')
        print(f'  Flags: {mosaic_info["flags"]}')
        print(f'  Duration: {mosaic_info["duration"]}')

except Exception as e:
    print(e)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    SymbolFacade,
    descriptors,
    generateMosaicId,
    models
} 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; 60 > attempt; attempt++) {
        await new Promise(resolve => { setTimeout(resolve, 1000); });
        const response = await fetch(
            `${NODE_URL}/transactionStatus/${transactionHash}`);
        if (!response.ok) {
            if (404 === response.status) {
                console.log('  Transaction status: unknown');
                continue;
            }
            throw new Error(`HTTP ${response.status}`);
        }
        const status = await response.json();
        console.log('  Transaction status:', status.group);
        if ('confirmed' === status.group) {
            console.log(`${label} confirmed in`, attempt, 'seconds');
            return;
        }
        if ('failed' === status.group)
            throw new Error(`${label} failed: ${status.code}`);
    }
    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 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 medianMultiplier = feeJSON.medianFeeMultiplier;
    const minimumMultiplier = feeJSON.minFeeMultiplier;
    const feeMultiplier = Math.max(medianMultiplier, minimumMultiplier);
    console.log('  Fee multiplier:', feeMultiplier);

    // Build the modification transaction
    const MOSAIC_NONCE = parseInt(process.env.MOSAIC_NONCE || '0', 10);
    console.log('Mosaic nonce:', MOSAIC_NONCE);

    const mosaicId = generateMosaicId(signerAddress, MOSAIC_NONCE);
    const mosaicIdHex = mosaicId.toString(16)
        .toUpperCase().padStart(16, '0');
    console.log(`Mosaic ID: ${mosaicId} (0x${mosaicIdHex})`);

    const modifyTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicDefinitionTransactionV1Descriptor(
            new models.MosaicId(0n),
            new models.BlockDuration(0n),
            new models.MosaicNonce(MOSAIC_NONCE),
            models.MosaicFlags.REVOKABLE,
            0),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);

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

    const modifyHash = facade.hashTransaction(modifyTx).toString();
    console.log('Transaction hash:', modifyHash);

    // Announce transaction
    await announceTransaction(jsonPayload, 'mosaic modification');

    // Wait for confirmation
    await waitForConfirmation(modifyHash, 'mosaic modification');

    // Retrieve the mosaic
    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('  Divisibility:', mosaicInfo.divisibility);
    console.log('  Flags:', mosaicInfo.flags);
    console.log('  Duration:', mosaicInfo.duration);
} catch (e) {
    console.error(e.message);
}

Download source

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

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

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

import org.symbol.sdk.CryptoTypes;
import org.symbol.sdk.facade.SymbolFacade;
import org.symbol.sdk.symbol.Address;
import org.symbol.sdk.symbol.IdGenerator;
import org.symbol.sdk.symbol.KeyPair;
import org.symbol.sdk.symbol.SymbolTransactionFactory;
import org.symbol.sdk.symbol.descriptors.*;
import org.symbol.sdk.symbol.models.*;

public final class ModifyMosaicDefinition {
    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", "https://reference.symboltest.net:3001");

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

    private void announceTransaction(
        final String payload,
        final String label
    ) throws IOException, InterruptedException {
        System.out.printf("Announcing %s to /transactions%n", label);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + "/transactions"))
            .header("Content-Type", "application/json")
            .PUT(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        System.out.printf("  Response: %s%n", response.body());
    }

    private void waitForConfirmation(
        final String transactionHash,
        final String label
    ) throws IOException, InterruptedException {
        System.out.printf("Waiting for %s confirmation...%n", label);
        for (int attempt = 0; 60 > attempt; ++attempt) {
            Thread.sleep(1000);
            final String statusPath =
                "/transactionStatus/" + transactionHash;
            final HttpRequest statusRequest = HttpRequest.newBuilder(
                URI.create(nodeUrl + statusPath)).GET().build();
            final HttpResponse<String> statusResponse = HTTP_CLIENT
                .send(statusRequest, BodyHandlers.ofString());
            if (404 == statusResponse.statusCode()) {
                System.out.println("  Transaction status: unknown");
                continue;
            }
            if (2 != statusResponse.statusCode() / 100)
                throw new IOException(
                    "HTTP " + statusResponse.statusCode());

            final JsonNode status =
                JSON_MAPPER.readTree(statusResponse.body());
            final String group = status.get("group").asText();
            System.out.printf("  Transaction status: %s%n", group);
            if ("confirmed".equals(group)) {
                System.out.printf("%s confirmed in %d seconds%n",
                    label, attempt);
                return;
            }
            if ("failed".equals(group))
                throw new IOException(String.format("%s failed: %s",
                    label, status.get("code").asText()));
        }
        throw new IOException(String.format(
            "%s not confirmed after 60 seconds", label));
    }

    public static void main(final String[] args) {
        try {
            new ModifyMosaicDefinition().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 privateKeyString = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKeyString));

        final Address signerAddress = facade.network.publicKeyToAddress(
            signerKeyPair.getPublicKey());
        System.out.printf("Signer address: %s%n", signerAddress);


        // Fetch recommended fees
        final String feePath = "/network/fees/transaction";
        System.out.printf("Fetching recommended fees from %s%n", feePath);
        final HttpRequest feeRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + feePath)).GET().build();
        final HttpResponse<String> feeResponse = HTTP_CLIENT.send(
            feeRequest, BodyHandlers.ofString());
        final JsonNode feeJson = JSON_MAPPER.readTree(feeResponse.body());
        final long feeMultiplier = Math.max(
            feeJson.get("medianFeeMultiplier").asLong(),
            feeJson.get("minFeeMultiplier").asLong());
        System.out.printf("  Fee multiplier: %d%n", feeMultiplier);


        // Build the modification transaction
        final long mosaicNonce = Long.parseUnsignedLong(
            System.getenv().getOrDefault("MOSAIC_NONCE", "0"));
        System.out.printf("Mosaic nonce: %d%n", mosaicNonce);

        final long mosaicId = IdGenerator.generateMosaicId(
            signerAddress, mosaicNonce);
        System.out.printf("Mosaic ID: %d (0x%016X)%n",
            mosaicId, mosaicId);

        final Transaction modifyTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicDefinitionTransactionV1Descriptor(
                    new MosaicId(0),
                    new BlockDuration(0),
                    new MosaicNonce(mosaicNonce),
                    MosaicFlags.REVOKABLE,
                    0),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

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

        final String modifyHash =
            facade.hashTransaction(modifyTx).toString();
        System.out.printf("Transaction hash: %s%n", modifyHash);

        // Announce transaction
        announceTransaction(jsonPayload, "mosaic modification");

        // Wait for confirmation
        waitForConfirmation(modifyHash, "mosaic modification");

        // Retrieve the mosaic
        final String mosaicIdHex = "%016X".formatted(mosaicId);
        final String mosaicPath = "/mosaics/" + mosaicIdHex;
        System.out.printf("Fetching mosaic information from %s%n",
            mosaicPath);
        final HttpRequest mosaicRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + mosaicPath)).GET().build();
        final HttpResponse<String> mosaicResponse = HTTP_CLIENT.send(
            mosaicRequest, BodyHandlers.ofString());
        final JsonNode mosaicInfo = JSON_MAPPER.readTree(
            mosaicResponse.body()).get("mosaic");
        System.out.println("Mosaic information:");
        System.out.printf("  Mosaic ID: %s%n",
            mosaicInfo.get("id").asText());
        System.out.printf("  Supply: %s%n",
            mosaicInfo.get("supply").asText());
        System.out.printf("  Divisibility: %s%n",
            mosaicInfo.get("divisibility").asText());
        System.out.printf("  Flags: %s%n",
            mosaicInfo.get("flags").asText());
        System.out.printf("  Duration: %s%n",
            mosaicInfo.get("duration").asText());

    }
}

Download source

Code Explanation⚓︎

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());
        final String privateKeyString = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKeyString));

        final Address signerAddress = facade.network.publicKeyToAddress(
            signerKeyPair.getPublicKey());
        System.out.printf("Signer address: %s%n", signerAddress);

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 must be the original creator of the mosaic.

    # 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_multiplier = response_json['medianFeeMultiplier']
        minimum_multiplier = response_json['minFeeMultiplier']
        fee_multiplier = max(median_multiplier, minimum_multiplier)
        print(f'  Fee multiplier: {fee_multiplier}')
    // 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 medianMultiplier = feeJSON.medianFeeMultiplier;
    const minimumMultiplier = feeJSON.minFeeMultiplier;
    const feeMultiplier = Math.max(medianMultiplier, minimumMultiplier);
    console.log('  Fee multiplier:', feeMultiplier);
        // Fetch recommended fees
        final String feePath = "/network/fees/transaction";
        System.out.printf("Fetching recommended fees from %s%n", feePath);
        final HttpRequest feeRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + feePath)).GET().build();
        final HttpResponse<String> feeResponse = HTTP_CLIENT.send(
            feeRequest, BodyHandlers.ofString());
        final JsonNode feeJson = JSON_MAPPER.readTree(feeResponse.body());
        final long feeMultiplier = Math.max(
            feeJson.get("medianFeeMultiplier").asLong(),
            feeJson.get("minFeeMultiplier").asLong());
        System.out.printf("  Fee multiplier: %d%n", feeMultiplier);

Recommended fees are fetched from /network/fees/transaction GET, following the process described in the Transfer Transaction tutorial.

Building the Modification Transaction⚓︎

Modifying a mosaic definition requires knowing its current values (flags, divisibility, and duration), because the network combines them with the values in the transaction rather than replacing them. The mosaic can be retrieved using the /mosaics/{mosaicId} GET endpoint or looked up in the Symbol Explorer.

In this tutorial, the current values are already known because the mosaic was just created and retrieved in the previous tutorial.

    # Build the modification transaction
    MOSAIC_NONCE = int(os.getenv('MOSAIC_NONCE', '0'))
    print(f'Mosaic nonce: {MOSAIC_NONCE}')

    mosaic_id = generate_mosaic_id(signer_address, MOSAIC_NONCE)
    print(f'Mosaic ID: {mosaic_id} (0x{mosaic_id:016X})')

    modify_tx = facade.create_transaction_from_descriptor(
        {
            'type': 'mosaic_definition_transaction_v1',
            'duration': 0,
            'divisibility': 0,
            'nonce': MOSAIC_NONCE,
            'flags': 'revokable'
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)
    // Build the modification transaction
    const MOSAIC_NONCE = parseInt(process.env.MOSAIC_NONCE || '0', 10);
    console.log('Mosaic nonce:', MOSAIC_NONCE);

    const mosaicId = generateMosaicId(signerAddress, MOSAIC_NONCE);
    const mosaicIdHex = mosaicId.toString(16)
        .toUpperCase().padStart(16, '0');
    console.log(`Mosaic ID: ${mosaicId} (0x${mosaicIdHex})`);

    const modifyTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicDefinitionTransactionV1Descriptor(
            new models.MosaicId(0n),
            new models.BlockDuration(0n),
            new models.MosaicNonce(MOSAIC_NONCE),
            models.MosaicFlags.REVOKABLE,
            0),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);
        // Build the modification transaction
        final long mosaicNonce = Long.parseUnsignedLong(
            System.getenv().getOrDefault("MOSAIC_NONCE", "0"));
        System.out.printf("Mosaic nonce: %d%n", mosaicNonce);

        final long mosaicId = IdGenerator.generateMosaicId(
            signerAddress, mosaicNonce);
        System.out.printf("Mosaic ID: %d (0x%016X)%n",
            mosaicId, mosaicId);

        final Transaction modifyTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicDefinitionTransactionV1Descriptor(
                    new MosaicId(0),
                    new BlockDuration(0),
                    new MosaicNonce(mosaicNonce),
                    MosaicFlags.REVOKABLE,
                    0),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

The MOSAIC_NONCE environment variable specifies the nonce of the mosaic to modify. The nonce must match the one used when creating the mosaic to target the same mosaic.

The modification transaction uses the same MosaicDefinitionTransactionV1 type as the original creation. The key difference is that the nonce targets an existing mosaic instead of creating a new one.

When processing the transaction, each property is combined with the mosaic's current value using the following rules:

  • Flags are XOR'd with the current flags. Setting a flag that is already active removes it; setting a flag that is not active adds it. For a description of each available flag, see Building the Mosaic Definition Transaction.
  • Divisibility is XOR'd with the current divisibility. The resulting value must be between 0 and 6.
  • Duration is added to the current remaining duration. Duration can only be extended, not reduced, because the field is unsigned. A value of 0 leaves the duration unchanged. Eternal mosaics (duration 0) cannot have their duration modified. The resulting duration cannot exceed 10,512,000 blocks (approximately 10 years).

In this example, the existing mosaic has flags transferable restrictable (numeric value 6). The modification sets flags: 'revokable' (numeric value 8). XOR produces 6 ⊕ 8 = 14, which corresponds to transferable restrictable revokable.

The following table illustrates how XOR affects individual flags:

Flag Current Modification Result (XOR)
transferable on off on
restrictable on off on
revokable off on on

Setting divisibility to 0 results in 2 ⊕ 0 = 2 (no change), and duration: 0 adds nothing to the current duration.

Lease fee

Each mosaic definition transaction incurs the full lease fee paid in XYM, whether creating or modifying a mosaic. This is the same fee every time, in addition to the standard transaction fee. The lease fee amount can be queried from the /network/fees/rental GET endpoint (effectiveMosaicRentalFee property).

Submitting the Modification⚓︎

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

    modify_hash = facade.hash_transaction(modify_tx)
    print(f'Transaction hash: {modify_hash}')

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

    const modifyHash = facade.hashTransaction(modifyTx).toString();
    console.log('Transaction hash:', modifyHash);

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

        final String modifyHash =
            facade.hashTransaction(modifyTx).toString();
        System.out.printf("Transaction hash: %s%n", modifyHash);

        // Announce transaction
        announceTransaction(jsonPayload, "mosaic modification");

The modification transaction is signed and announced following the same process as in the Transfer Transaction tutorial.

    # Wait for confirmation
    wait_for_confirmation(modify_hash, 'mosaic modification')
    // Wait for confirmation
    await waitForConfirmation(modifyHash, 'mosaic modification');
        // Wait for confirmation
        waitForConfirmation(modifyHash, "mosaic modification");

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

Retrieving the Mosaic⚓︎

    # Retrieve 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'  Divisibility: {mosaic_info["divisibility"]}')
        print(f'  Flags: {mosaic_info["flags"]}')
        print(f'  Duration: {mosaic_info["duration"]}')
    // Retrieve the mosaic
    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('  Divisibility:', mosaicInfo.divisibility);
    console.log('  Flags:', mosaicInfo.flags);
    console.log('  Duration:', mosaicInfo.duration);
        // Retrieve the mosaic
        final String mosaicIdHex = "%016X".formatted(mosaicId);
        final String mosaicPath = "/mosaics/" + mosaicIdHex;
        System.out.printf("Fetching mosaic information from %s%n",
            mosaicPath);
        final HttpRequest mosaicRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + mosaicPath)).GET().build();
        final HttpResponse<String> mosaicResponse = HTTP_CLIENT.send(
            mosaicRequest, BodyHandlers.ofString());
        final JsonNode mosaicInfo = JSON_MAPPER.readTree(
            mosaicResponse.body()).get("mosaic");
        System.out.println("Mosaic information:");
        System.out.printf("  Mosaic ID: %s%n",
            mosaicInfo.get("id").asText());
        System.out.printf("  Supply: %s%n",
            mosaicInfo.get("supply").asText());
        System.out.printf("  Divisibility: %s%n",
            mosaicInfo.get("divisibility").asText());
        System.out.printf("  Flags: %s%n",
            mosaicInfo.get("flags").asText());
        System.out.printf("  Duration: %s%n",
            mosaicInfo.get("duration").asText());

To verify the modification was applied, the code retrieves the mosaic from the network using the /mosaics/{mosaicId} GET endpoint and displays its updated properties.

A successful response confirms the mosaic now has the expected flags value.

Output⚓︎

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

Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100
Mosaic nonce: 1770998662
Mosaic ID: 6619508144549180335 (0x5BDD3795F7A8B3AF)
Built mosaic modification transaction:
{
  "signature": "729A40BAD69DA8B3CE38D442CFCD1258CA41B7933CF80EFE24A7A354773E0B08D7E2796DED2D93A5F1C16A5EB3298A2113601348A436A4340BFFC38BD7EEBF07",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 16717,
  "fee": "15000",
  "deadline": "103755503591",
  "id": "6619508144549180335",
  "duration": "0",
  "nonce": 1770998662,
  "flags": 8,
  "divisibility": 0
}
Transaction hash: 110D3B362D76020EBCD96A5A2E7C621ADE53D98BFACEEE273DA224D8E8B07225
Announcing mosaic modification to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for mosaic modification confirmation...
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
mosaic modification confirmed in 11 seconds
Fetching mosaic information from /mosaics/5BDD3795F7A8B3AF
Mosaic information:
  Mosaic ID: 5BDD3795F7A8B3AF
  Supply: 0
  Divisibility: 2
  Flags: 14
  Duration: 0

Some highlights from the output:

  • Mosaic nonce (line 5): The nonce 1770998662 matches the nonce used when the mosaic was created, targeting the same mosaic for modification.

  • Mosaic ID (line 6): The mosaic ID 0x5BDD3795F7A8B3AF is derived from the nonce and signer address, confirming the correct mosaic is being modified.

  • Transaction properties (lines 17, 19-20): The modification sets duration to 0 (no change), flags to 8 (revokable), and divisibility to 0 (no change).

  • Updated properties (lines 34-36): The mosaic's divisibility remains 2 (unchanged by XOR with 0). The flags are now 14, which corresponds to transferable (2) + restrictable (4) + revokable (8). The duration remains 0 (eternal, unchanged).

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

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Generate mosaic ID
Modify mosaic flags , MosaicDefinitionTransactionV1
Verify the updated mosaic /mosaics/{mosaicId} GET