コンテンツにスキップ

ネームスペースのアドレスへのリンクと解除⚓︎

中級

ネームスペースアドレス にリンクさせることができます。これにより、トランザクション において長い16進数のアドレスの代わりに、人間が読み取り可能なエイリアス(別名)を使用できるようになります。

このチュートリアルでは、ネームスペースをアカウントのアドレスにリンクする方法と、不要になった際にリンクを解除する方法を説明します。

前提条件⚓︎

開始する前に、以下を確認してください。

さらに、トランザクションがどのようにアナウンスされ承認されるかを理解するために、転送トランザクション のチュートリアルを復習しておいてください。

ネームスペースの所有権が必要

ネームスペースをアドレスにリンクできるのは、そのネームスペースを所有しているアカウントのみです。 ターゲットとなるアドレスによる連署や承認は必要ありません。

完全なコード⚓︎

このチュートリアルの完全なコード一覧を以下に示します。 詳細な手順ごとの説明は次のセクションで行います。

import json
import os
import time
import urllib.request

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

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} (0x{namespace_id:016X})')

# Target address to link the namespace to
target_address = Address(
    os.getenv('TARGET_ADDRESS',
    'TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI'))
print(f'Target address: {target_address}')

try:
    # 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.create_transaction_from_descriptor(
        {
            'type': 'address_alias_transaction_v1',
            'namespace_id': namespace_id,
            'address': target_address,
            'alias_action': 'link'
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)

    # 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:016X}'
    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.create_transaction_from_descriptor(
        {
            'type': 'transfer_transaction_v1',
            'recipient_address': recipient_address,
            'mosaics': [{
                'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
                'amount': 1_000_000  # 1 XYM
            }]
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)
    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,
    SymbolFacade,
    descriptors,
    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); });
        const response = await fetch(
            `${NODE_URL}/transactionStatus/${transactionHash}`);
        if (!response.ok) {
            if (404 === response.status) {
                console.log('  Transaction status: unknown');
                continue;
            }
            throw new Error(`HTTP ${response.status}`);
        }
        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}`);
    }
    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];
const namespaceIdHex = namespaceId.toString(16)
    .toUpperCase().padStart(16, '0');
console.log(
    'Namespace ID:',
    `${namespaceId} (0x${namespaceIdHex})`);

// 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 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.createTransactionFromTypedDescriptor(
        new descriptors.AddressAliasTransactionV1Descriptor(
            new models.NamespaceId(namespaceId),
            targetAddress,
            models.AliasAction.LINK),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);

    // 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/${namespaceIdHex}`;
    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.createTransactionFromTypedDescriptor(
        new descriptors.TransferTransactionV1Descriptor(
            recipientAddress,
            [
                new descriptors.UnresolvedMosaicDescriptor(
                    generateMosaicAliasId('symbol.xym'),
                    new models.Amount(1_000_000n)) // 1 XYM
            ],
            undefined),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);
    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

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

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 java.util.List;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.symbol.sdk.CryptoTypes;
import org.symbol.sdk.facade.SymbolFacade;
import org.symbol.sdk.symbol.Address;
import org.symbol.sdk.symbol.IdGenerator;
import org.symbol.sdk.symbol.KeyPair;
import org.symbol.sdk.symbol.SymbolTransactionFactory;
import org.symbol.sdk.symbol.descriptors.*;
import org.symbol.sdk.symbol.models.*;

public final class LinkNamespaceToAddress {
    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", "https://reference.symboltest.net:3001");

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

    private void announceTransaction(
        final String payload,
        final String label
    ) throws IOException, InterruptedException {
        System.out.printf("Announcing %s to /transactions%n", label);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + "/transactions"))
            .header("Content-Type", "application/json")
            .PUT(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        System.out.printf("  Response: %s%n", response.body());
    }

    private void waitForConfirmation(
        final String transactionHash,
        final String label
    ) throws IOException, InterruptedException {
        System.out.printf("Waiting for %s confirmation...%n", label);
        for (int attempt = 0; 60 > attempt; ++attempt) {
            Thread.sleep(1000);
            final String statusPath =
                "/transactionStatus/" + transactionHash;
            final HttpRequest statusRequest = HttpRequest.newBuilder(
                URI.create(nodeUrl + statusPath)).GET().build();
            final HttpResponse<String> statusResponse = HTTP_CLIENT
                .send(statusRequest, BodyHandlers.ofString());
            if (404 == statusResponse.statusCode()) {
                System.out.println("  Transaction status: unknown");
                continue;
            }
            if (2 != statusResponse.statusCode() / 100)
                throw new IOException(
                    "HTTP " + statusResponse.statusCode());

            final JsonNode status =
                JSON_MAPPER.readTree(statusResponse.body());
            final String group = status.get("group").asText();
            System.out.printf("  Transaction status: %s%n", group);
            if ("confirmed".equals(group)) {
                System.out.printf("%s confirmed in %d seconds%n",
                    label, attempt);
                return;
            }
            if ("failed".equals(group))
                throw new IOException(String.format("%s failed: %s",
                    label, status.get("code").asText()));
        }
        throw new IOException(String.format(
            "%s not confirmed after 60 seconds", label));
    }

    public static void main(final String[] args) {
        try {
            new LinkNamespaceToAddress().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 signerPrivateKey = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(signerPrivateKey));

        final Address signerAddress = facade.network.publicKeyToAddress(
            signerKeyPair.getPublicKey());
        System.out.printf("Signer address: %s%n", signerAddress);


        final String namespaceName = System.getenv().getOrDefault(
            "NAMESPACE_NAME", "my_namespace");
        System.out.printf("Namespace name: %s%n", namespaceName);

        final List<Long> nsPath = IdGenerator.generateNamespacePath(
            namespaceName);
        final long namespaceId = nsPath.get(nsPath.size() - 1);
        System.out.printf("Namespace ID: %s (0x%016X)%n",
            Long.toUnsignedString(namespaceId), namespaceId);

        // Target address to link the namespace to
        final Address targetAddress = new Address(
            System.getenv().getOrDefault(
                "TARGET_ADDRESS",
                "TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI"));
        System.out.printf("Target address: %s%n", targetAddress);

        // Fetch recommended fees
        final String feePath = "/network/fees/transaction";
        System.out.printf("Fetching recommended fees from %s%n", feePath);
        final JsonNode feeJson = getJson(feePath);
        final long feeMultiplier = Math.max(
            feeJson.get("medianFeeMultiplier").asLong(),
            feeJson.get("minFeeMultiplier").asLong());
        System.out.printf("  Fee multiplier: %d%n", feeMultiplier);

        // Build the alias transaction
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new AddressAliasTransactionV1Descriptor(
                    new NamespaceId(namespaceId),
                    targetAddress,
                    AliasAction.LINK),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

        // Sign transaction and generate final payload
        final String jsonPayload = SymbolTransactionFactory
            .attachSignature(transaction,
                facade.signTransaction(signerKeyPair, transaction));
        System.out.println("Address alias 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 and confirm transaction
        announceTransaction(jsonPayload, "address alias transaction");
        waitForConfirmation(transactionHash, "address alias transaction");

        // Retrieve the namespace to verify the alias
        final String namespacePath = "/namespaces/%016X".formatted(
            namespaceId);
        System.out.printf("Fetching namespace information from %s%n",
            namespacePath);
        final JsonNode namespaceInfo = getJson(namespacePath)
            .get("namespace");
        System.out.println("Alias information:");
        final int aliasType = namespaceInfo.get("alias").get("type")
            .asInt();
        System.out.printf("  Alias type: %d%n", aliasType);
        if (2 == aliasType) { // ADDRESS type
            final Address aliasedAddress = Address
                .fromDecodedAddressHexString(
                    namespaceInfo.get("alias").get("address").asText());
            System.out.printf("  Linked address: %s%n",
                aliasedAddress);
        }


        // Send a transfer using the alias instead of a raw address
        System.out.printf("Using alias in transfer: %s%n",
            namespaceName);

        // Encode the namespace ID as a recipient address
        final long recipientId = IdGenerator.generateNamespacePath(
            namespaceName).get(nsPath.size() - 1);
        final Address recipientAddress = Address.fromNamespaceId(
            new NamespaceId(recipientId), facade.network.identifier);
        System.out.printf("Recipient address (alias): %s%n",
            recipientAddress);

        final Transaction testTransaction =
            facade.createTransactionFromTypedDescriptor(
                new TransferTransactionV1Descriptor(
                    recipientAddress,
                    List.of(new UnresolvedMosaicDescriptor(
                        new UnresolvedMosaicId(
                            IdGenerator.generateMosaicAliasId(
                                "symbol.xym")),
                        new Amount(1_000_000))), // 1 XYM
                    null),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);
        final String testJsonPayload = SymbolTransactionFactory
            .attachSignature(testTransaction,
                facade.signTransaction(signerKeyPair,
                    testTransaction));
        System.out.println("Test transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(testTransaction.toJson()));
        final String testTransactionHash =
            facade.hashTransaction(testTransaction).toString();
        System.out.printf("Transaction hash: %s%n", testTransactionHash);
        announceTransaction(testJsonPayload, "test transaction");
        waitForConfirmation(testTransactionHash, "test transaction");

    }

    private JsonNode getJson(final String path)
        throws IOException, InterruptedException {
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        return JSON_MAPPER.readTree(response.body());
    }
}

Download source

コード解説⚓︎

アカウントの設定⚓︎

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());
        final String signerPrivateKey = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(signerPrivateKey));

        final Address signerAddress = facade.network.publicKeyToAddress(
            signerKeyPair.getPublicKey());
        System.out.printf("Signer address: %s%n", signerAddress);

このスニペットは、署名者の秘密鍵を SIGNER_PRIVATE_KEY 環境変数から読み取ります。設定されていない場合はデフォルトのテストキーが使用されます。 署名者のアドレスは公開鍵から派生します。 このアカウントは、リンク対象のネームスペースを所有している必要があります。

ネームスペースとターゲットアドレスの定義⚓︎

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} (0x{namespace_id:016X})')

# 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];
const namespaceIdHex = namespaceId.toString(16)
    .toUpperCase().padStart(16, '0');
console.log(
    'Namespace ID:',
    `${namespaceId} (0x${namespaceIdHex})`);

// Target address to link the namespace to
const targetAddress = new SymbolFacade.Address(
    process.env.TARGET_ADDRESS ||
    'TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI');
console.log('Target address:', targetAddress.toString());
        final String namespaceName = System.getenv().getOrDefault(
            "NAMESPACE_NAME", "my_namespace");
        System.out.printf("Namespace name: %s%n", namespaceName);

        final List<Long> nsPath = IdGenerator.generateNamespacePath(
            namespaceName);
        final long namespaceId = nsPath.get(nsPath.size() - 1);
        System.out.printf("Namespace ID: %s (0x%016X)%n",
            Long.toUnsignedString(namespaceId), namespaceId);

        // Target address to link the namespace to
        final Address targetAddress = new Address(
            System.getenv().getOrDefault(
                "TARGET_ADDRESS",
                "TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI"));
        System.out.printf("Target address: %s%n", targetAddress);

コードでは以下を定義しています。

  • ネームスペース名: リンクするネームスペース。 NAMESPACE_NAME 環境変数から読み込まれ、設定されていない場合は my_namespace がデフォルトとなります。 この名前は、自身のアカウントがすでに所有しているネームスペースと一致している必要があります。
  • ネームスペース ID: ID は、 を使用してネームスペース名から生成されます。 これは階層内の各レベルの ID 配列を返します。 最終的なネームスペース ID を取得するために、最後の要素が選択されます。 最後の要素を取得する方法は、ルートネームスペースとサブネームスペースの両方で機能します。

    foo のようなルートネームスペースの場合、配列には要素が1つ含まれます。 symbol.xym のようなサブネームスペースの場合、要素は2つ含まれ、最後の要素が symbol 配下の xym の ID となります。

    サブネームスペース ID は一意

    サブネームスペース ID は階層的に派生するため、末尾の名前が同じでも親が異なる2つのサブネームスペースは、異なる ID を生成します。 例えば、 foo.xymbar.xym のパスの最後の要素は異なります。

  • ターゲットアドレス: ネームスペースが指し示す先のアドレス。 TARGET_ADDRESS 環境変数から読み込まれます。 設定されていない場合は、デフォルトのテストアドレスが使用されます。

    # 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 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);
        // Fetch recommended fees
        final String feePath = "/network/fees/transaction";
        System.out.printf("Fetching recommended fees from %s%n", feePath);
        final JsonNode feeJson = getJson(feePath);
        final long feeMultiplier = Math.max(
            feeJson.get("medianFeeMultiplier").asLong(),
            feeJson.get("minFeeMultiplier").asLong());
        System.out.printf("  Fee multiplier: %d%n", feeMultiplier);

転送トランザクション チュートリアルで説明されているプロセスに従い、推奨手数料を /network/fees/transaction GET から取得します。

トランザクションの構築⚓︎

    # Build the alias transaction
    transaction = facade.create_transaction_from_descriptor(
        {
            'type': 'address_alias_transaction_v1',
            'namespace_id': namespace_id,
            'address': target_address,
            'alias_action': 'link'
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)
    // Build the alias transaction
    const transaction = facade.createTransactionFromTypedDescriptor(
        new descriptors.AddressAliasTransactionV1Descriptor(
            new models.NamespaceId(namespaceId),
            targetAddress,
            models.AliasAction.LINK),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);
        // Build the alias transaction
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new AddressAliasTransactionV1Descriptor(
                    new NamespaceId(namespaceId),
                    targetAddress,
                    AliasAction.LINK),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

アドレスエイリアストランザクションは、ネームスペースをアドレスにリンクします。 に渡した署名者は、そのネームスペースを所有している必要があります。 トランザクションの記述子には以下を指定します。

  • Type: アドレスエイリアストランザクションにはタイプ AddressAliasTransactionV1 を使用します。

  • ネームスペース ID: リンクされるネームスペースの識別子。

  • アドレス: ネームスペースにリンクするターゲットアドレス。

  • エイリアスアクション: link という値はエイリアスを作成します。後にエイリアスを削除するには、代わりに unlink を使用します。

エイリアスのリンク解除

ネームスペースのアドレスへのリンクを解除するには、同じネームスペース ID とアドレスを指定し、 alias_action フィールドを unlink に設定した別の AddressAliasTransactionV1 トランザクションをアナウンスします。

リンク解除プロセスによってネームスペース自体が削除されるわけではなく、ネームスペースとアドレスの関連付けのみが削除されます。 リンク解除後、そのネームスペースは別のアドレスや モザイク にリンクさせることができます。

トランザクションの送信⚓︎

    # 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');
        // Sign transaction and generate final payload
        final String jsonPayload = SymbolTransactionFactory
            .attachSignature(transaction,
                facade.signTransaction(signerKeyPair, transaction));
        System.out.println("Address alias 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 and confirm transaction
        announceTransaction(jsonPayload, "address alias transaction");
        waitForConfirmation(transactionHash, "address alias transaction");

トランザクションは、転送トランザクション チュートリアルと同じプロセスに従って署名され、アナウンスされます。

エイリアスの検証⚓︎

    # Retrieve the namespace to verify the alias
    namespace_path = f'/namespaces/{namespace_id:016X}'
    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/${namespaceIdHex}`;
    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());
    }
        // Retrieve the namespace to verify the alias
        final String namespacePath = "/namespaces/%016X".formatted(
            namespaceId);
        System.out.printf("Fetching namespace information from %s%n",
            namespacePath);
        final JsonNode namespaceInfo = getJson(namespacePath)
            .get("namespace");
        System.out.println("Alias information:");
        final int aliasType = namespaceInfo.get("alias").get("type")
            .asInt();
        System.out.printf("  Alias type: %d%n", aliasType);
        if (2 == aliasType) { // ADDRESS type
            final Address aliasedAddress = Address
                .fromDecodedAddressHexString(
                    namespaceInfo.get("alias").get("address").asText());
            System.out.printf("  Linked address: %s%n",
                aliasedAddress);
        }

エイリアスが作成されたことを確認するために、コードは /namespaces/{namespaceId} GET エンドポイントを使用してネットワークからネームスペース情報を取得します。

レスポンスにはエイリアスタイプ( 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.create_transaction_from_descriptor(
        {
            'type': 'transfer_transaction_v1',
            'recipient_address': recipient_address,
            'mosaics': [{
                'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
                'amount': 1_000_000  # 1 XYM
            }]
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)
    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.createTransactionFromTypedDescriptor(
        new descriptors.TransferTransactionV1Descriptor(
            recipientAddress,
            [
                new descriptors.UnresolvedMosaicDescriptor(
                    generateMosaicAliasId('symbol.xym'),
                    new models.Amount(1_000_000n)) // 1 XYM
            ],
            undefined),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);
    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');
        // Send a transfer using the alias instead of a raw address
        System.out.printf("Using alias in transfer: %s%n",
            namespaceName);

        // Encode the namespace ID as a recipient address
        final long recipientId = IdGenerator.generateNamespacePath(
            namespaceName).get(nsPath.size() - 1);
        final Address recipientAddress = Address.fromNamespaceId(
            new NamespaceId(recipientId), facade.network.identifier);
        System.out.printf("Recipient address (alias): %s%n",
            recipientAddress);

        final Transaction testTransaction =
            facade.createTransactionFromTypedDescriptor(
                new TransferTransactionV1Descriptor(
                    recipientAddress,
                    List.of(new UnresolvedMosaicDescriptor(
                        new UnresolvedMosaicId(
                            IdGenerator.generateMosaicAliasId(
                                "symbol.xym")),
                        new Amount(1_000_000))), // 1 XYM
                    null),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);
        final String testJsonPayload = SymbolTransactionFactory
            .attachSignature(testTransaction,
                facade.signTransaction(signerKeyPair,
                    testTransaction));
        System.out.println("Test transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(testTransaction.toJson()));
        final String testTransactionHash =
            facade.hashTransaction(testTransaction).toString();
        System.out.printf("Transaction hash: %s%n", testTransactionHash);
        announceTransaction(testJsonPayload, "test transaction");
        waitForConfirmation(testTransactionHash, "test transaction");

ネームスペースがアドレスにリンクされると、トランザクションにおいてアドレスの代わりにネームスペースを使用できるようになります。 コードは、完全な16進数アドレスの代わりにエイリアスを受信者アドレスとして使用する 転送トランザクション の作成を示しています。

簡略化のため、この例ではトランザクションを作成しますが、アナウンスや承認の待機は行いません。

ネームスペースを受信者アドレスとして使用するには、 を使用してネームスペースIDを24バイトのアドレスに変換します。 前のセクションで説明したように、ネームスペースパスの最後のコンポーネントがネームスペースIDとして使用されます。

転送トランザクションのアナウンス方法の詳細については、転送トランザクション チュートリアルを参照してください。

アドレス解決レシート

ネットワークがネームスペースエイリアスを受信者アドレスとして使用するトランザクションを処理すると、アドレス解決レシート(Address Resolution Receipt) が生成されます。 このレシートには、トランザクションが承認された時点でエイリアスが実際に指し示していたアドレスが記録されます。

これは過去の監査可能性にとって重要です。エイリアスはいつでも変更または削除できるため、たとえエイリアスがその後更新されていたとしても、解決されたアドレスを常に検証できることがレシートによって保証されます。

解決レシートは /statements/resolutions/address GET エンドポイントを使用して照会できます。 レシートの詳細については、テキストブックの 解決ステートメント セクションを参照してください。

出力⚓︎

以下に示す出力は、プログラムの典型的な実行結果に対応しています。

Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Namespace name: ns_1778584141043
Namespace ID: 16980998145596513971 (0xEBA89FD81FD8DEB3)
Target address: TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI
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

出力の主なポイント:

  • ネームスペースとターゲット (3、5行目): ネームスペースとリンク先のアドレスが表示されます。

  • トランザクションハッシュ (21行目): トランザクションハッシュを使用して、 Symbol Testnet Explorer でトランザクションを検索できます。

  • エイリアスの検証 (30-31行目): ネームスペース情報により、エイリアスタイプが 2 (アドレス) であることが確認され、リンクされたアドレスが表示されています。

  • エイリアスの使用 (33行目): エイリアスを受信者として使用して転送トランザクションが作成されており、完全なアドレスの代わりに使用できることが実証されています。

    異なる受信者アドレス

    受信者アドレスがターゲットアドレスと異なるのは、それがターゲットアドレス自体ではなく エンコードされたネームスペース ID であるためです。 ネットワークはトランザクションを処理する際に、エイリアスをリンクされたアドレスに解決します。

結論⚓︎

このチュートリアルでは、以下の方法を説明しました。

ステップ 関連ドキュメント
ネームスペース ID を生成する
アドレスエイリアストランザクションを構築する , AddressAliasTransactionV1
エイリアスを検証する /namespaces/{namespaceId} GET
エイリアスを使用する
アドレス解決レシートを照会する /statements/resolutions/address GET