Skip to content

Querying Currency Supply⚓︎

BEGINNER

Exchanges and market data aggregators need accurate supply figures to display market capitalization and token metrics.

The Symbol network exposes the maximum, total, and circulating supply of XYM, the native currency, through dedicated REST endpoints.

This tutorial shows how to query each value and derive additional metrics from them.

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 os
import urllib.request
from decimal import Decimal

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

SUPPLY_URL = f'{NODE_URL}/network/currency/supply'

try:
    with urllib.request.urlopen(f'{SUPPLY_URL}/max') as response:
        maximum_supply = Decimal(response.read().decode().strip())
    print(f'Maximum supply: {maximum_supply:,.6f} XYM')

    with urllib.request.urlopen(f'{SUPPLY_URL}/total') as response:
        total_supply = Decimal(response.read().decode().strip())
    print(f'Total supply: {total_supply:,.6f} XYM')

    with urllib.request.urlopen(f'{SUPPLY_URL}/circulating') as response:
        circulating_supply = Decimal(response.read().decode().strip())
    print(f'Circulating supply: {circulating_supply:,.6f} XYM')

    non_circulating_supply = total_supply - circulating_supply
    print(f'Non-circulating supply: {non_circulating_supply:,.6f} XYM')

    unminted_supply = maximum_supply - total_supply
    print(f'Unminted supply: {unminted_supply:,.6f} XYM')

except Exception as error:
    print(error)

Download source

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

const SUPPLY_PATH = '/network/currency/supply';

// Convert the returned decimal string (whole XYM) to atomic units.
// A float conversion would lose precision when the value has more
// than 15 significant digits.
const toAtomic = s => {
    const [whole, frac = ''] = s.trim().split('.');
    return (BigInt(whole) * 1_000_000n) + BigInt(frac.padEnd(6, '0'));
};
// Format an atomic amount back as whole XYM with 6 decimals.
const fmt = v =>
    `${(v / 1_000_000n).toLocaleString('en-US')}.` +
    `${(v % 1_000_000n).toString().padStart(6, '0')}`;

try {

    const maximumResponse = await fetch(`${NODE_URL}${SUPPLY_PATH}/max`);
    const maximumSupply = toAtomic(await maximumResponse.text());
    console.log(`Maximum supply: ${fmt(maximumSupply)} XYM`);

    const totalResponse = await fetch(`${NODE_URL}${SUPPLY_PATH}/total`);
    const totalSupply = toAtomic(await totalResponse.text());
    console.log(`Total supply: ${fmt(totalSupply)} XYM`);

    const circulatingResponse =
        await fetch(`${NODE_URL}${SUPPLY_PATH}/circulating`);
    const circulatingSupply = toAtomic(await circulatingResponse.text());
    console.log(`Circulating supply: ${fmt(circulatingSupply)} XYM`);

    const nonCirculatingSupply = totalSupply - circulatingSupply;
    console.log(
        `Non-circulating supply: ${fmt(nonCirculatingSupply)} XYM`);

    const unmintedSupply = maximumSupply - totalSupply;
    console.log(`Unminted supply: ${fmt(unmintedSupply)} XYM`);
} catch (error) {
    console.log(error);
}

Download source

//JAVA 21+

import java.io.IOException;
import java.math.BigDecimal;
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.Locale;

final class QueryCurrencySupply {
    private static final HttpClient HTTP_CLIENT =
        HttpClient.newHttpClient();

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

    private static BigDecimal fetchSupplyValue(
        final String supplyType
    ) throws IOException, InterruptedException {
        final String supplyPath =
            String.format("/network/currency/supply/%s", supplyType);
        final String url = String.format("%s%s", NODE_URL, supplyPath);
        final HttpRequest request =
            HttpRequest.newBuilder(URI.create(url)).GET().build();
        final HttpResponse<String> response =
            HTTP_CLIENT.send(request, BodyHandlers.ofString());
        return new BigDecimal(response.body().trim());
    }

    private static String formatSupply(final BigDecimal value) {
        return String.format(Locale.US, "%,.6f", value);
    }

    public static void main(final String[] args) {
        new QueryCurrencySupply().run();
    }

    private void run() {
        System.out.printf("Using node %s%n", NODE_URL);

        try {

            final BigDecimal maximumSupply = fetchSupplyValue("max");
            System.out.printf("Maximum supply: %s XYM%n",
                formatSupply(maximumSupply));

            final BigDecimal totalSupply = fetchSupplyValue("total");
            System.out.printf("Total supply: %s XYM%n",
                formatSupply(totalSupply));

            final BigDecimal circulatingSupply =
                fetchSupplyValue("circulating");
            System.out.printf("Circulating supply: %s XYM%n",
                formatSupply(circulatingSupply));

            final BigDecimal nonCirculatingSupply =
                totalSupply.subtract(circulatingSupply);
            System.out.printf("Non-circulating supply: %s XYM%n",
                formatSupply(nonCirculatingSupply));

            final BigDecimal unmintedSupply =
                maximumSupply.subtract(totalSupply);
            System.out.printf("Unminted supply: %s XYM%n",
                formatSupply(unmintedSupply));
        } catch (final Exception ex) {
            System.out.println(ex.getMessage());
        }
    }
}

Download source

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

Default node is testnet

The default node points to testnet. For production supply data, set NODE_URL to a mainnet node. For a list of available mainnet nodes, see symbol.fyi/nodes.

Code Explanation⚓︎

Fetching Supply Values⚓︎

    with urllib.request.urlopen(f'{SUPPLY_URL}/max') as response:
        maximum_supply = Decimal(response.read().decode().strip())
    print(f'Maximum supply: {maximum_supply:,.6f} XYM')

    with urllib.request.urlopen(f'{SUPPLY_URL}/total') as response:
        total_supply = Decimal(response.read().decode().strip())
    print(f'Total supply: {total_supply:,.6f} XYM')

    with urllib.request.urlopen(f'{SUPPLY_URL}/circulating') as response:
        circulating_supply = Decimal(response.read().decode().strip())
    print(f'Circulating supply: {circulating_supply:,.6f} XYM')
    const maximumResponse = await fetch(`${NODE_URL}${SUPPLY_PATH}/max`);
    const maximumSupply = toAtomic(await maximumResponse.text());
    console.log(`Maximum supply: ${fmt(maximumSupply)} XYM`);

    const totalResponse = await fetch(`${NODE_URL}${SUPPLY_PATH}/total`);
    const totalSupply = toAtomic(await totalResponse.text());
    console.log(`Total supply: ${fmt(totalSupply)} XYM`);

    const circulatingResponse =
        await fetch(`${NODE_URL}${SUPPLY_PATH}/circulating`);
    const circulatingSupply = toAtomic(await circulatingResponse.text());
    console.log(`Circulating supply: ${fmt(circulatingSupply)} XYM`);
            final BigDecimal maximumSupply = fetchSupplyValue("max");
            System.out.printf("Maximum supply: %s XYM%n",
                formatSupply(maximumSupply));

            final BigDecimal totalSupply = fetchSupplyValue("total");
            System.out.printf("Total supply: %s XYM%n",
                formatSupply(totalSupply));

            final BigDecimal circulatingSupply =
                fetchSupplyValue("circulating");
            System.out.printf("Circulating supply: %s XYM%n",
                formatSupply(circulatingSupply));

Each supply value is available through a dedicated endpoint:

All three endpoints return a plain-text number (not JSON), already expressed in whole units with decimal places (e.g. 8999999999.000000), not in atomic units.

A supply value like 8323505878.695894 has 16 digits, but a floating-point number can only store about 15 digits accurately. Parsing these values as floats can silently change the last digit, so the code parses and subtracts them using exact numeric types instead (Decimal in Python, the toAtomic helper with BigInt in JavaScript, and BigDecimal in Java).

Circulating supply is node-dependent

The list of non-circulating accounts is configured by each node operator (in the node's rest.json file), so different nodes could report different circulating supply values. If you are integrating supply data, ensure you query a trusted node with the default configuration.

Deriving Additional Metrics⚓︎

    non_circulating_supply = total_supply - circulating_supply
    print(f'Non-circulating supply: {non_circulating_supply:,.6f} XYM')

    unminted_supply = maximum_supply - total_supply
    print(f'Unminted supply: {unminted_supply:,.6f} XYM')
    const nonCirculatingSupply = totalSupply - circulatingSupply;
    console.log(
        `Non-circulating supply: ${fmt(nonCirculatingSupply)} XYM`);

    const unmintedSupply = maximumSupply - totalSupply;
    console.log(`Unminted supply: ${fmt(unmintedSupply)} XYM`);
            final BigDecimal nonCirculatingSupply =
                totalSupply.subtract(circulatingSupply);
            System.out.printf("Non-circulating supply: %s XYM%n",
                formatSupply(nonCirculatingSupply));

            final BigDecimal unmintedSupply =
                maximumSupply.subtract(totalSupply);
            System.out.printf("Unminted supply: %s XYM%n",
                formatSupply(unmintedSupply));

After fetching all three values, the code derives two additional metrics:

  • Non-circulating: The difference between total and circulating supply.
  • Unminted: The difference between maximum and total supply, representing the XYM that remains to be minted.

Output⚓︎

The following output shows a typical run querying the currency supply:

1
2
3
4
5
6
Using node https://reference.symboltest.net:3001
Maximum supply: 8,999,999,999.000000 XYM
Total supply: 8,323,505,878.695894 XYM
Circulating supply: 8,323,495,854.693871 XYM
Non-circulating supply: 10,024.002023 XYM
Unminted supply: 676,494,120.304106 XYM

These values come from a testnet node and do not reflect mainnet supply figures.

The output shows the full breakdown of the XYM supply:

  • Maximum supply (line 2): The hard cap for XYM.
  • Total supply (line 3): Lower than the maximum, because not all XYM has been minted yet.
  • Circulating supply (line 4): Lower still, because some minted XYM is held by non-circulating accounts.
  • Non-circulating supply (line 5): The difference between total and circulating supply.
  • Unminted supply (line 6): The remaining XYM that will be gradually minted through inflation rewards.

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Fetch maximum supply /network/currency/supply/max GET
Fetch total supply /network/currency/supply/total GET
Fetch circulating supply /network/currency/supply/circulating GET
Derive additional metrics -

Next steps⚓︎

To check a specific account's XYM balance, see the Query Account Balance tutorial.