Skip to content

Revoking a Mosaic⚓︎

Mosaics created with the revokable flag allow their creator to reclaim units from any account, returning them to the creator's own account balance. This is useful for enforcing contractual terms, reclaiming unused tokens, or correcting erroneous distributions.

This tutorial shows how to revoke mosaic units from another account.

Prerequisites⚓︎

Before you start, make sure to:

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

For more details on revocability, see Revocability in the Textbook.

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.sc import Amount

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


# Helper function to fetch account mosaic balances
def get_account_mosaics(address):
    account_path = f'/accounts/{address}'
    print(f'Fetching account information from {account_path}')
    with urllib.request.urlopen(f'{NODE_URL}{account_path}') as response:
        response_json = json.loads(response.read().decode())
        return response_json['account']['mosaics']


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

SOURCE_ADDRESS = os.getenv('SOURCE_ADDRESS',
    'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA')
print(f'Source address: {SOURCE_ADDRESS}')

MOSAIC_ID_HEX = os.getenv('MOSAIC_ID', '7aed3d514c986941')
mosaic_id = int(MOSAIC_ID_HEX, 16)
print(f'Mosaic ID: {mosaic_id} (0x{MOSAIC_ID_HEX})')

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

    # --- CHECKING INITIAL BALANCE ---
    print('\n--- Checking initial balance ---')
    mosaics = get_account_mosaics(SOURCE_ADDRESS)
    for mosaic in mosaics:
        if mosaic['id'] == MOSAIC_ID_HEX.upper():
            print(f'  Mosaic ID: {mosaic["id"]},'
                f' Amount: {mosaic["amount"]}')

    # --- REVOKING MOSAIC ---
    print('\n--- Revoking mosaic ---')

    revoke_tx = facade.transaction_factory.create({
        'type': 'mosaic_supply_revocation_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'source_address': SOURCE_ADDRESS,
        'mosaic': {
            'mosaic_id': mosaic_id,
            'amount': 7_00
        }
    })
    revoke_tx.fee = Amount(fee_mult * revoke_tx.size)

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

    # Announce transaction
    revoke_hash = facade.hash_transaction(revoke_tx)
    print(f'Transaction hash: {revoke_hash}')

    print('Announcing mosaic revocation to /transactions')
    request = urllib.request.Request(
        f'{NODE_URL}/transactions',
        data=json_payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='PUT'
    )
    with urllib.request.urlopen(request) as response:
        print(f'  Response: {response.read().decode()}')

    # Wait for confirmation
    print('Waiting for mosaic revocation confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            status_url = f'{NODE_URL}/transactionStatus/{revoke_hash}'
            with urllib.request.urlopen(status_url) as response:
                status = json.loads(response.read().decode())
                print(f'  Transaction status: {status["group"]}')
            if status['group'] == 'confirmed':
                print('Mosaic revocation confirmed in',
                    attempt, 'seconds')
                break
            if status['group'] == 'failed':
                raise Exception(
                    f'Mosaic revocation failed: {status["code"]}')
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')

    # --- VERIFYING REVOCATION ---
    print('\n--- Verifying revocation ---')
    mosaics = get_account_mosaics(SOURCE_ADDRESS)
    for mosaic in mosaics:
        if mosaic['id'] == MOSAIC_ID_HEX.upper():
            print(f'  Mosaic ID: {mosaic["id"]},'
                f' Amount: {mosaic["amount"]}')

except Exception as e:
    print(e)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    SymbolFacade,
    NetworkTimestamp,
    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 fetch account mosaic balances
async function getAccountMosaics(address) {
    const accountPath = `/accounts/${address}`;
    console.log('Fetching account information from', accountPath);
    const response = await fetch(`${NODE_URL}${accountPath}`);
    const responseJSON = await response.json();
    return responseJSON.account.mosaics;
}

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

const SOURCE_ADDRESS = process.env.SOURCE_ADDRESS
    || 'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA';
console.log('Source address:', SOURCE_ADDRESS);

const MOSAIC_ID_HEX = process.env.MOSAIC_ID
    || '7aed3d514c986941';
const mosaicId = BigInt(`0x${MOSAIC_ID_HEX}`);
console.log(
    `Mosaic ID: ${mosaicId} (0x${MOSAIC_ID_HEX})`);

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

    // --- CHECKING INITIAL BALANCE ---
    console.log('\n--- Checking initial balance ---');
    let mosaics = await getAccountMosaics(SOURCE_ADDRESS);
    for (const mosaic of mosaics) {
        if (mosaic.id === MOSAIC_ID_HEX.toUpperCase())
            console.log(`  Mosaic ID: ${mosaic.id},`
                + ` Amount: ${mosaic.amount}`);
    }

    // --- REVOKING MOSAIC ---
    console.log('\n--- Revoking mosaic ---');

    const revokeTx = facade.transactionFactory.create({
        type: 'mosaic_supply_revocation_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        sourceAddress: SOURCE_ADDRESS,
        mosaic: {
            mosaicId: mosaicId,
            amount: 7_00n
        }
    });
    revokeTx.fee = new models.Amount(feeMult * revokeTx.size);

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

    // Announce transaction
    const revokeHash = facade.hashTransaction(revokeTx).toString();
    console.log('Transaction hash:', revokeHash);

    console.log('Announcing mosaic revocation to /transactions');
    const announceResponse = await fetch(`${NODE_URL}/transactions`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    console.log('  Response:', await announceResponse.text());

    // Wait for confirmation
    console.log('Waiting for mosaic revocation confirmation...');
    for (let attempt = 0; attempt < 60; attempt++) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        try {
            const statusUrl =
                `${NODE_URL}/transactionStatus/${revokeHash}`;
            const statusResponse = await fetch(statusUrl);

            if (!statusResponse.ok) {
                console.log('  Transaction status: unknown');
                continue;
            }

            const status = await statusResponse.json();
            console.log('  Transaction status:', status.group);

            if (status.group === 'confirmed') {
                console.log('Mosaic revocation confirmed in',
                    attempt, 'seconds');
                break;
            }

            if (status.group === 'failed') {
                throw new Error(
                    'Mosaic revocation failed: ' + status.code);
            }
        } catch (error) {
            if (error.message.includes('failed:'))
                throw error;
            console.log('  Transaction status: unknown');
        }
    }

    // --- VERIFYING REVOCATION ---
    console.log('\n--- Verifying revocation ---');
    mosaics = await getAccountMosaics(SOURCE_ADDRESS);
    for (const mosaic of mosaics) {
        if (mosaic.id === MOSAIC_ID_HEX.toUpperCase())
            console.log(`  Mosaic ID: ${mosaic.id},`
                + ` Amount: ${mosaic.amount}`);
    }
} catch (e) {
    console.error(e.message);
}

Download source

Code Explanation⚓︎

Setting Up the Accounts⚓︎

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

SOURCE_ADDRESS = os.getenv('SOURCE_ADDRESS',
    'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA')
print(f'Source address: {SOURCE_ADDRESS}')

MOSAIC_ID_HEX = os.getenv('MOSAIC_ID', '7aed3d514c986941')
mosaic_id = int(MOSAIC_ID_HEX, 16)
print(f'Mosaic ID: {mosaic_id} (0x{MOSAIC_ID_HEX})')
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());

const SOURCE_ADDRESS = process.env.SOURCE_ADDRESS
    || 'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA';
console.log('Source address:', SOURCE_ADDRESS);

const MOSAIC_ID_HEX = process.env.MOSAIC_ID
    || '7aed3d514c986941';
const mosaicId = BigInt(`0x${MOSAIC_ID_HEX}`);
console.log(
    `Mosaic ID: ${mosaicId} (0x${MOSAIC_ID_HEX})`);

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 with the revokable flag.

The SOURCE_ADDRESS environment variable specifies the address of the account from which mosaic units will be revoked.

The MOSAIC_ID environment variable specifies the hexadecimal identifier of the mosaic to revoke. See Querying Account Balance to list the mosaics held by an account.

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.

Checking Initial Balance⚓︎

    mosaics = get_account_mosaics(SOURCE_ADDRESS)
    for mosaic in mosaics:
        if mosaic['id'] == MOSAIC_ID_HEX.upper():
            print(f'  Mosaic ID: {mosaic["id"]},'
                f' Amount: {mosaic["amount"]}')
    let mosaics = await getAccountMosaics(SOURCE_ADDRESS);
    for (const mosaic of mosaics) {
        if (mosaic.id === MOSAIC_ID_HEX.toUpperCase())
            console.log(`  Mosaic ID: ${mosaic.id},`
                + ` Amount: ${mosaic.amount}`);
    }

Before revoking, the helper function get_account_mosaics fetches the source account's current balance for the target mosaic from the /accounts/{accountId} GET endpoint. This provides a baseline to compare against after the revocation.

Building the Revocation Transaction⚓︎

    revoke_tx = facade.transaction_factory.create({
        'type': 'mosaic_supply_revocation_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'source_address': SOURCE_ADDRESS,
        'mosaic': {
            'mosaic_id': mosaic_id,
            'amount': 7_00
        }
    })
    revoke_tx.fee = Amount(fee_mult * revoke_tx.size)
    const revokeTx = facade.transactionFactory.create({
        type: 'mosaic_supply_revocation_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        sourceAddress: SOURCE_ADDRESS,
        mosaic: {
            mosaicId: mosaicId,
            amount: 7_00n
        }
    });
    revokeTx.fee = new models.Amount(feeMult * revokeTx.size);

The revocation transaction reclaims mosaic units from the source account and returns them to the creator's balance:

  • Type: Mosaic supply revocation transactions use the type mosaic_supply_revocation_transaction_v1.

  • Source address: The address of the account holding the mosaic units to revoke. This can be any account that currently holds units of the specified mosaic.

  • Mosaic: An object containing the mosaic ID and the amount to revoke.

  • Amount: The number of atomic units to revoke from the source account. To find out the mosaic's divisibility, query the /mosaics/{mosaicId} GET endpoint. For example, with a divisibility of 2, an amount of 700 represents 7.00 whole units (700 / 102).

Partial revocation

The amount does not have to match the source account's full balance. Any amount up to the source's current holdings can be revoked in a single transaction.

Submitting the Revocation⚓︎

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

    # Announce transaction
    revoke_hash = facade.hash_transaction(revoke_tx)
    print(f'Transaction hash: {revoke_hash}')

    print('Announcing mosaic revocation to /transactions')
    request = urllib.request.Request(
        f'{NODE_URL}/transactions',
        data=json_payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='PUT'
    )
    with urllib.request.urlopen(request) as response:
        print(f'  Response: {response.read().decode()}')
    // Sign and generate final payload
    const signature = facade.signTransaction(signerKeyPair, revokeTx);
    const jsonPayload = facade.transactionFactory.static.attachSignature(
            revokeTx, signature);
    console.log('Built mosaic revocation transaction:');
    console.dir(revokeTx.toJson(), { colors: true });

    // Announce transaction
    const revokeHash = facade.hashTransaction(revokeTx).toString();
    console.log('Transaction hash:', revokeHash);

    console.log('Announcing mosaic revocation to /transactions');
    const announceResponse = await fetch(`${NODE_URL}/transactions`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    console.log('  Response:', await announceResponse.text());

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

    # Wait for confirmation
    print('Waiting for mosaic revocation confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            status_url = f'{NODE_URL}/transactionStatus/{revoke_hash}'
            with urllib.request.urlopen(status_url) as response:
                status = json.loads(response.read().decode())
                print(f'  Transaction status: {status["group"]}')
            if status['group'] == 'confirmed':
                print('Mosaic revocation confirmed in',
                    attempt, 'seconds')
                break
            if status['group'] == 'failed':
                raise Exception(
                    f'Mosaic revocation failed: {status["code"]}')
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')
    // Wait for confirmation
    console.log('Waiting for mosaic revocation confirmation...');
    for (let attempt = 0; attempt < 60; attempt++) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        try {
            const statusUrl =
                `${NODE_URL}/transactionStatus/${revokeHash}`;
            const statusResponse = await fetch(statusUrl);

            if (!statusResponse.ok) {
                console.log('  Transaction status: unknown');
                continue;
            }

            const status = await statusResponse.json();
            console.log('  Transaction status:', status.group);

            if (status.group === 'confirmed') {
                console.log('Mosaic revocation confirmed in',
                    attempt, 'seconds');
                break;
            }

            if (status.group === 'failed') {
                throw new Error(
                    'Mosaic revocation failed: ' + status.code);
            }
        } catch (error) {
            if (error.message.includes('failed:'))
                throw error;
            console.log('  Transaction status: unknown');
        }
    }

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

Verifying the Revocation⚓︎

    mosaics = get_account_mosaics(SOURCE_ADDRESS)
    for mosaic in mosaics:
        if mosaic['id'] == MOSAIC_ID_HEX.upper():
            print(f'  Mosaic ID: {mosaic["id"]},'
                f' Amount: {mosaic["amount"]}')
    mosaics = await getAccountMosaics(SOURCE_ADDRESS);
    for (const mosaic of mosaics) {
        if (mosaic.id === MOSAIC_ID_HEX.toUpperCase())
            console.log(`  Mosaic ID: ${mosaic.id},`
                + ` Amount: ${mosaic.amount}`);
    }

To verify the revocation, the helper function get_account_mosaics fetches the source account's balance again. The balance should be lower than the initial balance by the revoked amount.

Output⚓︎

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

Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Source address: TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
Mosaic ID: 8857803461494335809 (0x7aed3d514c986941)
Fetching current network time from /node/time
  Network time: 104783268507 ms since nemesis
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100

--- Checking initial balance ---
Fetching account information from /accounts/TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
  Mosaic ID: 7AED3D514C986941, Amount: 1000

--- Revoking mosaic ---
Built mosaic revocation transaction:
{
  "signature": "225719859B43C8B9FCB04432E83FA95258C15A64B60974754DC2E4CAF9E58110995F2ECB5552049C3F79632FFAF19F4B90247012A7777E79D3AF0A17507A9F0D",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 17229,
  "fee": "16800",
  "deadline": "104790468507",
  "source_address": "987D075454716222F609929E883174AD8C996D5828C938BC",
  "mosaic": {
    "mosaic_id": "8857803461494335809",
    "amount": "700"
  }
}
Transaction hash: C0E59A5E36FC50CC5BDF8A16EABAB791611737B64E3590D43576374B803771E6
Announcing mosaic revocation to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for mosaic revocation confirmation...
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
Mosaic revocation confirmed in 8 seconds

--- Verifying revocation ---
Fetching account information from /accounts/TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
  Mosaic ID: 7AED3D514C986941, Amount: 300

Some highlights from the output:

  • Mosaic ID (line 4): The mosaic ID 8857803461494335809 (0x7aed3d514c986941) identifies the mosaic to revoke.

  • Initial balance (line 12): Before the revocation, the source account holds 1000 atomic units of the mosaic.

  • Source address (line 24): The source_address field identifies the account from which units are revoked. This is the hex-encoded form of the Base32 address shown on line 3.

  • Revoked amount (lines 26-27): The mosaic object specifies the mosaic ID in decimal format and the amount 700. The decimal value corresponds to the hexadecimal ID shown on line 4.

  • Verified balance (line 47): After the revocation, the source account's balance is 300, confirming that 700 atomic units were successfully reclaimed.

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
Check account balance /accounts/{accountId} GET
Revoke mosaic units
Verify the revocation /accounts/{accountId} GET