Skip to content

Registering a Root Namespace⚓︎

INTERMEDIATE

Namespaces provide labels that group related mosaics under a meaningful name, like the nem prefix in the native nem:xem mosaic.

Namespaces can be nested under other namespaces, and this tutorial shows how to register a root namespace for one year.

To learn how to register a subnamespace instead, read the Registering a Subnamespace 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:
    # 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
    rental_fee = calculate_namespace_rental_fee(True)
    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': namespace_name
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)

    # Calculate and attach the transaction fee
    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={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 {
    // Build the namespace name
    const namespaceName = process.env.ROOT_NAMESPACE ||
        `ns_${Math.floor(Date.now() / 1000)}`;
    console.log('Creating root namespace:', namespaceName);

    // Build the transaction
    const rentalFee = calculateNamespaceRentalFee(true);
    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),
            namespaceName),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);


    // Calculate and attach the transaction fee
    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=${namespaceName}`;
    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 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", "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 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 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()));

        // 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 long rentalFee = FeeCalculator.calculateNamespaceRentalFee(
            true);
        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),
                    namespaceName.getBytes(UTF_8),
                    null),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);

        // Calculate and attach the transaction fee
        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);

        // Announce the transaction
        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=" + namespaceName;
        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⚓︎

Setting Up the Account⚓︎

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

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.

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_${Math.floor(Date.now() / 1000)}`;
    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 reserves on the network for one year. See Name in the Textbook for the naming rules.

To avoid collisions across multiple runs of the tutorial, a timestamp is added to the name. In practice, however, 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
    rental_fee = calculate_namespace_rental_fee(True)
    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': namespace_name
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)
    // Build the transaction
    const rentalFee = calculateNamespaceRentalFee(true);
    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),
            namespaceName),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);
        // Build the transaction
        final long rentalFee = FeeCalculator.calculateNamespaceRentalFee(
            true);
        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),
                    namespaceName.getBytes(UTF_8),
                    null),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);

The namespace registration transaction then registers the namespace on the network, specifying:

  • Transaction type: Namespace registration transactions use NamespaceRegistrationTransactionV1.

  • : The special account that collects namespace lease fees. Each network has a fixed sink address:

    • mainnet: NAMESPACEWH4MKFMBCVFERDPOOP4FK7MTBXDPZZA
    • testnet: TAMESPACEWH4MKFMBCVFERDPOOP4FK7MTDJEYP35

    The network rejects transactions that send the lease fee to any other address.

  • : The lease fee, which is 100 XEM for root namespaces. The SDK's helper returns the required amount. The argument requests the fee for a root namespace.

    The network rejects transactions that pay less than this fee. Larger amounts are accepted, but the entire amount is transferred to the sink account.

  • : The name of the root namespace.

The facade adds the signer, which becomes the namespace owner, and derives the timestamp and deadline from the two-hour deadline duration.

    # Calculate and attach the transaction fee
    fee = calculate_transaction_fee(transaction)
    transaction.fee = Amount(fee)
    print(f'  Transaction fee: {fee / 1_000_000} XEM')
    // Calculate and attach the transaction fee
    const fee = calculateTransactionFee(transaction);
    transaction.fee = new models.Amount(fee);
    console.log(`  Transaction fee: ${Number(fee) / 1_000_000} XEM`);
        // Calculate and attach the transaction fee
        final long fee = FeeCalculator.calculateTransactionFee(
            transaction);
        transaction.setFee(new Amount(fee));
        System.out.printf("  Transaction fee: %s XEM%n",
            (double) fee / 1_000_000);

Finally, the transaction fee is calculated with and attached to the transaction. Unlike the lease fee, the transaction fee is paid to the harvester account. Namespace registration transactions pay a fixed transaction fee of 0.15 XEM, as shown in the fee schedule.

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

        // Announce the transaction
        final String result = announceTransaction(
            payload, "namespace registration");

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

    # Wait for confirmation
    if 'SUCCESS' == announce_result:
        wait_for_confirmation(transaction_hash, 'namespace registration')
    else:
        print(f'Transaction rejected: {announce_result}')
    // Wait for confirmation
    if ('SUCCESS' === announceResult)
        await waitForConfirmation(transactionHash, 'namespace registration');
    else
        console.log('Transaction rejected:', announceResult);
        // Wait for confirmation
        if ("SUCCESS".equals(result))
            waitForConfirmation(
                transactionHash, "namespace registration");
        else
            System.out.printf("Transaction rejected: %s%n", result);

The code then waits for the transaction to be confirmed by polling the /transaction/get GET endpoint until the transaction is included in a block.

Retrieving the Namespace⚓︎

    # Retrieve the namespace
    namespace_path = f'/namespace?namespace={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=${namespaceName}`;
    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=" + namespaceName;
        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 namespace was registered, the code retrieves it from the network using the /namespace GET endpoint and displays its properties.

A successful response confirms that the namespace is registered and active.

The response also shows the registration height, which is the block in which the namespace was registered, marking the start of the one-year lease.

Output⚓︎

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

Using node http://libertalia.nemtest.net:7890
Signer address: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
Creating root namespace: ns_1783091378
  Namespace lease fee: 100.0 XEM
  Transaction fee: 0.15 XEM
Built transaction:
{
  "type": 8193,
  "version": 1,
  "network": 152,
  "timestamp": 355503793,
  "signer_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
  "signature": "D15220D1888AC85CE205D4D2B1AF3540CA415716DD266A29646FE0DEFAFBED924F0B698C4F6EEAA706AA836FA82516552D54B5BCE91D841D6F1C865633EED50D",
  "fee": "150000",
  "deadline": 355510993,
  "rental_fee_sink": "54414D4553504143455748344D4B464D42435646455244504F4F5034464B374D54444A4559503335",
  "rental_fee": "100000000",
  "name": "6e735f31373833303931333738"
}
Transaction hash: D56EDC5946F1A41304CDDC2BC322793C558DFB57831EF7D45024DE46D6AD827F
Announcing namespace registration to /transaction/announce
  Result: SUCCESS
Waiting for namespace registration confirmation from /transaction/get?hash=D56EDC5946F1A41304CDDC2BC322793C558DFB57831EF7D45024DE46D6AD827F
  Transaction status: pending
  Transaction status: pending
  Transaction status: pending
namespace registration confirmed in block 685363
Fetching namespace information from /namespace?namespace=ns_1783091378
Namespace information:
  Name: ns_1783091378
  Owner: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
  Registration height: 685363

Some highlights from the output:

  • Namespace name (line 3): The chosen name ns_1783091378 includes a timestamp to ensure uniqueness. Search for this name in the NEM testnet explorer to view the namespace details.

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

  • Namespace information (lines 30-32): The registered namespace, its owner (the signer's address), and the registration height, which is the block at which the lease began.

Conclusion⚓︎

This tutorial showed how to:

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

Next Steps⚓︎

Now that you have a root namespace, you can: