Skip to content
INTERMEDIATE

Linking and Unlinking Namespaces to Addresses⚓︎

Namespaces can be linked to addresses to create human-readable aliases that can be used instead of long hexadecimal addresses in transactions.

This tutorial shows how to link a namespace to an account address and how to unlink it when no longer needed.

Prerequisites⚓︎

Before you start, make sure to:

Additionally, review the Transfer transaction tutorial to understand how transactions are announced and confirmed.

Namespace ownership required

Only the account that owns the namespace can link it to an address. The target address does not need to cosign or approve the link.

Full Code⚓︎

import json
import os
import time
import urllib.request

from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.SymbolFacade import SymbolFacade
from symbolchain.sc import Amount, NamespaceId
from symbolchain.symbol.IdGenerator import (
    generate_mosaic_alias_id,
    generate_namespace_path
)
from symbolchain.symbol.Network import Address, NetworkTimestamp

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


# Helper function to announce a transaction
def announce_transaction(payload, label):
    print(f'Announcing {label} to /transactions')
    request = urllib.request.Request(
        f'{NODE_URL}/transactions',
        data=payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='PUT'
    )
    with urllib.request.urlopen(request) as announce_response:
        print(f'  Response: {announce_response.read().decode()}')


# Helper function to wait for transaction confirmation
def wait_for_confirmation(tx_hash, label):
    print(f'Waiting for {label} confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            url = f'{NODE_URL}/transactionStatus/{tx_hash}'
            with urllib.request.urlopen(url) as confirm_response:
                status = json.loads(confirm_response.read().decode())
                print(f"  Transaction status: {status['group']}")
                if status['group'] == 'confirmed':
                    print(f'{label} confirmed in {attempt} seconds')
                    return
                if status['group'] == 'failed':
                    raise RuntimeError(f"{label} failed: {status['code']}")
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')
    raise TimeoutError(f'{label} not confirmed after 60 seconds')


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

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

namespace_name = os.getenv('NAMESPACE_NAME', 'my_namespace')
print(f'Namespace name: {namespace_name}')

namespace_id = generate_namespace_path(namespace_name)[-1]
print(f'Namespace ID: {namespace_id} ({hex(namespace_id)})')

# Target address to link the namespace to
target_address = Address(
    os.getenv('TARGET_ADDRESS',
    'TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI'))
print(f'Target address: {target_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_multiplier = response_json['medianFeeMultiplier']
        minimum_multiplier = response_json['minFeeMultiplier']
        fee_multiplier = max(median_multiplier, minimum_multiplier)
        print(f'  Fee multiplier: {fee_multiplier}')

    # Build the alias transaction
    transaction = facade.transaction_factory.create({
        'type': 'address_alias_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'namespace_id': namespace_id,
        'address': target_address,
        'alias_action': 'link'
    })
    transaction.fee = Amount(fee_multiplier * transaction.size)

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

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

    # Announce and confirm transaction
    announce_transaction(json_payload, 'address alias transaction')
    wait_for_confirmation(transaction_hash, 'address alias transaction')

    # Retrieve the namespace to verify the alias
    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('Alias information:')
        alias_type = namespace_info['alias']['type']
        print(f'  Alias type: {alias_type}')
        if alias_type == 2:  # ADDRESS type
            aliased_address = Address.from_decoded_address_hex_string(
                namespace_info['alias']['address'])
            print(f'  Linked address: {aliased_address}')

    # Send a transfer using the alias instead of a raw address
    print(f'Using alias in transfer: {namespace_name}')

    # Encode the namespace ID as a recipient address
    recipient_id = generate_namespace_path(namespace_name)[-1]
    recipient_address = Address.from_namespace_id(
        NamespaceId(recipient_id), facade.network.identifier)
    print(f'Recipient address (alias): {recipient_address}')

    test_transaction = facade.transaction_factory.create({
        'type': 'transfer_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'recipient_address': recipient_address,
        'mosaics': [{
            'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
            'amount': 1_000_000  # 1 XYM
        }]
    })
    test_transaction.fee = Amount(fee_multiplier * test_transaction.size)
    test_json_payload = facade.transaction_factory.attach_signature(
        test_transaction,
        facade.sign_transaction(signer_key_pair, test_transaction))
    print('Test transaction:')
    print(json.dumps(test_transaction.to_json(), indent=2))
    test_transaction_hash = facade.hash_transaction(test_transaction)
    print(f'Transaction hash: {test_transaction_hash}')
    announce_transaction(test_json_payload, 'test transaction')
    wait_for_confirmation(test_transaction_hash, 'test transaction')

except Exception as e:
    print(e)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    Address,
    NetworkTimestamp,
    SymbolFacade,
    generateMosaicAliasId,
    generateNamespacePath,
    models
} from 'symbol-sdk/symbol';

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

// Helper function to announce a transaction
async function announceTransaction(payload, label) {
    console.log(`Announcing ${label} to /transactions`);
    const response = await fetch(`${NODE_URL}/transactions`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: payload
    });
    console.log('  Response:', await response.text());
}

// Helper function to wait for transaction confirmation
async function waitForConfirmation(transactionHash, label) {
    console.log(`Waiting for ${label} confirmation...`);
    for (let attempt = 0; 60 > attempt; attempt++) {
        await new Promise(resolve => { setTimeout(resolve, 1000); });
        try {
            const response = await fetch(
                `${NODE_URL}/transactionStatus/${transactionHash}`);
            const status = await response.json();
            console.log('  Transaction status:', status.group);
            if ('confirmed' === status.group) {
                console.log(`${label} confirmed in`, attempt, 'seconds');
                return;
            }
            if ('failed' === status.group)
                throw new Error(`${label} failed: ${status.code}`);
        } catch (e) {
            if (e.message.includes('failed'))
                throw e;
            console.log('  Transaction status: unknown');
        }
    }
    throw new Error(`${label} not confirmed after 60 seconds`);
}


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

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

const namespaceName = process.env.NAMESPACE_NAME || 'my_namespace';
console.log('Namespace name:', namespaceName);

const nsPath = generateNamespacePath(namespaceName);
const namespaceId = nsPath[nsPath.length - 1];
console.log(
    'Namespace ID:',
    `${namespaceId} (0x${namespaceId.toString(16)})`);

// Target address to link the namespace to
const targetAddress = new SymbolFacade.Address(
    process.env.TARGET_ADDRESS ||
    'TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI');
console.log('Target address:', targetAddress.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 medianMultiplier = feeJSON.medianFeeMultiplier;
    const minimumMultiplier = feeJSON.minFeeMultiplier;
    const feeMultiplier = Math.max(medianMultiplier, minimumMultiplier);
    console.log('  Fee multiplier:', feeMultiplier);

    // Build the alias transaction
    const transaction = facade.transactionFactory.create({
        type: 'address_alias_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        namespaceId,
        address: targetAddress,
        aliasAction: 'link'
    });
    transaction.fee = new models.Amount(feeMultiplier * transaction.size);

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

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

    // Announce and confirm transaction
    await announceTransaction(jsonPayload, 'address alias transaction');
    await waitForConfirmation(transactionHash, 'address alias transaction');

    // Retrieve the namespace to verify the alias
    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('Alias information:');
    console.log('  Alias type:', namespaceInfo.alias.type);
    if (2 === namespaceInfo.alias.type) { // ADDRESS type
        const aliasedAddress = Address.fromDecodedAddressHexString(
            namespaceInfo.alias.address);
        console.log('  Linked address:', aliasedAddress.toString());
    }

    // Send a transfer using the alias instead of a raw address
    console.log('Using alias in transfer:', namespaceName);

    // Encode the namespace ID as a recipient address
    const recipientPath = generateNamespacePath(namespaceName);
    const recipientId = recipientPath[recipientPath.length - 1];
    const recipientAddress = Address.fromNamespaceId(
        new models.NamespaceId(recipientId), facade.network.identifier);
    console.log('  Recipient address (alias):',
        recipientAddress.toString());

    const test_transaction = facade.transactionFactory.create({
        type: 'transfer_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        recipientAddress,
        mosaics: [{
            mosaicId: generateMosaicAliasId('symbol.xym'),
            amount: 1_000_000n // 1 XYM
        }]
    });
    test_transaction.fee = new models.Amount(
        feeMultiplier * test_transaction.size);
    const testJsonPayload =
        facade.transactionFactory.static.attachSignature(
            test_transaction,
            facade.signTransaction(signerKeyPair, test_transaction));
    console.log('Test transaction:');
    console.dir(test_transaction.toJson(), { colors: true });
    const testTransactionHash =
        facade.hashTransaction(test_transaction).toString();
    console.log('Transaction hash:', testTransactionHash);
    await announceTransaction(testJsonPayload, 'test transaction');
    await waitForConfirmation(testTransactionHash, 'test transaction');

} 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 must own the namespace being linked.

Defining the Namespace and Target Address⚓︎

namespace_name = os.getenv('NAMESPACE_NAME', 'my_namespace')
print(f'Namespace name: {namespace_name}')

namespace_id = generate_namespace_path(namespace_name)[-1]
print(f'Namespace ID: {namespace_id} ({hex(namespace_id)})')

# Target address to link the namespace to
target_address = Address(
    os.getenv('TARGET_ADDRESS',
    'TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI'))
print(f'Target address: {target_address}')
const namespaceName = process.env.NAMESPACE_NAME || 'my_namespace';
console.log('Namespace name:', namespaceName);

const nsPath = generateNamespacePath(namespaceName);
const namespaceId = nsPath[nsPath.length - 1];
console.log(
    'Namespace ID:',
    `${namespaceId} (0x${namespaceId.toString(16)})`);

// Target address to link the namespace to
const targetAddress = new SymbolFacade.Address(
    process.env.TARGET_ADDRESS ||
    'TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI');
console.log('Target address:', targetAddress.toString());

The code defines:

  • Namespace name: The namespace to link, read from the NAMESPACE_NAME environment variable, which defaults to my_namespace if not set. This name must match a namespace that your account already owns.
  • Namespace ID: The ID is generated from the namespace name using , which returns an array of IDs for each level in the hierarchy. The last element is selected to get the final namespace ID. Taking the last element works for both root namespaces and subnamespaces.

    For a root namespace like foo, the array contains one element. For a subnamespace like symbol.xym, it contains two elements, and the last one is the ID of xym under symbol.

    Subnamespace IDs are unique

    Subnamespace IDs are derived hierarchically, so two subnamespaces with the same leaf name but different parents produce different IDs. For example, the last element of the path for foo.xym and bar.xym will be different.

  • Target address: The address that the namespace will point to, read from the TARGET_ADDRESS environment variable. If not set, a default test address is used.

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_multiplier = response_json['medianFeeMultiplier']
        minimum_multiplier = response_json['minFeeMultiplier']
        fee_multiplier = max(median_multiplier, minimum_multiplier)
        print(f'  Fee multiplier: {fee_multiplier}')
    // 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 medianMultiplier = feeJSON.medianFeeMultiplier;
    const minimumMultiplier = feeJSON.minFeeMultiplier;
    const feeMultiplier = Math.max(medianMultiplier, minimumMultiplier);
    console.log('  Fee multiplier:', feeMultiplier);

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 alias transaction
    transaction = facade.transaction_factory.create({
        'type': 'address_alias_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'namespace_id': namespace_id,
        'address': target_address,
        'alias_action': 'link'
    })
    transaction.fee = Amount(fee_multiplier * transaction.size)
    // Build the alias transaction
    const transaction = facade.transactionFactory.create({
        type: 'address_alias_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        namespaceId,
        address: targetAddress,
        aliasAction: 'link'
    });
    transaction.fee = new models.Amount(feeMultiplier * transaction.size);

The address alias transaction specifies:

  • Type: Address alias transactions use the type AddressAliasTransactionV1.

  • Signer public key: The account that owns the namespace and will pay the transaction fee.

  • Namespace ID: The identifier of the namespace being linked.

  • Address: The target address to link to the namespace.

  • Alias action: The value link creates the alias. To remove the alias later, use unlink instead.

Unlinking an alias

To unlink a namespace from an address, announce another AddressAliasTransactionV1 transaction with the same namespace ID and address, but set the field to unlink.

The unlinking process does not remove the namespace itself, only the association between the namespace and the address. After unlinking, the namespace can be linked to a different address or mosaic.

Submitting the Transaction⚓︎

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

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

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

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

    // Announce and confirm transaction
    await announceTransaction(jsonPayload, 'address alias transaction');
    await waitForConfirmation(transactionHash, 'address alias transaction');

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

Verifying the Alias⚓︎

    # Retrieve the namespace to verify the alias
    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('Alias information:')
        alias_type = namespace_info['alias']['type']
        print(f'  Alias type: {alias_type}')
        if alias_type == 2:  # ADDRESS type
            aliased_address = Address.from_decoded_address_hex_string(
                namespace_info['alias']['address'])
            print(f'  Linked address: {aliased_address}')
    // Retrieve the namespace to verify the alias
    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('Alias information:');
    console.log('  Alias type:', namespaceInfo.alias.type);
    if (2 === namespaceInfo.alias.type) { // ADDRESS type
        const aliasedAddress = Address.fromDecodedAddressHexString(
            namespaceInfo.alias.address);
        console.log('  Linked address:', aliasedAddress.toString());
    }

To verify the alias was created, the code retrieves the namespace information from the network using the /namespaces/{namespaceId} GET endpoint.

The response includes the alias type (address) and the linked address, confirming the namespace now points to the specified address.

Using the Alias⚓︎

    # Send a transfer using the alias instead of a raw address
    print(f'Using alias in transfer: {namespace_name}')

    # Encode the namespace ID as a recipient address
    recipient_id = generate_namespace_path(namespace_name)[-1]
    recipient_address = Address.from_namespace_id(
        NamespaceId(recipient_id), facade.network.identifier)
    print(f'Recipient address (alias): {recipient_address}')

    test_transaction = facade.transaction_factory.create({
        'type': 'transfer_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'recipient_address': recipient_address,
        'mosaics': [{
            'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
            'amount': 1_000_000  # 1 XYM
        }]
    })
    test_transaction.fee = Amount(fee_multiplier * test_transaction.size)
    test_json_payload = facade.transaction_factory.attach_signature(
        test_transaction,
        facade.sign_transaction(signer_key_pair, test_transaction))
    print('Test transaction:')
    print(json.dumps(test_transaction.to_json(), indent=2))
    test_transaction_hash = facade.hash_transaction(test_transaction)
    print(f'Transaction hash: {test_transaction_hash}')
    announce_transaction(test_json_payload, 'test transaction')
    wait_for_confirmation(test_transaction_hash, 'test transaction')
    // Send a transfer using the alias instead of a raw address
    console.log('Using alias in transfer:', namespaceName);

    // Encode the namespace ID as a recipient address
    const recipientPath = generateNamespacePath(namespaceName);
    const recipientId = recipientPath[recipientPath.length - 1];
    const recipientAddress = Address.fromNamespaceId(
        new models.NamespaceId(recipientId), facade.network.identifier);
    console.log('  Recipient address (alias):',
        recipientAddress.toString());

    const test_transaction = facade.transactionFactory.create({
        type: 'transfer_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        recipientAddress,
        mosaics: [{
            mosaicId: generateMosaicAliasId('symbol.xym'),
            amount: 1_000_000n // 1 XYM
        }]
    });
    test_transaction.fee = new models.Amount(
        feeMultiplier * test_transaction.size);
    const testJsonPayload =
        facade.transactionFactory.static.attachSignature(
            test_transaction,
            facade.signTransaction(signerKeyPair, test_transaction));
    console.log('Test transaction:');
    console.dir(test_transaction.toJson(), { colors: true });
    const testTransactionHash =
        facade.hashTransaction(test_transaction).toString();
    console.log('Transaction hash:', testTransactionHash);
    await announceTransaction(testJsonPayload, 'test transaction');
    await waitForConfirmation(testTransactionHash, 'test transaction');

Once the namespace is linked to an address, the namespace can be used in place of the address in transactions. The code demonstrates creating a transfer transaction using the alias as the recipient address instead of the full hexadecimal address.

To use a namespace as a recipient address, the namespace ID is converted into a 24-byte address using . As described in the previous section, the last component of the namespace path is used as the namespace ID.

The rest of the transaction is a conventional transfer transaction. For more details on how to sign, announce, and confirm transactions, see the Transfer Transaction tutorial.

Address Resolution Receipt

When the network processes a transaction that uses a namespace alias as a recipient address, it generates an Address Resolution Receipt. This receipt records the actual address the alias pointed to at the time the transaction was confirmed.

This is important for historical auditability: since aliases can be changed or removed at any time, the receipt ensures that the resolved address can always be verified, even if the alias has since been updated.

Resolution receipts can be queried using the /statements/resolutions/address GET endpoint. For more details on receipts, see the Resolution Statements section in the Textbook.

Output⚓︎

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

Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Namespace name: ns_1778584141043
Namespace ID: 16980998145596513971 (0xeba89fd81fd8deb3)
Target address: TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI
Fetching current network time from /node/time
  Network time: 111527494401n ms since nemesis
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100
Built transaction:
{
  signature: 'BDFC6FE36DF7070CD14CCE4F8559D472F18F0BCC231E46767B1BC58C1581AF5DB10E6A3429158CD6C874FD28C8F40D44FEE258DDF60A402993A88AB1E23DFE07',
  signerPublicKey: '3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29',
  version: 1,
  network: 152,
  type: 16974,
  fee: '16100',
  deadline: '111534694401',
  namespaceId: '16980998145596513971',
  address: '98AD8BAAB80B1DC684542EC175259711AB2C41D2FE4DA9AD',
  aliasAction: 1
}
Transaction hash: 868226671E678B3929E8FDE8C89846A494DA3713D290298CB6AD41E62B9E897B
Announcing address alias transaction to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for address alias transaction confirmation...
  Transaction status: unconfirmed
  Transaction status: confirmed
address alias transaction confirmed in 7 seconds
Fetching namespace information from /namespaces/eba89fd81fd8deb3
Alias information:
  Alias type: 2
  Linked address: TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI
Using alias in transfer: ns_1778584141043
  Recipient address (alias): TGZ55WA73CP2R2YAAAAAAAAAAAAAAAAAAAAAAAA
Test transaction:
{
  signature: '0001D617313CD13F5E5283196B711CFECB0AC81F563300B80C1CB30FD43AB40AF03FBCAEC2BDE4C27B58AD183E6D6179CCB8AE873199933A04FB9C7232646008',
  signerPublicKey: '3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29',
  version: 1,
  network: 152,
  type: 16724,
  fee: '17600',
  deadline: '111534694401',
  recipientAddress: '99B3DED81FD89FA8EB000000000000000000000000000000',
  mosaics: [ { mosaicId: '16666583871264174062', amount: '1000000' } ],
  message: ''
}
Transaction hash: 9F6F03950D6F4B893F95EEE67AF8CC2BEB3F077BFF738712387EA7555D9D5547
Announcing test transaction to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for test transaction confirmation...
  Transaction status: unconfirmed
  Transaction status: confirmed
test transaction confirmed in 30 seconds

Some highlights from the output:

  • Namespace and target (lines 3, 5): The namespace and the address being linked.

  • Transaction hash (line 23): The transaction hash can be used to search for the transaction in the Symbol Testnet Explorer.

  • Alias verification (lines 32-33): The namespace information confirms the alias type is 2 (address) and shows the linked address.

  • Using the alias (line 35): A transfer transaction is created using the alias as the recipient, demonstrating that it can be used in place of the full address.

    Different recipient address

    The recipient address differs from the target address because it is the encoded namespace ID, not the target address itself. The network resolves the alias to the linked address when processing the transaction.

Conclusion⚓︎

This tutorial showed how to:

Step Related documentation
Generate namespace ID
Build an address alias transaction
Verify the alias /namespaces/{namespaceId} GET
Use the alias in a transfer
Query address resolution receipts /statements/resolutions/address GET