Skip to content

Registering a Root Namespace⚓︎

INTERMEDIATE

Namespaces provide human-readable aliases for accounts and mosaics, which can be used instead of long addresses and hexadecimal mosaic IDs.

This tutorial shows how to register a root namespace and set its lease duration.

Once registered, additional steps are required to link the namespace to a mosaic or account, as explained in Next Steps.

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_namespace_id
from symbolchain.symbol.Network import Address

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 namespace name
    namespace_name = os.getenv(
        'ROOT_NAMESPACE', f'ns_{int(time.time())}')
    print(f'Creating root namespace: {namespace_name}')

    # Build the transaction
    transaction = facade.create_transaction_from_descriptor(
        {
            'type': 'namespace_registration_transaction_v1',
            'registration_type': 'root',
            'duration': 86400,  # approximately 30 days
            'name': namespace_name
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)

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

    transaction_hash = facade.hash_transaction(transaction)
    print(f'Transaction hash: {transaction_hash}')

    # Announce transaction
    announce_transaction(json_payload, 'namespace registration')

    # Wait for confirmation
    wait_for_confirmation(transaction_hash, 'namespace registration')

    # Retrieve the namespace
    namespace_id = generate_namespace_id(namespace_name)
    print(f'Namespace ID: {namespace_id} (0x{namespace_id:016X})')

    namespace_path = f'/namespaces/{namespace_id:016X}'
    print(f'Fetching namespace information from {namespace_path}')
    with urllib.request.urlopen(
        f'{NODE_URL}{namespace_path}'
    ) as response:
        response_json = json.loads(response.read().decode())
        namespace_info = response_json['namespace']
        print('Namespace information:')
        reg_type = namespace_info['registrationType']
        print(f'  Registration type: {reg_type}')
        owner_address = Address.from_decoded_address_hex_string(
            namespace_info['ownerAddress'])
        print(f'  Owner address: {owner_address}')
        print(f"  Start height: {namespace_info['startHeight']}")

        print(f"  End height: {namespace_info['endHeight']}")

except Exception as e:
    print(e)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    Address,
    SymbolFacade,
    descriptors,
    generateNamespaceId,
    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 namespace name
    const namespaceName =
        process.env.ROOT_NAMESPACE || `ns_${Date.now()}`;
    console.log('Creating root namespace:', namespaceName);

    // Build the transaction
    const transaction = facade.createTransactionFromTypedDescriptor(
        new descriptors.NamespaceRegistrationTransactionV1Descriptor(
            new models.NamespaceId(0n),
            models.NamespaceRegistrationType.ROOT,
            new models.BlockDuration(86400n), // approximately 30 days
            undefined,
            namespaceName),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);

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

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

    // Announce transaction
    await announceTransaction(jsonPayload, 'namespace registration');

    // Wait for confirmation
    await waitForConfirmation(transactionHash, 'namespace registration');

    // Retrieve the namespace
    const namespaceId = generateNamespaceId(namespaceName);
    const namespaceIdHex = namespaceId.toString(16)
        .toUpperCase().padStart(16, '0');
    console.log(
        'Namespace ID:',
        `${namespaceId} (0x${namespaceIdHex})`);

    const namespacePath = `/namespaces/${namespaceIdHex}`;
    console.log('Fetching namespace information from', namespacePath);
    const namespaceResponse = await fetch(`${NODE_URL}${namespacePath}`);
    const namespaceJSON = await namespaceResponse.json();
    const namespaceInfo = namespaceJSON.namespace;
    console.log('Namespace information:');
    console.log('  Registration type:', namespaceInfo.registrationType);
    const ownerAddress = Address.fromDecodedAddressHexString(
        namespaceInfo.ownerAddress);
    console.log('  Owner address:', ownerAddress.toString());
    console.log('  Start height:', namespaceInfo.startHeight);
    console.log('  End height:', namespaceInfo.endHeight);
} 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 java.nio.charset.StandardCharsets;

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

        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 JsonNode feeJson = getJson(feePath);
        final long feeMultiplier = Math.max(
            feeJson.get("medianFeeMultiplier").asLong(),
            feeJson.get("minFeeMultiplier").asLong());
        System.out.printf("  Fee multiplier: %d%n", feeMultiplier);

        // Build the namespace name
        final String namespaceName = System.getenv().getOrDefault(
            "ROOT_NAMESPACE",
            "ns_" + System.currentTimeMillis() / 1000);
        System.out.printf("Creating root namespace: %s%n",
            namespaceName);

        // Build the transaction
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new NamespaceRegistrationTransactionV1Descriptor(
                    new NamespaceId(0),
                    NamespaceRegistrationType.ROOT,
                    new BlockDuration(86400), // approximately 30 days
                    null,
                    namespaceName.getBytes(StandardCharsets.UTF_8)),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

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

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

        // Announce transaction
        announceTransaction(jsonPayload, "namespace registration");

        // Wait for confirmation
        waitForConfirmation(transactionHash, "namespace registration");

        // Retrieve the namespace
        final long namespaceId = IdGenerator.generateNamespaceId(
            namespaceName);
        System.out.printf("Namespace ID: %s (0x%016X)%n",
            Long.toUnsignedString(namespaceId), namespaceId);

        final String namespacePath = "/namespaces/%016X".formatted(
            namespaceId);
        System.out.printf("Fetching namespace information from %s%n",
            namespacePath);
        final JsonNode namespaceInfo = getJson(namespacePath)
            .get("namespace");
        System.out.println("Namespace information:");
        System.out.printf("  Registration type: %s%n",
            namespaceInfo.get("registrationType").asText());
        final Address ownerAddress = Address
            .fromDecodedAddressHexString(
                namespaceInfo.get("ownerAddress").asText());
        System.out.printf("  Owner address: %s%n", ownerAddress);
        System.out.printf("  Start height: %s%n",
            namespaceInfo.get("startHeight").asText());
        System.out.printf("  End height: %s%n",
            namespaceInfo.get("endHeight").asText());
    }

    private JsonNode getJson(final String path)
        throws IOException, InterruptedException {
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        return JSON_MAPPER.readTree(response.body());
    }

}

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

        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 will own the registered namespace.

    # 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 JsonNode feeJson = getJson(feePath);
        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.

Choosing the Namespace Name⚓︎

    # Build the namespace name
    namespace_name = os.getenv(
        'ROOT_NAMESPACE', f'ns_{int(time.time())}')
    print(f'Creating root namespace: {namespace_name}')
    // Build the namespace name
    const namespaceName =
        process.env.ROOT_NAMESPACE || `ns_${Date.now()}`;
    console.log('Creating root namespace:', namespaceName);
        // Build the namespace name
        final String namespaceName = System.getenv().getOrDefault(
            "ROOT_NAMESPACE",
            "ns_" + System.currentTimeMillis() / 1000);
        System.out.printf("Creating root namespace: %s%n",
            namespaceName);

A namespace is identified by its name, which the transaction registers on the network. See Name in the Textbook for the naming rules.

To ensure the namespace name is unique across multiple runs of the tutorial, a timestamp is added to the name. In practice, programs would use a fixed name for their namespaces. You can force the tutorial to use a fixed name through the ROOT_NAMESPACE environment variable.

Building the Transaction⚓︎

    # Build the transaction
    transaction = facade.create_transaction_from_descriptor(
        {
            'type': 'namespace_registration_transaction_v1',
            'registration_type': 'root',
            'duration': 86400,  # approximately 30 days
            'name': namespace_name
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)
    // Build the transaction
    const transaction = facade.createTransactionFromTypedDescriptor(
        new descriptors.NamespaceRegistrationTransactionV1Descriptor(
            new models.NamespaceId(0n),
            models.NamespaceRegistrationType.ROOT,
            new models.BlockDuration(86400n), // approximately 30 days
            undefined,
            namespaceName),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);
        // Build the transaction
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new NamespaceRegistrationTransactionV1Descriptor(
                    new NamespaceId(0),
                    NamespaceRegistrationType.ROOT,
                    new BlockDuration(86400), // approximately 30 days
                    null,
                    namespaceName.getBytes(StandardCharsets.UTF_8)),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

The namespace registration transaction registers the namespace on the network. The signer passed to becomes the owner of the registered namespace. The transaction's descriptor contains:

  • : Namespace registration transactions use the type NamespaceRegistrationTransactionV1.

  • : The value root indicates a root namespace is being created. Use child to register a subnamespace instead.

  • : The number of blocks for which the namespace will be leased. The minimum duration is 86,400 blocks (approximately 30 days), and the maximum is 5,256,000 blocks (approximately 5 years).

  • : The name of the root namespace.

Namespace lease fees

In addition to the standard transaction fee, registering a namespace requires a lease fee proportional to the requested duration.

Unlike the transaction fee, the lease fee is not included in the transaction request. It is calculated and deducted automatically by the network from the transaction signer’s account when the registration transaction is confirmed.

The amount of the lease fee can be calculated beforehand using the /network/fees/rental GET endpoint.

Submitting the Transaction⚓︎

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

    transaction_hash = facade.hash_transaction(transaction)
    print(f'Transaction hash: {transaction_hash}')

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

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

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

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

        // Announce transaction
        announceTransaction(jsonPayload, "namespace registration");

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

    # Wait for confirmation
    wait_for_confirmation(transaction_hash, 'namespace registration')
    // Wait for confirmation
    await waitForConfirmation(transactionHash, 'namespace registration');
        // Wait for confirmation
        waitForConfirmation(transactionHash, "namespace registration");

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

    # Retrieve the namespace
    namespace_id = generate_namespace_id(namespace_name)
    print(f'Namespace ID: {namespace_id} (0x{namespace_id:016X})')

    namespace_path = f'/namespaces/{namespace_id:016X}'
    print(f'Fetching namespace information from {namespace_path}')
    with urllib.request.urlopen(
        f'{NODE_URL}{namespace_path}'
    ) as response:
        response_json = json.loads(response.read().decode())
        namespace_info = response_json['namespace']
        print('Namespace information:')
        reg_type = namespace_info['registrationType']
        print(f'  Registration type: {reg_type}')
        owner_address = Address.from_decoded_address_hex_string(
            namespace_info['ownerAddress'])
        print(f'  Owner address: {owner_address}')
        print(f"  Start height: {namespace_info['startHeight']}")
    // Retrieve the namespace
    const namespaceId = generateNamespaceId(namespaceName);
    const namespaceIdHex = namespaceId.toString(16)
        .toUpperCase().padStart(16, '0');
    console.log(
        'Namespace ID:',
        `${namespaceId} (0x${namespaceIdHex})`);

    const namespacePath = `/namespaces/${namespaceIdHex}`;
    console.log('Fetching namespace information from', namespacePath);
    const namespaceResponse = await fetch(`${NODE_URL}${namespacePath}`);
    const namespaceJSON = await namespaceResponse.json();
    const namespaceInfo = namespaceJSON.namespace;
    console.log('Namespace information:');
    console.log('  Registration type:', namespaceInfo.registrationType);
    const ownerAddress = Address.fromDecodedAddressHexString(
        namespaceInfo.ownerAddress);
    console.log('  Owner address:', ownerAddress.toString());
    console.log('  Start height:', namespaceInfo.startHeight);
    console.log('  End height:', namespaceInfo.endHeight);
        // Retrieve the namespace
        final long namespaceId = IdGenerator.generateNamespaceId(
            namespaceName);
        System.out.printf("Namespace ID: %s (0x%016X)%n",
            Long.toUnsignedString(namespaceId), namespaceId);

        final String namespacePath = "/namespaces/%016X".formatted(
            namespaceId);
        System.out.printf("Fetching namespace information from %s%n",
            namespacePath);
        final JsonNode namespaceInfo = getJson(namespacePath)
            .get("namespace");
        System.out.println("Namespace information:");
        System.out.printf("  Registration type: %s%n",
            namespaceInfo.get("registrationType").asText());
        final Address ownerAddress = Address
            .fromDecodedAddressHexString(
                namespaceInfo.get("ownerAddress").asText());
        System.out.printf("  Owner address: %s%n", ownerAddress);
        System.out.printf("  Start height: %s%n",
            namespaceInfo.get("startHeight").asText());
        System.out.printf("  End height: %s%n",
            namespaceInfo.get("endHeight").asText());

To verify the namespace was registered, the code retrieves it from the network using the /namespaces/{namespaceId} GET endpoint and displays its properties.

The namespace ID is computed using . This function applies a deterministic hashing algorithm to the namespace name, producing the ID needed to query the namespace information.

A successful response confirms the namespace was registered and is active on the network.

Namespace registered but not linked yet

A namespace becomes useful when it serves as an alias for a mosaic or an account. Link the namespace to an identifier using the guides in Next Steps.

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
Creating root namespace: ns_1766533079
Built transaction:
{
  "signature": "64F716B9F4CB265D7130FDBD6BABB93A3AE2CC31896D0D3C85DE7CAFDE3CC5053730FC56FCF64B3FCFD24213A9BF3432AC08D2C03766EAF847C86421D88EA603",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 16718,
  "fee": "15900",
  "deadline": "99289814227",
  "duration": "86400",
  "id": "10704093651797406551",
  "registration_type": 0,
  "name": "6e735f31373636353333303739"
}
Transaction hash: 2E3ACFD140AEAA21E7839376C4290695E9E58761DC37B1E60D3207F76370CC22
Announcing namespace registration to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for namespace registration confirmation...
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
namespace registration confirmed in 11 seconds
Namespace ID: 10704093651797406551 (0x948C9476ADE5F357)
Fetching namespace information from /namespaces/948C9476ADE5F357
Namespace information:
  Registration type: 0
  Owner address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
  Start height: 2984442
  End height: 3073722

Some highlights from the output:

  • Namespace name (line 5): The chosen name ns_1766533079 includes a timestamp to ensure uniqueness. Search for this name in the Symbol Testnet Explorer to view the namespace details.

  • Fee (line 13): The transaction fee of 0.0159 XYM is calculated as the transaction size multiplied by the fee multiplier. The lease fee is deducted separately by the network when the transaction is confirmed.

  • ID and name (lines 16, 18): The id field shows the namespace ID as a decimal number, while name contains the namespace name encoded as a hexadecimal string. For example, 6e735f... decodes to ns_1....

  • Namespace ID (line 28): Shows both decimal and hexadecimal representations to match the id field on line 16.

  • Registration type (line 31): The value 0 indicates a root namespace (versus 1 for subnamespaces).

  • Owner address (line 32): The account that registered and owns the namespace.

  • Start and end heights (lines 33-34): The namespace is active from block 2984442 to block 3073722. The end height includes a grace period (1 day on testnet, 30 days on mainnet) beyond the requested duration, giving owners time to renew before the namespace becomes available to others.

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

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Build a namespace registration transaction , NamespaceRegistrationTransactionV1
Generate namespace ID
Retrieve the namespace /namespaces/{namespaceId} GET

Next Steps⚓︎

Now that you have a root namespace, you can: