Skip to content

Registering a Subnamespace⚓︎

Subnamespaces (also called "child" namespaces) extend the hierarchical structure of Namespaces.

This tutorial shows how to register a subnamespace under an existing root namespace.

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
    root_namespace_name = 'ns_root'
    child_namespace_name = f'sub_{int(time.time())}'
    full_namespace_name = (
        f'{root_namespace_name}.{child_namespace_name}')
    print(f'Creating child namespace: {full_namespace_name}')

    # Generate the parent namespace ID from the root namespace name
    parent_id = generate_namespace_id(root_namespace_name)
    print(f'  Parent namespace ID: {hex(parent_id)}')

    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': 'child',
        'parent_id': parent_id,
        'name': child_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(
        child_namespace_name, parent_id)
    print(f'Child 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'  Parent ID: {namespace_info["parentId"]}')
        print(f'  Depth: {namespace_info["depth"]}')
        print(f'  Level 0: {namespace_info["level0"]}')
        if int(namespace_info['depth']) >= 1:
            print(f'  Level 1: {namespace_info["level1"]}')
        if int(namespace_info['depth']) >= 2 and \
                'level2' in namespace_info:
            print(f'  Level 2: {namespace_info["level2"]}')
        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 rootNamespaceName = 'ns_root';
    const childNamespaceName = `sub_${Date.now()}`;
    const fullNamespaceName =
        `${rootNamespaceName}.${childNamespaceName}`;
    console.log('Creating child namespace:', fullNamespaceName);

    // Generate the parent namespace ID from the root name
    const parentId = generateNamespaceId(rootNamespaceName);
    console.log(
        'Parent namespace ID:', `0x${parentId.toString(16)}`);

    const transaction = facade.transactionFactory.create({
        type: 'namespace_registration_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        registrationType: 'child',
        parentId: parentId,
        name: childNamespaceName
    });
    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(
        childNamespaceName, parentId);
    console.log(
        'Child 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('  Parent ID:', namespaceInfo.parentId);
    console.log('  Depth:', namespaceInfo.depth);
    console.log('  Level 0:', namespaceInfo.level0);
    if (namespaceInfo.depth >= 1) {
        console.log('  Level 1:', namespaceInfo.level1);
    }
    if (namespaceInfo.depth >= 2 &&
            namespaceInfo.level2) {
        console.log('  Level 2:', namespaceInfo.level2);
    }
    console.log('  Start height:', namespaceInfo.startHeight);
    console.log('  End height:', namespaceInfo.endHeight);
} catch (e) {
    console.error(e.message);
}

Download source

Code Explanation⚓︎

The code follows the same pattern as the Registering a Root Namespace tutorial. This section focuses only on the key differences.

For detailed explanations of the common steps (setting up the account, fetching network time and fees, and announcing), see Registering a Root Namespace.

Building the Transaction⚓︎

    # Build the transaction
    root_namespace_name = 'ns_root'
    child_namespace_name = f'sub_{int(time.time())}'
    full_namespace_name = (
        f'{root_namespace_name}.{child_namespace_name}')
    print(f'Creating child namespace: {full_namespace_name}')

    # Generate the parent namespace ID from the root namespace name
    parent_id = generate_namespace_id(root_namespace_name)
    print(f'  Parent namespace ID: {hex(parent_id)}')

    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': 'child',
        'parent_id': parent_id,
        'name': child_namespace_name
    })
    transaction.fee = Amount(fee_mult * transaction.size)
    // Build the transaction
    const rootNamespaceName = 'ns_root';
    const childNamespaceName = `sub_${Date.now()}`;
    const fullNamespaceName =
        `${rootNamespaceName}.${childNamespaceName}`;
    console.log('Creating child namespace:', fullNamespaceName);

    // Generate the parent namespace ID from the root name
    const parentId = generateNamespaceId(rootNamespaceName);
    console.log(
        'Parent namespace ID:', `0x${parentId.toString(16)}`);

    const transaction = facade.transactionFactory.create({
        type: 'namespace_registration_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        registrationType: 'child',
        parentId: parentId,
        name: childNamespaceName
    });
    transaction.fee = new models.Amount(feeMult * transaction.size);

The main difference when registering a subnamespace is in the transaction descriptor:

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

  • Parent ID: Instead of specifying a duration, you provide the namespace ID of the parent namespace. The parent must be either a root namespace or another child namespace (if creating a third-level namespace).

    The parent namespace ID is calculated from its name using .

  • Name: The name of the subnamespace. This name follows the same rules as root namespace names: lowercase letters, numbers, hyphens, and underscores, starting with a letter or number, up to 64 characters.

    Note that this is just the name of the subnamespace, not the full path. For example, to create company.product, where company is the root, you would set name: 'product' and parent_id: generateNamespaceId('company').

    To ensure the subnamespace 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.

The subnamespace automatically inherits the expiration time of its root namespace. When the root expires, all subnamespaces expire with it.

Subnamespace lease fees

In addition to the standard transaction fee, registering a subnamespace requires a lease fee.

Unlike the transaction fee, the lease fee is not included in the transaction request.

For subnamespaces, this fee is fixed regardless of duration. The network deducts the lease fee automatically when the transaction is confirmed, so you do not need to specify it in the transaction.

The transaction is then signed, announced, and confirmed following the same process as in Registering a Root Namespace.

Retrieving the Subnamespace⚓︎

    # Retrieve the namespace
    namespace_id = generate_namespace_id(
        child_namespace_name, parent_id)
    print(f'Child 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'  Parent ID: {namespace_info["parentId"]}')
        print(f'  Depth: {namespace_info["depth"]}')
        print(f'  Level 0: {namespace_info["level0"]}')
        if int(namespace_info['depth']) >= 1:
            print(f'  Level 1: {namespace_info["level1"]}')
        if int(namespace_info['depth']) >= 2 and \
                'level2' in namespace_info:
            print(f'  Level 2: {namespace_info["level2"]}')
        print(f'  Start height: {namespace_info["startHeight"]}')
        print(f'  End height: {namespace_info["endHeight"]}')
    // Retrieve the namespace
    const namespaceId = generateNamespaceId(
        childNamespaceName, parentId);
    console.log(
        'Child 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('  Parent ID:', namespaceInfo.parentId);
    console.log('  Depth:', namespaceInfo.depth);
    console.log('  Level 0:', namespaceInfo.level0);
    if (namespaceInfo.depth >= 1) {
        console.log('  Level 1:', namespaceInfo.level1);
    }
    if (namespaceInfo.depth >= 2 &&
            namespaceInfo.level2) {
        console.log('  Level 2:', namespaceInfo.level2);
    }
    console.log('  Start height:', namespaceInfo.startHeight);
    console.log('  End height:', namespaceInfo.endHeight);

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

The subnamespace ID is computed using . This function takes both the subnamespace name and the parent ID, applying a deterministic hashing algorithm to produce the child namespace ID.

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

Subnamespace registered but not linked yet

A subnamespace becomes useful when it serves as an alias for a mosaic or an account. Link the subnamespace 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: 99282637399 ms since nemesis
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100
Creating child namespace: ns_root.sub_1766533103
Parent namespace ID: 0xb0786316d5c1d9dd
Built transaction:
{
  "signature": "9E91F08855D1911BE7208BB3441ACF8AE18EBEB529867D6DAC8552A837E17AA2F05BEBAE510950D5194E0837C4A6E780138CCF29A065F55C05A76CEF6AFA610E",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 16718,
  "fee": "16000",
  "deadline": "99289837399",
  "parent_id": "12716022497607277021",
  "id": "11305240440531381773",
  "registration_type": 1,
  "name": "7375625f31373636353333313033"
}
Transaction hash: 43962A040342198E485F5E91364CA0270F737C56E58909FE03FE3F02AD97CA56
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: confirmed
Namespace registration confirmed in 19 seconds
Child namespace ID: 11305240440531381773 (0x9ce448486e40ee0d)
Fetching namespace information from /namespaces/9ce448486e40ee0d
Namespace information:
  Registration type: 1
  Owner address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
  Parent ID: B0786316D5C1D9DD
  Depth: 2
  Level 0: B0786316D5C1D9DD
  Level 1: 9CE448486E40EE0D
  Start height: 2956766
  End height: 3046046

Some highlights from the output:

  • Full namespace path (line 7): The subnamespace ns_root.sub_1766533103 shows the full hierarchical name.

  • Parent namespace ID (lines 8, 33): The parent ID 0xb0786316d5c1d9dd links this subnamespace to its root.

  • Fee (line 16): The transaction fee of 0.016 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 19, 21): The id field shows the subnamespace ID as a decimal number, while name contains only the child portion encoded as hexadecimal (for example, 7375625f... decodes to sub_1...).

  • Child namespace ID (line 30): Shows both decimal and hexadecimal representations to match the id field on line 19.

  • Registration type (line 34): The value 1 indicates a subnamespace (versus 0 for root namespaces).

  • Owner address (line 35): The account that registered the namespace, which must be the same as the root namespace owner.

  • Depth (line 36): The depth of 2 indicates there are 2 levels in the namespace hierarchy. Level 0 is the root namespace, and level 1 is this subnamespace.

  • Levels (lines 37-38): The full hierarchical path. level0 contains the root namespace ID, and level1 contains the child ID. If depth were 3, level2 would contain the grandchild ID.

  • Start and end heights (lines 39-40): These values are inherited from the root namespace, not set independently.

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 subnamespace registration transaction
Retrieve the subnamespace /namespaces/{namespaceId} GET

Next Steps⚓︎

Now that you have a subnamespace, you can:

  • Register additional subnamespaces to expand your hierarchical structure
  • Link your namespace to a mosaic or account to create an alias