Skip to content

Registering a Root Namespace⚓︎

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

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.symbol.IdGenerator import generate_namespace_id
from symbolchain.sc import Amount
from symbolchain.symbol.Network import Address

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

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

    # Build the transaction
    namespace_name = f'ns_{int(time.time())}'
    print(f'Creating root namespace: {namespace_name}')

    transaction = facade.transaction_factory.create({
        'type': 'namespace_registration_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'registration_type': 'root',
        'duration': 86400,  # approximately 30 days
        'name': namespace_name
    })
    transaction.fee = Amount(fee_mult * transaction.size)

    # 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
    print('Announcing namespace registration 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 namespace registration confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            status_url = (
                f'{NODE_URL}/transactionStatus/{transaction_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('Namespace registration confirmed in',
                    attempt, 'seconds')
                break
            if status['group'] == 'failed':
                raise Exception('Namespace registration failed:',
                    status['code'])
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')

    # Retrieve the namespace
    namespace_id = generate_namespace_id(namespace_name)
    print(f'Namespace ID: {namespace_id} ({hex(namespace_id)})')

    namespace_path = f'/namespaces/{namespace_id:x}'
    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_hex = bytes.fromhex(namespace_info["ownerAddress"])
        owner_address = Address(owner_address_hex)
        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 {
    SymbolFacade,
    NetworkTimestamp,
    models,
    generateNamespaceId
} from 'symbol-sdk/symbol';

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

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 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 receiveTimestamp =
        timeJSON.communicationTimestamps.receiveTimestamp;
    const timestamp = new NetworkTimestamp(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);

    // Build the transaction
    const namespaceName = `ns_${Date.now()}`;
    console.log('Creating root namespace:', namespaceName);

    const transaction = facade.transactionFactory.create({
        type: 'namespace_registration_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        registrationType: 'root',
        duration: 86400n, // approximately 30 days
        name: namespaceName
    });
    transaction.fee = new models.Amount(feeMult * transaction.size);

    // 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
    console.log('Announcing namespace registration 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 namespace registration confirmation...');
    for (let attempt = 0; attempt < 60; attempt++) {
        await new Promise(resolve => setTimeout(resolve, 1000));

        try {
            const statusUrl =
                `${NODE_URL}/transactionStatus/${transactionHash}`;
            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('Namespace registration confirmed in',
                    attempt, 'seconds');
                break;
            }

            if (status.group === 'failed') {
                throw new Error(
                    `Namespace registration failed: ${status.code}`);
            }
        } catch (error) {
            if (error.message.includes('failed:')) {
                throw error;
            }
            console.log('  Transaction status: unknown');
        }
    }

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

    const namespacePath =
        `/namespaces/${namespaceId.toString(16)}`;
    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 = new models.Address(
        Uint8Array.from(Buffer.from(namespaceInfo.ownerAddress, 'hex')));
    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

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

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.

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 receiveTimestamp =
        timeJSON.communicationTimestamps.receiveTimestamp;
    const timestamp = new NetworkTimestamp(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.

Building the Transaction⚓︎

    # Build the transaction
    namespace_name = f'ns_{int(time.time())}'
    print(f'Creating root namespace: {namespace_name}')

    transaction = facade.transaction_factory.create({
        'type': 'namespace_registration_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'registration_type': 'root',
        'duration': 86400,  # approximately 30 days
        'name': namespace_name
    })
    transaction.fee = Amount(fee_mult * transaction.size)
    // Build the transaction
    const namespaceName = `ns_${Date.now()}`;
    console.log('Creating root namespace:', namespaceName);

    const transaction = facade.transactionFactory.create({
        type: 'namespace_registration_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        registrationType: 'root',
        duration: 86400n, // approximately 30 days
        name: namespaceName
    });
    transaction.fee = new models.Amount(feeMult * transaction.size);

The namespace registration transaction specifies:

  • Type: Namespace registration transactions use the type namespace_registration_transaction_v1.

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

  • Duration: 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).

  • Name: The name of the root namespace. Names can only contain lowercase letters, numbers, hyphens, and underscores, must start with a letter or number, and can be at most 64 characters long.

    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.

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
    print('Announcing namespace registration 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 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
    console.log('Announcing namespace registration 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 transaction is signed and announced following the same process as in Creating a Transfer Transaction.

    # Wait for confirmation
    print('Waiting for namespace registration confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            status_url = (
                f'{NODE_URL}/transactionStatus/{transaction_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('Namespace registration confirmed in',
                    attempt, 'seconds')
                break
            if status['group'] == 'failed':
                raise Exception('Namespace registration failed:',
                    status['code'])
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')
    // Wait for confirmation
    console.log('Waiting for namespace registration confirmation...');
    for (let attempt = 0; attempt < 60; attempt++) {
        await new Promise(resolve => setTimeout(resolve, 1000));

        try {
            const statusUrl =
                `${NODE_URL}/transactionStatus/${transactionHash}`;
            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('Namespace registration confirmed in',
                    attempt, 'seconds');
                break;
            }

            if (status.group === 'failed') {
                throw new Error(
                    `Namespace registration 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.

Retrieving the Namespace⚓︎

    # Retrieve the namespace
    namespace_id = generate_namespace_id(namespace_name)
    print(f'Namespace ID: {namespace_id} ({hex(namespace_id)})')

    namespace_path = f'/namespaces/{namespace_id:x}'
    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_hex = bytes.fromhex(namespace_info["ownerAddress"])
        owner_address = Address(owner_address_hex)
        print(f'  Owner address: {owner_address}')
        print(f'  Start height: {namespace_info["startHeight"]}')
        print(f'  End height: {namespace_info["endHeight"]}')
    // Retrieve the namespace
    const namespaceId = generateNamespaceId(namespaceName);
    console.log(
        'Namespace ID:',
        `${namespaceId} (0x${namespaceId.toString(16)})`);

    const namespacePath =
        `/namespaces/${namespaceId.toString(16)}`;
    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 = new models.Address(
        Uint8Array.from(Buffer.from(namespaceInfo.ownerAddress, 'hex')));
    console.log('  Owner address:', ownerAddress.toString());
    console.log('  Start height:', namespaceInfo.startHeight);
    console.log('  End height:', namespaceInfo.endHeight);

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 current network time from /node/time
  Network time: 99282614227 ms since nemesis
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 7): 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 15): 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 18, 20): 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 30): Shows both decimal and hexadecimal representations to match the id field on line 18.

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

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

  • Start and end heights (lines 35-36): 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
Generate namespace ID
Build a namespace registration transaction
Retrieve the namespace /namespaces/{namespaceId} GET

Next Steps⚓︎

Now that you have a root namespace, you can: