Skip to content

Querying an Account Balance⚓︎

BEGINNER

Accounts on Symbol can hold mosaics (fungible tokens), including the native currency XYM.

This tutorial shows how to query an account's mosaic balances and display them with the appropriate number of decimal places. If the mosaic ID has a linked namespace, it is also retrieved and displayed as the mosaic's friendly name.

Prerequisites⚓︎

This tutorial uses the Symbol REST API without requiring an SDK. You only need a way to make HTTP requests.

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 urllib.request

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


def get_account_info(account_identifier):
    """
    Fetch account information by address or public key.

    Args:
        account_identifier: The account address or public key

    Returns:
        Dictionary containing the account information
    """
    account_path = f'/accounts/{account_identifier}'
    try:
        account_url = f'{NODE_URL}{account_path}'
        with urllib.request.urlopen(account_url) as response:
            account_info = json.loads(response.read().decode())
            return account_info['account']
    except urllib.error.HTTPError as err:
        if err.status == 404:
            print(f'Address does not exist: {err}')
        elif err.status == 409:
            print(f'Address is not properly formatted: {err}')
        else:
            print(f'Unexpected error: {err}')
        raise SystemExit(1) from err


def get_mosaic_names(mosaic_ids):
    """
    Fetch friendly names for a set of mosaics.

    Args:
        mosaic_ids: List of mosaic IDs as integers

    Returns:
        Dictionary mapping mosaic IDs to their namespace names
    """
    mosaic_ids_hex = [f'{mosaic_id:016X}' for mosaic_id in mosaic_ids]
    request_body = json.dumps({'mosaicIds': mosaic_ids_hex}).encode()
    request = urllib.request.Request(
        f'{NODE_URL}/namespaces/mosaic/names',
        data=request_body,
        headers={'Content-Type': 'application/json'}
    )
    with urllib.request.urlopen(request) as response:
        names_info = json.loads(response.read().decode())
        # Build a dictionary mapping mosaic IDs to their names
        names_map = {}
        for entry in names_info['mosaicNames']:
            entry_id = int(entry['mosaicId'], 16)
            names_map[entry_id] = entry['names']
        return names_map


def get_mosaics_info(mosaic_ids):
    """
    Fetch information for multiple mosaics in a single request.

    Args:
        mosaic_ids: List of mosaic IDs as integers

    Returns:
        Dictionary mapping mosaic IDs to their properties
    """
    mosaic_ids_hex = [f'{mosaic_id:016X}' for mosaic_id in mosaic_ids]
    request_body = json.dumps({'mosaicIds': mosaic_ids_hex}).encode()
    request = urllib.request.Request(
        f'{NODE_URL}/mosaics',
        data=request_body,
        headers={'Content-Type': 'application/json'}
    )
    with urllib.request.urlopen(request) as response:
        mosaics_info = json.loads(response.read().decode())
        # Build a dictionary mapping mosaic IDs to their properties
        mosaics_map = {}
        for entry in mosaics_info:
            entry_id = int(entry['mosaic']['id'], 16)
            mosaics_map[entry_id] = entry['mosaic']
        return mosaics_map


def format_amount(amount, divisibility):
    """
    Format an atomic amount with decimal places.

    Args:
        amount: The atomic amount as an integer
        divisibility: Number of decimal places

    Returns:
        Formatted amount as a string
    """
    if divisibility == 0:
        return str(amount)
    whole_part = amount // (10 ** divisibility)
    fractional_part = amount % (10 ** divisibility)
    return f'{whole_part}.{fractional_part:0{divisibility}d}'


# The account address to query
ADDRESS = os.getenv(
    'ADDRESS', 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ')
print(f'Fetching account information from {ADDRESS}')

try:
    # Get account information
    account = get_account_info(ADDRESS)

    # Display balances for all mosaics the account holds
    account_mosaics = account['mosaics']
    if not account_mosaics:
        print('Account holds no mosaics')
    else:
        print(f'Account holds {len(account_mosaics)} mosaic(s):')

        # Fetch mosaic properties and names for all mosaics
        acc_mosaic_ids = [int(m['id'], 16) for m in account_mosaics]
        acc_mosaic_names = get_mosaic_names(acc_mosaic_ids)
        acc_mosaics_info = get_mosaics_info(acc_mosaic_ids)

        for mosaic_entry in account_mosaics:
            mosaic_id = int(mosaic_entry['id'], 16)
            balance = int(mosaic_entry['amount'])

            # Get mosaic properties
            info = acc_mosaics_info[mosaic_id]
            mosaic_divisibility = info['divisibility']

            # Format and display the balance
            formatted_balance = format_amount(
                balance, mosaic_divisibility)
            mosaic_id_hex = f'0x{mosaic_id:016X}'

            # Display mosaic ID and names (if available)
            names = acc_mosaic_names.get(mosaic_id, [])
            if names:
                names_str = ', '.join(names)
                print(f'- Mosaic {mosaic_id_hex} ({names_str})')
            else:
                print(f'- Mosaic {mosaic_id_hex}')

            print(f'  Balance: {formatted_balance}')
            print(f'  Balance (atomic): {balance}')
            print(f'  Divisibility: {mosaic_divisibility}')
except urllib.error.URLError as e:
    print(e.reason)

Download source

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


/**
 * Fetch account information by address or public key.
 * @param {string} accountIdentifier - Account address or public key
 * @returns {Promise<object>} The account information
 */
async function getAccountInfo(accountIdentifier) {
    const accountPath = `/accounts/${accountIdentifier}`;
    const accountResponse = await fetch(`${NODE_URL}${accountPath}`);
    if (!accountResponse.ok) {
        if (404 === accountResponse.status) {
            console.error(
                'Address does not exist:', accountResponse.statusText);
        } else if (409 === accountResponse.status) {
            console.error(
                'Address is not properly formatted:',
                accountResponse.statusText);
        } else {
            console.error(
                'Unexpected error:', accountResponse.statusText);
        }
        process.exit(1);
    }
    const accountInfo = await accountResponse.json();
    return accountInfo.account;
}

/**
 * Fetch friendly names for a set of mosaics.
 * @param {bigint[]} mosaicIds - Array of mosaic IDs
 * @returns {Promise<Map>} Map of mosaic IDs to their namespace names
 */
async function getMosaicNames(mosaicIds) {
    const mosaicIdsHex = mosaicIds.map(
        id => id.toString(16).toUpperCase().padStart(16, '0')
    );
    const response = await fetch(`${NODE_URL}/namespaces/mosaic/names`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ mosaicIds: mosaicIdsHex })
    });
    const namesInfo = await response.json();
    // Build a map from mosaic IDs to their names
    const namesMap = new Map();
    for (const entry of namesInfo.mosaicNames) {
        const mosaicId = BigInt(`0x${entry.mosaicId}`);
        namesMap.set(mosaicId, entry.names);
    }
    return namesMap;
}

/**
 * Fetch information for multiple mosaics in a single request.
 * @param {bigint[]} mosaicIds - Array of mosaic IDs
 * @returns {Promise<Map>} Map of mosaic IDs to their properties
 */
async function getMosaicsInfo(mosaicIds) {
    const mosaicIdsHex = mosaicIds.map(
        id => id.toString(16).toUpperCase().padStart(16, '0')
    );
    const response = await fetch(`${NODE_URL}/mosaics`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ mosaicIds: mosaicIdsHex })
    });
    const mosaicsInfo = await response.json();
    // Build a map from mosaic IDs to their properties
    const mosaicsMap = new Map();
    for (const entry of mosaicsInfo) {
        const mosaicId = BigInt(`0x${entry.mosaic.id}`);
        mosaicsMap.set(mosaicId, entry.mosaic);
    }
    return mosaicsMap;
}

/**
 * Format an atomic amount with decimal places.
 * @param {bigint} amount - The atomic amount
 * @param {number} divisibility - Number of decimal places
 * @returns {string} The formatted amount
 */
function formatAmount(amount, divisibility) {
    if (0 === divisibility)
        return amount.toString();

    const divisor = 10n ** BigInt(divisibility);
    const wholePart = amount / divisor;
    const fractionalPart = amount % divisor;
    const fractionalStr = fractionalPart.toString()
        .padStart(divisibility, '0');
    return `${wholePart}.${fractionalStr}`;
}

// The account address to query
const ADDRESS = process.env.ADDRESS ||
    'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ';
console.log('Fetching account information from', ADDRESS);

try {
    // Get account information
    const account = await getAccountInfo(ADDRESS);

    // Display balances for all mosaics the account holds
    const accountMosaics = account.mosaics;
    if (0 === accountMosaics.length) {
        console.log('Account holds no mosaics');
    } else {
        console.log(`Account holds ${accountMosaics.length} mosaic(s):`);

        // Fetch mosaic properties and names for all mosaics
        const mosaicIds = accountMosaics.map(m => BigInt(`0x${m.id}`));
        const mosaicNames = await getMosaicNames(mosaicIds);
        const mosaicsInfo = await getMosaicsInfo(mosaicIds);

        for (const mosaicEntry of accountMosaics) {
            const mosaicId = BigInt(`0x${mosaicEntry.id}`);
            const balance = BigInt(mosaicEntry.amount);

            // Get mosaic properties
            const info = mosaicsInfo.get(mosaicId);
            const divisibility = info.divisibility;

            // Format and display the balance
            const formattedBalance = formatAmount(balance, divisibility);
            const mosaicIdHex = `0x${mosaicId.toString(16)
                .toUpperCase().padStart(16, '0')}`;

            // Display mosaic ID and names (if available)
            const names = mosaicNames.get(mosaicId) || [];
            if (0 < names.length) {
                console.log(
                    `- Mosaic ${mosaicIdHex} (${names.join(', ')})`);
            } else {
                console.log(`- Mosaic ${mosaicIdHex}`);
            }

            console.log(`  Balance: ${formattedBalance}`);
            console.log(`  Balance (atomic): ${balance.toString()}`);
            console.log(`  Divisibility: ${divisibility}`);
        }
    }
} catch (e) {
    console.error(e.message, '| Cause:', e.cause?.code ?? 'unknown');
}

Download source

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

import java.io.IOException;
import java.math.BigInteger;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;

public final class QueryBalance {
    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    private static final String NODE_URL = System.getenv().getOrDefault(
        "NODE_URL", "https://reference.symboltest.net:3001");


    /**
     * Fetch account information by address or public key.
     */
    private static JsonNode getAccountInfo(
        final String accountIdentifier
    ) throws IOException, InterruptedException {
        final String accountPath = String.format(
            "/accounts/%s", accountIdentifier);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(NODE_URL + accountPath)).GET().build();
        final HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, BodyHandlers.ofString());

        if (200 != response.statusCode()) {
            if (404 == response.statusCode())
                System.out.printf(
                    "Address does not exist: %s%n", response.body());
            else if (409 == response.statusCode())
                System.out.printf(
                    "Address is not properly formatted: %s%n",
                    response.body());
            else
                System.out.printf("Unexpected error: %s%n",
                    response.body());
            System.exit(1);
        }

        return JSON_MAPPER.readTree(response.body()).get("account");
    }


    /**
     * Fetch friendly names for a set of mosaics.
     */
    private static Map<BigInteger, List<String>> getMosaicNames(
        final List<BigInteger> mosaicIds
    ) throws IOException, InterruptedException {
        final ObjectNode requestBody = JSON_MAPPER.createObjectNode();
        final ArrayNode mosaicIdsHex = requestBody.putArray("mosaicIds");
        for (final BigInteger mosaicId : mosaicIds)
            mosaicIdsHex.add(String.format("%016X", mosaicId));
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(NODE_URL + "/namespaces/mosaic/names"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                requestBody.toString()))
            .build();
        final HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, BodyHandlers.ofString());
        final JsonNode namesInfo = JSON_MAPPER.readTree(response.body());

        // Build a map from mosaic IDs to their names
        final Map<BigInteger, List<String>> namesMap = new HashMap<>();
        for (final JsonNode entry : namesInfo.get("mosaicNames")) {
            final BigInteger mosaicId = new BigInteger(
                entry.get("mosaicId").asText(), 16);
            final List<String> names = new ArrayList<>();
            for (final JsonNode name : entry.get("names"))
                names.add(name.asText());
            namesMap.put(mosaicId, names);
        }

        return namesMap;
    }


    /**
     * Fetch information for multiple mosaics in a single request.
     */
    private static Map<BigInteger, JsonNode> getMosaicsInfo(
        final List<BigInteger> mosaicIds
    ) throws IOException, InterruptedException {
        final ObjectNode requestBody = JSON_MAPPER.createObjectNode();
        final ArrayNode mosaicIdsHex = requestBody.putArray("mosaicIds");
        for (final BigInteger mosaicId : mosaicIds)
            mosaicIdsHex.add(String.format("%016X", mosaicId));
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(NODE_URL + "/mosaics"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                requestBody.toString()))
            .build();
        final HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, BodyHandlers.ofString());
        final JsonNode mosaicsInfo = JSON_MAPPER.readTree(
            response.body());

        // Build a map from mosaic IDs to their properties
        final Map<BigInteger, JsonNode> mosaicsMap = new HashMap<>();
        for (final JsonNode entry : mosaicsInfo) {
            final JsonNode mosaic = entry.get("mosaic");
            final BigInteger mosaicId = new BigInteger(
                mosaic.get("id").asText(), 16);
            mosaicsMap.put(mosaicId, mosaic);
        }

        return mosaicsMap;
    }


    /**
     * Format an atomic amount with decimal places.
     */
    private static String formatAmount(
        final BigInteger amount,
        final int divisibility
    ) {
        if (0 == divisibility)
            return amount.toString();

        final BigInteger divisor = BigInteger.TEN.pow(divisibility);
        final BigInteger[] parts = amount.divideAndRemainder(divisor);
        return String.format(
            "%s.%0" + divisibility + "d", parts[0], parts[1]);
    }

    public static void main(final String[] args) {
        try {
            new QueryBalance().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", NODE_URL);

        // The account address to query
        final String address = System.getenv().getOrDefault(
            "ADDRESS", "TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ");
        System.out.printf("Fetching account information from %s%n",
            address);

        // Get account information
        final JsonNode account = getAccountInfo(address);

        // Display balances for all mosaics the account holds
        final JsonNode accountMosaics = account.get("mosaics");
        if (accountMosaics.isEmpty()) {
            System.out.println("Account holds no mosaics");
        } else {
            System.out.printf("Account holds %d mosaic(s):%n",
                accountMosaics.size());

            // Fetch mosaic properties and names for all mosaics
            final List<BigInteger> mosaicIds = new ArrayList<>();
            for (final JsonNode mosaicEntry : accountMosaics)
                mosaicIds.add(new BigInteger(
                    mosaicEntry.get("id").asText(), 16));
            final Map<BigInteger, List<String>> mosaicNames =
                getMosaicNames(mosaicIds);
            final Map<BigInteger, JsonNode> mosaicsInfo =
                getMosaicsInfo(mosaicIds);

            for (final JsonNode mosaicEntry : accountMosaics) {
                final BigInteger mosaicId = new BigInteger(
                    mosaicEntry.get("id").asText(), 16);
                final BigInteger balance = new BigInteger(
                    mosaicEntry.get("amount").asText());

                // Get mosaic properties
                final JsonNode info = mosaicsInfo.get(mosaicId);
                final int divisibility = info.get("divisibility")
                    .asInt();

                // Format and display the balance
                final String formattedBalance = formatAmount(
                    balance, divisibility);
                final String mosaicIdHex = String.format(
                    "0x%016X", mosaicId);

                // Display mosaic ID and names (if available)
                final List<String> names = mosaicNames.getOrDefault(
                    mosaicId, List.of());
                if (names.isEmpty())
                    System.out.printf("- Mosaic %s%n", mosaicIdHex);
                else
                    System.out.printf("- Mosaic %s (%s)%n",
                        mosaicIdHex,
                        String.join(", ", names));

                System.out.printf("  Balance: %s%n",
                    formattedBalance);
                System.out.printf("  Balance (atomic): %s%n",
                    balance);
                System.out.printf("  Divisibility: %d%n",
                    divisibility);
            }
        }
    }
}

Download source

The snippet uses the NODE_URL environment variable to set the Symbol API node. If no value is provided, a default one is used.

The tutorial defines the following functions:

  • : Fetches account state by address or public key.
  • : Fetches namespace aliases for mosaics.
  • : Fetches properties for multiple mosaics in a single request.
  • : Formats amounts with the appropriate number of decimal places, according to their divisibility.

Code Explanation⚓︎

Fetching Account Information⚓︎

def get_account_info(account_identifier):
    """
    Fetch account information by address or public key.

    Args:
        account_identifier: The account address or public key

    Returns:
        Dictionary containing the account information
    """
    account_path = f'/accounts/{account_identifier}'
    try:
        account_url = f'{NODE_URL}{account_path}'
        with urllib.request.urlopen(account_url) as response:
            account_info = json.loads(response.read().decode())
            return account_info['account']
    except urllib.error.HTTPError as err:
        if err.status == 404:
            print(f'Address does not exist: {err}')
        elif err.status == 409:
            print(f'Address is not properly formatted: {err}')
        else:
            print(f'Unexpected error: {err}')
        raise SystemExit(1) from err
/**
 * Fetch account information by address or public key.
 * @param {string} accountIdentifier - Account address or public key
 * @returns {Promise<object>} The account information
 */
async function getAccountInfo(accountIdentifier) {
    const accountPath = `/accounts/${accountIdentifier}`;
    const accountResponse = await fetch(`${NODE_URL}${accountPath}`);
    if (!accountResponse.ok) {
        if (404 === accountResponse.status) {
            console.error(
                'Address does not exist:', accountResponse.statusText);
        } else if (409 === accountResponse.status) {
            console.error(
                'Address is not properly formatted:',
                accountResponse.statusText);
        } else {
            console.error(
                'Unexpected error:', accountResponse.statusText);
        }
        process.exit(1);
    }
    const accountInfo = await accountResponse.json();
    return accountInfo.account;
}
    /**
     * Fetch account information by address or public key.
     */
    private static JsonNode getAccountInfo(
        final String accountIdentifier
    ) throws IOException, InterruptedException {
        final String accountPath = String.format(
            "/accounts/%s", accountIdentifier);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(NODE_URL + accountPath)).GET().build();
        final HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, BodyHandlers.ofString());

        if (200 != response.statusCode()) {
            if (404 == response.statusCode())
                System.out.printf(
                    "Address does not exist: %s%n", response.body());
            else if (409 == response.statusCode())
                System.out.printf(
                    "Address is not properly formatted: %s%n",
                    response.body());
            else
                System.out.printf("Unexpected error: %s%n",
                    response.body());
            System.exit(1);
        }

        return JSON_MAPPER.readTree(response.body()).get("account");
    }

The /accounts/{accountId} GET endpoint retrieves the state of an account, including all the mosaics it holds.

You can query an account using either its address or its public key.

Fetching Mosaic Names⚓︎

def get_mosaic_names(mosaic_ids):
    """
    Fetch friendly names for a set of mosaics.

    Args:
        mosaic_ids: List of mosaic IDs as integers

    Returns:
        Dictionary mapping mosaic IDs to their namespace names
    """
    mosaic_ids_hex = [f'{mosaic_id:016X}' for mosaic_id in mosaic_ids]
    request_body = json.dumps({'mosaicIds': mosaic_ids_hex}).encode()
    request = urllib.request.Request(
        f'{NODE_URL}/namespaces/mosaic/names',
        data=request_body,
        headers={'Content-Type': 'application/json'}
    )
    with urllib.request.urlopen(request) as response:
        names_info = json.loads(response.read().decode())
        # Build a dictionary mapping mosaic IDs to their names
        names_map = {}
        for entry in names_info['mosaicNames']:
            entry_id = int(entry['mosaicId'], 16)
            names_map[entry_id] = entry['names']
        return names_map
/**
 * Fetch friendly names for a set of mosaics.
 * @param {bigint[]} mosaicIds - Array of mosaic IDs
 * @returns {Promise<Map>} Map of mosaic IDs to their namespace names
 */
async function getMosaicNames(mosaicIds) {
    const mosaicIdsHex = mosaicIds.map(
        id => id.toString(16).toUpperCase().padStart(16, '0')
    );
    const response = await fetch(`${NODE_URL}/namespaces/mosaic/names`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ mosaicIds: mosaicIdsHex })
    });
    const namesInfo = await response.json();
    // Build a map from mosaic IDs to their names
    const namesMap = new Map();
    for (const entry of namesInfo.mosaicNames) {
        const mosaicId = BigInt(`0x${entry.mosaicId}`);
        namesMap.set(mosaicId, entry.names);
    }
    return namesMap;
}
    /**
     * Fetch friendly names for a set of mosaics.
     */
    private static Map<BigInteger, List<String>> getMosaicNames(
        final List<BigInteger> mosaicIds
    ) throws IOException, InterruptedException {
        final ObjectNode requestBody = JSON_MAPPER.createObjectNode();
        final ArrayNode mosaicIdsHex = requestBody.putArray("mosaicIds");
        for (final BigInteger mosaicId : mosaicIds)
            mosaicIdsHex.add(String.format("%016X", mosaicId));
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(NODE_URL + "/namespaces/mosaic/names"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                requestBody.toString()))
            .build();
        final HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, BodyHandlers.ofString());
        final JsonNode namesInfo = JSON_MAPPER.readTree(response.body());

        // Build a map from mosaic IDs to their names
        final Map<BigInteger, List<String>> namesMap = new HashMap<>();
        for (final JsonNode entry : namesInfo.get("mosaicNames")) {
            final BigInteger mosaicId = new BigInteger(
                entry.get("mosaicId").asText(), 16);
            final List<String> names = new ArrayList<>();
            for (final JsonNode name : entry.get("names"))
                names.add(name.asText());
            namesMap.put(mosaicId, names);
        }

        return namesMap;
    }

Mosaics are identified by 64-bit numeric IDs, which can be hard to read and remember. To improve usability, mosaics can be linked to human-readable namespace aliases.

The /namespaces/mosaic/names POST endpoint accepts multiple mosaic IDs and returns any namespace names that are currently linked to them. If a mosaic has no linked namespace, it will not appear in the response.

The method returns a map because a mosaic can have multiple namespace aliases (different namespaces can link to the same mosaic).

Fetching Mosaic Properties⚓︎

def get_mosaics_info(mosaic_ids):
    """
    Fetch information for multiple mosaics in a single request.

    Args:
        mosaic_ids: List of mosaic IDs as integers

    Returns:
        Dictionary mapping mosaic IDs to their properties
    """
    mosaic_ids_hex = [f'{mosaic_id:016X}' for mosaic_id in mosaic_ids]
    request_body = json.dumps({'mosaicIds': mosaic_ids_hex}).encode()
    request = urllib.request.Request(
        f'{NODE_URL}/mosaics',
        data=request_body,
        headers={'Content-Type': 'application/json'}
    )
    with urllib.request.urlopen(request) as response:
        mosaics_info = json.loads(response.read().decode())
        # Build a dictionary mapping mosaic IDs to their properties
        mosaics_map = {}
        for entry in mosaics_info:
            entry_id = int(entry['mosaic']['id'], 16)
            mosaics_map[entry_id] = entry['mosaic']
        return mosaics_map
/**
 * Fetch information for multiple mosaics in a single request.
 * @param {bigint[]} mosaicIds - Array of mosaic IDs
 * @returns {Promise<Map>} Map of mosaic IDs to their properties
 */
async function getMosaicsInfo(mosaicIds) {
    const mosaicIdsHex = mosaicIds.map(
        id => id.toString(16).toUpperCase().padStart(16, '0')
    );
    const response = await fetch(`${NODE_URL}/mosaics`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ mosaicIds: mosaicIdsHex })
    });
    const mosaicsInfo = await response.json();
    // Build a map from mosaic IDs to their properties
    const mosaicsMap = new Map();
    for (const entry of mosaicsInfo) {
        const mosaicId = BigInt(`0x${entry.mosaic.id}`);
        mosaicsMap.set(mosaicId, entry.mosaic);
    }
    return mosaicsMap;
}
    /**
     * Fetch information for multiple mosaics in a single request.
     */
    private static Map<BigInteger, JsonNode> getMosaicsInfo(
        final List<BigInteger> mosaicIds
    ) throws IOException, InterruptedException {
        final ObjectNode requestBody = JSON_MAPPER.createObjectNode();
        final ArrayNode mosaicIdsHex = requestBody.putArray("mosaicIds");
        for (final BigInteger mosaicId : mosaicIds)
            mosaicIdsHex.add(String.format("%016X", mosaicId));
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(NODE_URL + "/mosaics"))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(
                requestBody.toString()))
            .build();
        final HttpResponse<String> response = HttpClient.newHttpClient()
            .send(request, BodyHandlers.ofString());
        final JsonNode mosaicsInfo = JSON_MAPPER.readTree(
            response.body());

        // Build a map from mosaic IDs to their properties
        final Map<BigInteger, JsonNode> mosaicsMap = new HashMap<>();
        for (final JsonNode entry : mosaicsInfo) {
            final JsonNode mosaic = entry.get("mosaic");
            final BigInteger mosaicId = new BigInteger(
                mosaic.get("id").asText(), 16);
            mosaicsMap.put(mosaicId, mosaic);
        }

        return mosaicsMap;
    }

To format mosaic balances correctly, the snippet fetches their properties from the network. The key property required is divisibility, which defines how many decimal places a mosaic supports.

You can retrieve mosaic properties using the /mosaics/{mosaicId} GET endpoint for individual mosaics. The option used in this snippet is the /mosaics POST endpoint, which accepts multiple mosaic IDs in a single request and returns detailed information about each mosaic, including its divisibility.

Formatting Amounts⚓︎

def format_amount(amount, divisibility):
    """
    Format an atomic amount with decimal places.

    Args:
        amount: The atomic amount as an integer
        divisibility: Number of decimal places

    Returns:
        Formatted amount as a string
    """
    if divisibility == 0:
        return str(amount)
    whole_part = amount // (10 ** divisibility)
    fractional_part = amount % (10 ** divisibility)
    return f'{whole_part}.{fractional_part:0{divisibility}d}'
/**
 * Format an atomic amount with decimal places.
 * @param {bigint} amount - The atomic amount
 * @param {number} divisibility - Number of decimal places
 * @returns {string} The formatted amount
 */
function formatAmount(amount, divisibility) {
    if (0 === divisibility)
        return amount.toString();

    const divisor = 10n ** BigInt(divisibility);
    const wholePart = amount / divisor;
    const fractionalPart = amount % divisor;
    const fractionalStr = fractionalPart.toString()
        .padStart(divisibility, '0');
    return `${wholePart}.${fractionalStr}`;
}
    /**
     * Format an atomic amount with decimal places.
     */
    private static String formatAmount(
        final BigInteger amount,
        final int divisibility
    ) {
        if (0 == divisibility)
            return amount.toString();

        final BigInteger divisor = BigInteger.TEN.pow(divisibility);
        final BigInteger[] parts = amount.divideAndRemainder(divisor);
        return String.format(
            "%s.%0" + divisibility + "d", parts[0], parts[1]);
    }

This utility function converts atomic amounts into human-friendly representations:

  • Atomic amount: The raw value stored on the blockchain, expressed as an integer.
  • Formatted amount: The display format with decimal places determined by the mosaic's divisibility.

The formatting splits the atomic amount into whole and fractional parts by dividing and taking the remainder with respect to \(10^{\text{divisibility}}\). The fractional part is then zero-padded to ensure it always displays the correct number of decimal places.

Putting It All Together⚓︎

# The account address to query
ADDRESS = os.getenv(
    'ADDRESS', 'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ')
print(f'Fetching account information from {ADDRESS}')

try:
    # Get account information
    account = get_account_info(ADDRESS)

    # Display balances for all mosaics the account holds
    account_mosaics = account['mosaics']
    if not account_mosaics:
        print('Account holds no mosaics')
    else:
        print(f'Account holds {len(account_mosaics)} mosaic(s):')

        # Fetch mosaic properties and names for all mosaics
        acc_mosaic_ids = [int(m['id'], 16) for m in account_mosaics]
        acc_mosaic_names = get_mosaic_names(acc_mosaic_ids)
        acc_mosaics_info = get_mosaics_info(acc_mosaic_ids)

        for mosaic_entry in account_mosaics:
            mosaic_id = int(mosaic_entry['id'], 16)
            balance = int(mosaic_entry['amount'])

            # Get mosaic properties
            info = acc_mosaics_info[mosaic_id]
            mosaic_divisibility = info['divisibility']

            # Format and display the balance
            formatted_balance = format_amount(
                balance, mosaic_divisibility)
            mosaic_id_hex = f'0x{mosaic_id:016X}'

            # Display mosaic ID and names (if available)
            names = acc_mosaic_names.get(mosaic_id, [])
            if names:
                names_str = ', '.join(names)
                print(f'- Mosaic {mosaic_id_hex} ({names_str})')
            else:
                print(f'- Mosaic {mosaic_id_hex}')

            print(f'  Balance: {formatted_balance}')
            print(f'  Balance (atomic): {balance}')
            print(f'  Divisibility: {mosaic_divisibility}')
except urllib.error.URLError as e:
    print(e.reason)
// The account address to query
const ADDRESS = process.env.ADDRESS ||
    'TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ';
console.log('Fetching account information from', ADDRESS);

try {
    // Get account information
    const account = await getAccountInfo(ADDRESS);

    // Display balances for all mosaics the account holds
    const accountMosaics = account.mosaics;
    if (0 === accountMosaics.length) {
        console.log('Account holds no mosaics');
    } else {
        console.log(`Account holds ${accountMosaics.length} mosaic(s):`);

        // Fetch mosaic properties and names for all mosaics
        const mosaicIds = accountMosaics.map(m => BigInt(`0x${m.id}`));
        const mosaicNames = await getMosaicNames(mosaicIds);
        const mosaicsInfo = await getMosaicsInfo(mosaicIds);

        for (const mosaicEntry of accountMosaics) {
            const mosaicId = BigInt(`0x${mosaicEntry.id}`);
            const balance = BigInt(mosaicEntry.amount);

            // Get mosaic properties
            const info = mosaicsInfo.get(mosaicId);
            const divisibility = info.divisibility;

            // Format and display the balance
            const formattedBalance = formatAmount(balance, divisibility);
            const mosaicIdHex = `0x${mosaicId.toString(16)
                .toUpperCase().padStart(16, '0')}`;

            // Display mosaic ID and names (if available)
            const names = mosaicNames.get(mosaicId) || [];
            if (0 < names.length) {
                console.log(
                    `- Mosaic ${mosaicIdHex} (${names.join(', ')})`);
            } else {
                console.log(`- Mosaic ${mosaicIdHex}`);
            }

            console.log(`  Balance: ${formattedBalance}`);
            console.log(`  Balance (atomic): ${balance.toString()}`);
            console.log(`  Divisibility: ${divisibility}`);
        }
    }
} catch (e) {
    console.error(e.message, '| Cause:', e.cause?.code ?? 'unknown');
}
        // The account address to query
        final String address = System.getenv().getOrDefault(
            "ADDRESS", "TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ");
        System.out.printf("Fetching account information from %s%n",
            address);

        // Get account information
        final JsonNode account = getAccountInfo(address);

        // Display balances for all mosaics the account holds
        final JsonNode accountMosaics = account.get("mosaics");
        if (accountMosaics.isEmpty()) {
            System.out.println("Account holds no mosaics");
        } else {
            System.out.printf("Account holds %d mosaic(s):%n",
                accountMosaics.size());

            // Fetch mosaic properties and names for all mosaics
            final List<BigInteger> mosaicIds = new ArrayList<>();
            for (final JsonNode mosaicEntry : accountMosaics)
                mosaicIds.add(new BigInteger(
                    mosaicEntry.get("id").asText(), 16));
            final Map<BigInteger, List<String>> mosaicNames =
                getMosaicNames(mosaicIds);
            final Map<BigInteger, JsonNode> mosaicsInfo =
                getMosaicsInfo(mosaicIds);

            for (final JsonNode mosaicEntry : accountMosaics) {
                final BigInteger mosaicId = new BigInteger(
                    mosaicEntry.get("id").asText(), 16);
                final BigInteger balance = new BigInteger(
                    mosaicEntry.get("amount").asText());

                // Get mosaic properties
                final JsonNode info = mosaicsInfo.get(mosaicId);
                final int divisibility = info.get("divisibility")
                    .asInt();

                // Format and display the balance
                final String formattedBalance = formatAmount(
                    balance, divisibility);
                final String mosaicIdHex = String.format(
                    "0x%016X", mosaicId);

                // Display mosaic ID and names (if available)
                final List<String> names = mosaicNames.getOrDefault(
                    mosaicId, List.of());
                if (names.isEmpty())
                    System.out.printf("- Mosaic %s%n", mosaicIdHex);
                else
                    System.out.printf("- Mosaic %s (%s)%n",
                        mosaicIdHex,
                        String.join(", ", names));

                System.out.printf("  Balance: %s%n",
                    formattedBalance);
                System.out.printf("  Balance (atomic): %s%n",
                    balance);
                System.out.printf("  Divisibility: %d%n",
                    divisibility);
            }
        }

The main code reads the ADDRESS environment variable to determine which account to query. If no value is provided, it uses a default sample address.

It orchestrates the helper functions to:

  1. Retrieve the account information.
  2. Extract the mosaic IDs from the account.
  3. Fetch mosaic properties and namespace names for all mosaics.
  4. Iterate through each mosaic and format the balance with the appropriate decimal places.

Output⚓︎

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

Using node https://reference.symboltest.net:3001
Fetching account information from TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ
Account holds 3 mosaic(s):
- Mosaic 0x72C0212E67A08BCE (symbol.xym)
  Balance: 592.589332
  Balance (atomic): 592589332
  Divisibility: 6
- Mosaic 0x0D8B2E6D54A936E6
  Balance: 999999
  Balance (atomic): 999999
  Divisibility: 0
- Mosaic 0x42248928982939ED
  Balance: 0.01
  Balance (atomic): 1
  Divisibility: 2

The output displays all mosaics the account holds. Notice how different mosaics have different divisibility values:

  • The first mosaic has divisibility 6, showing six decimal places (592.589332). It also has a friendly name (symbol.xym), which identifies it as the network's native currency.
  • The second has divisibility 0, showing no decimal places (999999).
  • The third has divisibility 2, showing two decimal places (0.01).

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Fetch account state /accounts/{accountId} GET
Fetch mosaic names /namespaces/mosaic/names POST
Fetch mosaic properties /mosaics/{mosaicId} GET
/mosaics POST