Skip to content

Registering a Subnamespace⚓︎

INTERMEDIATE

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

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

To learn how to register a root namespace instead, read the Registering a Root Namespace guide.

Prerequisites⚓︎

Before you start, make sure to:

Additionally, review the Transfer XEM 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.NemFacade import NemFacade
from symbolchain.nc import Amount
from symbolchain.nem.FeeCalculator import (
    calculate_namespace_rental_fee,
    calculate_transaction_fee
)

NODE_URL = os.getenv('NODE_URL', 'http://libertalia.nemtest.net:7890')
print(f'Using node {NODE_URL}')


# Helper function to announce a transaction
def announce_transaction(payload, label):
    announce_path = '/transaction/announce'
    print(f'Announcing {label} to {announce_path}')
    request = urllib.request.Request(
        f'{NODE_URL}{announce_path}',
        data=payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    with urllib.request.urlopen(request) as announce_response:
        result = json.loads(announce_response.read().decode())
    print(f'  Result: {result["message"]}')
    return result['message']


# Helper function to wait for transaction confirmation
def wait_for_confirmation(tx_hash, label):
    status_path = f'/transaction/get?hash={tx_hash}'
    print(f'Waiting for {label} confirmation from {status_path}')
    is_confirmed = False
    for _ in range(120):
        try:
            with urllib.request.urlopen(
                f'{NODE_URL}{status_path}'
            ) as status_response:
                confirmed = json.loads(status_response.read().decode())
                height = confirmed['meta']['height']
                print(f'{label} confirmed in block {height}')
                is_confirmed = True
                break
        except urllib.error.HTTPError:
            print('  Transaction status: pending')
        time.sleep(1)
    if not is_confirmed:
        print(f'{label} confirmation took too long.')


SIGNER_PRIVATE_KEY = os.getenv(
    'SIGNER_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000')
signer_key_pair = NemFacade.KeyPair(PrivateKey(SIGNER_PRIVATE_KEY))

facade = NemFacade('testnet')
signer_address = facade.network.public_key_to_address(
    signer_key_pair.public_key)
print(f'Signer address: {signer_address}')

try:
    # Choose the subnamespace name
    root_namespace_name = os.getenv('ROOT_NAMESPACE', 'ns_root')
    child_namespace_name = os.getenv(
        'SUBNAMESPACE', f'sub_{int(time.time())}')
    full_namespace_name = (
        f'{root_namespace_name}.{child_namespace_name}')
    print(f'Creating subnamespace: {full_namespace_name}')

    # Build the transaction
    rental_fee = calculate_namespace_rental_fee(False)
    print(f'  Namespace lease fee: {rental_fee / 1_000_000} XEM')

    transaction = facade.create_transaction_from_descriptor({
        'type': 'namespace_registration_transaction_v1',
        'rental_fee_sink': 'TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35',
        'rental_fee': rental_fee,
        'name': child_namespace_name,
        'parent_name': root_namespace_name
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)

    fee = calculate_transaction_fee(transaction)
    transaction.fee = Amount(fee)
    print(f'  Transaction fee: {fee / 1_000_000} XEM')

    # 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 the transaction
    announce_result = announce_transaction(
        json_payload, 'namespace registration')

    # Wait for confirmation
    if 'SUCCESS' == announce_result:
        wait_for_confirmation(transaction_hash, 'namespace registration')
    else:
        print(f'Transaction rejected: {announce_result}')
    # Retrieve the namespace
    namespace_path = f'/namespace?namespace={full_namespace_name}'
    print(f'Fetching namespace information from {namespace_path}')
    with urllib.request.urlopen(
        f'{NODE_URL}{namespace_path}'
    ) as response:
        namespace_info = json.loads(response.read().decode())
        print('Namespace information:')
        print(f'  Name: {namespace_info["fqn"]}')
        print(f'  Owner: {namespace_info["owner"]}')
        print(f'  Registration height: {namespace_info["height"]}')

except Exception as error:
    print(error)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    Address,
    NemFacade,
    calculateNamespaceRentalFee,
    calculateTransactionFee,
    descriptors,
    models
} from 'symbol-sdk/nem';

const NODE_URL = process.env.NODE_URL ||
    'http://libertalia.nemtest.net:7890';
console.log('Using node', NODE_URL);

// Helper function to announce a transaction
async function announceTransaction(payload, label) {
    const announcePath = '/transaction/announce';
    console.log(`Announcing ${label} to ${announcePath}`);
    const response = await fetch(`${NODE_URL}${announcePath}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: payload
    });
    if (!response.ok)
        throw new Error(`HTTP ${response.status}`);
    const result = await response.json();
    console.log('  Result:', result.message);
    return result.message;
}

// Helper function to wait for transaction confirmation
async function waitForConfirmation(transactionHash, label) {
    const statusPath = `/transaction/get?hash=${transactionHash}`;
    console.log(`Waiting for ${label} confirmation from`, statusPath);
    let isConfirmed = false;
    for (let attempt = 1; 120 >= attempt; ++attempt) {
        const response = await fetch(`${NODE_URL}${statusPath}`);
        if (!response.ok) {
            console.log('  Transaction status: pending');
            await new Promise(resolve => { setTimeout(resolve, 1000); });
        } else {
            const confirmed = await response.json();
            console.log(`${label} confirmed in block`,
                confirmed.meta.height);
            isConfirmed = true;
            break;
        }
    }
    if (!isConfirmed)
        console.warn(`${label} confirmation took too long.`);
}

const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const signerKeyPair = new NemFacade.KeyPair(
    new PrivateKey(SIGNER_PRIVATE_KEY));

const facade = new NemFacade('testnet');
const signerAddress = facade.network.publicKeyToAddress(
    signerKeyPair.publicKey);
console.log('Signer address:', signerAddress.toString());

try {
    // Choose the subnamespace name
    const rootNamespaceName = process.env.ROOT_NAMESPACE || 'ns_root';
    const childNamespaceName = process.env.SUBNAMESPACE ||
        `sub_${Math.floor(Date.now() / 1000)}`;
    const fullNamespaceName =
        `${rootNamespaceName}.${childNamespaceName}`;
    console.log('Creating subnamespace:', fullNamespaceName);

    // Build the transaction
    const rentalFee = calculateNamespaceRentalFee(false);
    console.log('  Namespace lease fee:',
        `${Number(rentalFee) / 1_000_000} XEM`);

    const transaction = facade.createTransactionFromTypedDescriptor(
        new descriptors.NamespaceRegistrationTransactionV1Descriptor(
            new Address('TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35'),
            new models.Amount(rentalFee),
            childNamespaceName,
            rootNamespaceName),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);

    const fee = calculateTransactionFee(transaction);
    transaction.fee = new models.Amount(fee);
    console.log(`  Transaction fee: ${Number(fee) / 1_000_000} XEM`);

    // 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 the transaction
    const announceResult = await announceTransaction(
        jsonPayload, 'namespace registration');

    // Wait for confirmation
    if ('SUCCESS' === announceResult)
        await waitForConfirmation(transactionHash, 'namespace registration');
    else
        console.log('Transaction rejected:', announceResult);
    // Retrieve the namespace
    const namespacePath = `/namespace?namespace=${fullNamespaceName}`;
    console.log('Fetching namespace information from', namespacePath);
    const namespaceResponse = await fetch(`${NODE_URL}${namespacePath}`);
    const namespaceInfo = await namespaceResponse.json();
    console.log('Namespace information:');
    console.log('  Name:', namespaceInfo.fqn);
    console.log('  Owner:', namespaceInfo.owner);
    console.log('  Registration height:', namespaceInfo.height);

} catch (e) {
    console.error(e.message, '| Cause:', e.cause?.code ?? 'unknown');
}

Download source

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

import static java.nio.charset.StandardCharsets.UTF_8;

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 com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.symbol.sdk.CryptoTypes;
import org.symbol.sdk.facade.NemFacade;
import org.symbol.sdk.nem.Address;
import org.symbol.sdk.nem.FeeCalculator;
import org.symbol.sdk.nem.KeyPair;
import org.symbol.sdk.nem.NemTransactionFactory;
import org.symbol.sdk.nem.descriptors.*;
import org.symbol.sdk.nem.models.Amount;
import org.symbol.sdk.nem.models.Transaction;

public final class RegisterSubnamespace {
    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", "http://libertalia.nemtest.net:7890");

    private final NemFacade facade = new NemFacade("testnet");

    private String announceTransaction(
        final String payload,
        final String label
    ) throws IOException, InterruptedException {
        final String announcePath = "/transaction/announce";
        System.out.printf("Announcing %s to %s%n", label, announcePath);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + announcePath))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        if (2 != response.statusCode() / 100)
            throw new IOException("HTTP " + response.statusCode());
        final String result = JSON_MAPPER.readTree(response.body())
            .get("message").asText();
        System.out.printf("  Result: %s%n", result);
        return result;
    }

    private void waitForConfirmation(
        final String transactionHash,
        final String label
    ) throws IOException, InterruptedException {
        final String statusPath = String.format(
            "/transaction/get?hash=%s", transactionHash);
        System.out.printf("Waiting for %s confirmation from %s%n",
            label, statusPath);
        boolean isConfirmed = false;
        for (int attempt = 1; 120 >= attempt; ++attempt) {
            final HttpRequest request = HttpRequest.newBuilder(
                URI.create(nodeUrl + statusPath)).GET().build();
            final HttpResponse<String> response = HTTP_CLIENT.send(
                request, BodyHandlers.ofString());
            if (2 != response.statusCode() / 100) {
                System.out.println("  Transaction status: pending");
                Thread.sleep(1000);
            } else {
                final JsonNode confirmed = JSON_MAPPER.readTree(
                    response.body());
                System.out.printf("%s confirmed in block %s%n", label,
                    confirmed.get("meta").get("height").asText());
                isConfirmed = true;
                break;
            }
        }
        if (!isConfirmed)
            System.out.printf("%s confirmation took too long.%n", label);
    }

    public static void main(final String[] args) {
        try {
            new RegisterSubnamespace().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 privateKey = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKey));
        System.out.printf("Signer address: %s%n",
            facade.network.publicKeyToAddress(
                signerKeyPair.getPublicKey()));

        // Choose the subnamespace name
        final String rootNamespaceName = System.getenv().getOrDefault(
            "ROOT_NAMESPACE",
            "ns_root");
        final String childNamespaceName = System.getenv().getOrDefault(
            "SUBNAMESPACE",
            "sub_" + System.currentTimeMillis() / 1000);
        final String fullNamespaceName =
            rootNamespaceName + "." + childNamespaceName;
        System.out.printf("Creating subnamespace: %s%n",
            fullNamespaceName);

        // Build the transaction
        final long rentalFee = FeeCalculator.calculateNamespaceRentalFee(
            false);
        System.out.printf("  Namespace lease fee: %s XEM%n",
            (double) rentalFee / 1_000_000);
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new NamespaceRegistrationTransactionV1Descriptor(
                    new Address(
                        "TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35"),
                    new Amount(rentalFee),
                    childNamespaceName.getBytes(UTF_8),
                    rootNamespaceName.getBytes(UTF_8)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);

        final long fee = FeeCalculator.calculateTransactionFee(
            transaction);
        transaction.setFee(new Amount(fee));
        System.out.printf("  Transaction fee: %s XEM%n",
            (double) fee / 1_000_000);

        // Sign transaction and generate final payload
        final CryptoTypes.Signature signature = facade.signTransaction(
            signerKeyPair, transaction);
        final String payload = NemTransactionFactory.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);
        final String result = announceTransaction(
            payload, "namespace registration");
        // Wait for confirmation
        if ("SUCCESS".equals(result))
            waitForConfirmation(
                transactionHash, "namespace registration");
        else
            System.out.printf("Transaction rejected: %s%n", result);
        // Retrieve the namespace
        final String path = "/namespace?namespace=" + fullNamespaceName;
        System.out.printf(
            "Fetching namespace information from %s%n", path);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        final JsonNode info = JSON_MAPPER.readTree(response.body());
        System.out.println("Namespace information:");
        System.out.printf("  Name: %s%n", info.get("fqn").asText());
        System.out.printf("  Owner: %s%n", info.get("owner").asText());
        System.out.printf("  Registration height: %s%n",
            info.get("height").asText());

    }
}

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, creating a transaction, announcing) and the transaction descriptor fields shared with a root namespace, see Registering a Root Namespace.

Choosing the Subnamespace Name⚓︎

    # Choose the subnamespace name
    root_namespace_name = os.getenv('ROOT_NAMESPACE', 'ns_root')
    child_namespace_name = os.getenv(
        'SUBNAMESPACE', f'sub_{int(time.time())}')
    full_namespace_name = (
        f'{root_namespace_name}.{child_namespace_name}')
    print(f'Creating subnamespace: {full_namespace_name}')
    // Choose the subnamespace name
    const rootNamespaceName = process.env.ROOT_NAMESPACE || 'ns_root';
    const childNamespaceName = process.env.SUBNAMESPACE ||
        `sub_${Math.floor(Date.now() / 1000)}`;
    const fullNamespaceName =
        `${rootNamespaceName}.${childNamespaceName}`;
    console.log('Creating subnamespace:', fullNamespaceName);
        // Choose the subnamespace name
        final String rootNamespaceName = System.getenv().getOrDefault(
            "ROOT_NAMESPACE",
            "ns_root");
        final String childNamespaceName = System.getenv().getOrDefault(
            "SUBNAMESPACE",
            "sub_" + System.currentTimeMillis() / 1000);
        final String fullNamespaceName =
            rootNamespaceName + "." + childNamespaceName;
        System.out.printf("Creating subnamespace: %s%n",
            fullNamespaceName);

A subnamespace is identified by its full name, which joins the parent namespace name and the child name with a dot, such as ns_root.product. See Name in the Textbook for the naming rules.

To avoid collisions across multiple runs of the tutorial, a timestamp is added to the child name. In practice, however, programs would use a fixed name for their subnamespaces. You can force the tutorial to use fixed names through the ROOT_NAMESPACE and SUBNAMESPACE environment variables.

Use a parent namespace owned by the signer

By default, the code uses the test account referenced by SIGNER_PRIVATE_KEY and a parent namespace named ns_root.

If you come from the Registering a Root Namespace tutorial, set the SIGNER_PRIVATE_KEY and ROOT_NAMESPACE environment variables to match the account and namespace you created there, or any other namespace that the signer owns.

Building the Transaction⚓︎

    # Build the transaction
    rental_fee = calculate_namespace_rental_fee(False)
    print(f'  Namespace lease fee: {rental_fee / 1_000_000} XEM')

    transaction = facade.create_transaction_from_descriptor({
        'type': 'namespace_registration_transaction_v1',
        'rental_fee_sink': 'TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35',
        'rental_fee': rental_fee,
        'name': child_namespace_name,
        'parent_name': root_namespace_name
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)

    fee = calculate_transaction_fee(transaction)
    transaction.fee = Amount(fee)
    print(f'  Transaction fee: {fee / 1_000_000} XEM')
    // Build the transaction
    const rentalFee = calculateNamespaceRentalFee(false);
    console.log('  Namespace lease fee:',
        `${Number(rentalFee) / 1_000_000} XEM`);

    const transaction = facade.createTransactionFromTypedDescriptor(
        new descriptors.NamespaceRegistrationTransactionV1Descriptor(
            new Address('TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35'),
            new models.Amount(rentalFee),
            childNamespaceName,
            rootNamespaceName),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);

    const fee = calculateTransactionFee(transaction);
    transaction.fee = new models.Amount(fee);
    console.log(`  Transaction fee: ${Number(fee) / 1_000_000} XEM`);
        // Build the transaction
        final long rentalFee = FeeCalculator.calculateNamespaceRentalFee(
            false);
        System.out.printf("  Namespace lease fee: %s XEM%n",
            (double) rentalFee / 1_000_000);
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new NamespaceRegistrationTransactionV1Descriptor(
                    new Address(
                        "TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35"),
                    new Amount(rentalFee),
                    childNamespaceName.getBytes(UTF_8),
                    rootNamespaceName.getBytes(UTF_8)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);

        final long fee = FeeCalculator.calculateTransactionFee(
            transaction);
        transaction.setFee(new Amount(fee));
        System.out.printf("  Transaction fee: %s XEM%n",
            (double) fee / 1_000_000);

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

  • : The name of the subnamespace, chosen in the previous step.

    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 and .

  • : The name of the parent namespace, defined in the previous step. It can be a root namespace or another subnamespace.

  • : The lease fee, which is 10 XEM for subnamespaces, paid to the same sink account as root namespaces.

    The SDK's helper returns the required amount. The argument requests the fee for a subnamespace.

The transaction is then signed, announced, and confirmed following the same process as in the Registering a Root Namespace tutorial. The facade adds the signer and derives the timestamp and deadline from the two-hour deadline duration.

Retrieving the Subnamespace⚓︎

    # Retrieve the namespace
    namespace_path = f'/namespace?namespace={full_namespace_name}'
    print(f'Fetching namespace information from {namespace_path}')
    with urllib.request.urlopen(
        f'{NODE_URL}{namespace_path}'
    ) as response:
        namespace_info = json.loads(response.read().decode())
        print('Namespace information:')
        print(f'  Name: {namespace_info["fqn"]}')
        print(f'  Owner: {namespace_info["owner"]}')
        print(f'  Registration height: {namespace_info["height"]}')
    // Retrieve the namespace
    const namespacePath = `/namespace?namespace=${fullNamespaceName}`;
    console.log('Fetching namespace information from', namespacePath);
    const namespaceResponse = await fetch(`${NODE_URL}${namespacePath}`);
    const namespaceInfo = await namespaceResponse.json();
    console.log('Namespace information:');
    console.log('  Name:', namespaceInfo.fqn);
    console.log('  Owner:', namespaceInfo.owner);
    console.log('  Registration height:', namespaceInfo.height);
        // Retrieve the namespace
        final String path = "/namespace?namespace=" + fullNamespaceName;
        System.out.printf(
            "Fetching namespace information from %s%n", path);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        final JsonNode info = JSON_MAPPER.readTree(response.body());
        System.out.println("Namespace information:");
        System.out.printf("  Name: %s%n", info.get("fqn").asText());
        System.out.printf("  Owner: %s%n", info.get("owner").asText());
        System.out.printf("  Registration height: %s%n",
            info.get("height").asText());

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

The subnamespace is queried by its full name, which joins the parent and child names with a dot (for example, ns_root.sub_1783411728).

A successful response confirms the subnamespace is registered and active.

The response also shows the registration height, which is the block in which the root namespace was registered, because subnamespaces inherit their root namespace's lease.

Subnamespace duration

A subnamespace expires when its root namespace expires and cannot be renewed on its own. Renewing the root namespace also renews the subnamespace.

Output⚓︎

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

Using node http://libertalia.nemtest.net:7890
Signer address: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
Creating subnamespace: ns_root.sub_1783411728
  Namespace lease fee: 10.0 XEM
  Transaction fee: 0.15 XEM
Built transaction:
{
  "type": 8193,
  "version": 1,
  "network": 152,
  "timestamp": 355824143,
  "signer_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
  "signature": "EB8065E0D51173189950A7F28863135AA2E308B1F9A32840BCF48E85F15E0DD0AC48D8B6999D2BEDDC4B934842F93C9C4B4AFF28820CFA3B7EE73FE09B3D2303",
  "fee": "150000",
  "deadline": 355831343,
  "rental_fee_sink": "54414D4553504143455748344D4B464D42435646455244504F4F5034464B374D54444A4559503335",
  "rental_fee": "10000000",
  "name": "7375625f31373833343131373238",
  "parent_name": "6e735f726f6f74"
}
Transaction hash: 79C4412BC6A9AA73DB37AD01ECDC84A17926FB5E152979FFDCCFE567EC4F63ED
Announcing namespace registration to /transaction/announce
  Result: SUCCESS
Waiting for namespace registration confirmation from /transaction/get?hash=79C4412BC6A9AA73DB37AD01ECDC84A17926FB5E152979FFDCCFE567EC4F63ED
  Transaction status: pending
  Transaction status: pending
  Transaction status: pending
namespace registration confirmed in block 690650
Fetching namespace information from /namespace?namespace=ns_root.sub_1783411728
Namespace information:
  Name: ns_root.sub_1783411728
  Owner: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
  Registration height: 690649

Some highlights from the output:

  • Full namespace path (line 3): ns_root.sub_1783411728 combines the parent namespace ns_root with the subnamespace name set in the transaction.

  • Lease fee and transaction fee (lines 4-5): The lease fee is 10 XEM because this is a subnamespace (root namespaces pay 100 XEM instead), while the transaction fee is 0.15 XEM.

  • Namespace information (lines 31-33): The registered subnamespace, its owner (the signer's address), and the registration height, which is the block at which the root namespace's lease began, inherited by the subnamespace.

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Build a subnamespace registration transaction , NamespaceRegistrationTransactionV1
Calculate the lease fee
Retrieve the subnamespace /namespace GET

Next Steps⚓︎

Now that you have a subnamespace, you can:

  • Register additional subnamespaces to expand your hierarchical structure
  • Define mosaics under the subnamespace to create custom assets