コンテンツにスキップ
中級

サブネームスペースの登録⚓︎

サブネームスペース (「子」ネームスペースとも呼ばれます)は、 ネームスペース の階層構造を拡張します。

このチュートリアルでは、既存の ルートネームスペース の配下にサブネームスペースを登録する方法を説明します。

登録後は、 次のステップ で説明するように、ネームスペースをモザイクやアカウントにリンクするための追加ステップが必要です。

前提条件⚓︎

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

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

完全なコード⚓︎

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

import json
import os
import time
import urllib.request

from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.SymbolFacade import SymbolFacade
from symbolchain.symbol.Network import NetworkTimestamp
from symbolchain.symbol.IdGenerator import generate_namespace_id
from symbolchain.sc import Amount
from symbolchain.symbol.Network import Address

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

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

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

try:
    # Fetch current network time
    time_path = '/node/time'
    print(f'Fetching current network time from {time_path}')
    with urllib.request.urlopen(f'{NODE_URL}{time_path}') as response:
        response_json = json.loads(response.read().decode())
        receive_timestamp = (
            response_json['communicationTimestamps']['receiveTimestamp'])
        timestamp = NetworkTimestamp(int(receive_timestamp))
        print(f'  Network time: {timestamp.timestamp} ms since nemesis')

    # Fetch recommended fees
    fee_path = '/network/fees/transaction'
    print(f'Fetching recommended fees from {fee_path}')
    with urllib.request.urlopen(f'{NODE_URL}{fee_path}') as response:
        response_json = json.loads(response.read().decode())
        median_mult = response_json['medianFeeMultiplier']
        minimum_mult = response_json['minFeeMultiplier']
        fee_mult = max(median_mult, minimum_mult)
        print(f'  Fee multiplier: {fee_mult}')

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

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

    transaction = facade.transaction_factory.create({
        'type': 'namespace_registration_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'registration_type': 'child',
        'parent_id': parent_id,
        'name': child_namespace_name
    })
    transaction.fee = Amount(fee_mult * transaction.size)

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

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

    # Announce transaction
    print('Announcing namespace registration to /transactions')
    request = urllib.request.Request(
        f'{NODE_URL}/transactions',
        data=json_payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='PUT'
    )
    with urllib.request.urlopen(request) as response:
        print(f'  Response: {response.read().decode()}')

    # Wait for confirmation
    print('Waiting for namespace registration confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            status_url = (
                f'{NODE_URL}/transactionStatus/{transaction_hash}')
            with urllib.request.urlopen(status_url) as response:
                status = json.loads(response.read().decode())
                print(f'  Transaction status: {status["group"]}')
            if status['group'] == 'confirmed':
                print('Namespace registration confirmed in',
                    attempt, 'seconds')
                break
            if status['group'] == 'failed':
                raise Exception('Namespace registration failed:',
                    status['code'])
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')

    # Retrieve the namespace
    namespace_id = generate_namespace_id(
        child_namespace_name, parent_id)
    print(f'Child namespace ID: {namespace_id} ({hex(namespace_id)})')

    namespace_path = f'/namespaces/{namespace_id:x}'
    print(f'Fetching namespace information from {namespace_path}')
    with urllib.request.urlopen(
            f'{NODE_URL}{namespace_path}') as response:
        response_json = json.loads(response.read().decode())
        namespace_info = response_json['namespace']
        print('Namespace information:')
        reg_type = namespace_info["registrationType"]
        print(f'  Registration type: {reg_type}')
        owner_address = Address.from_decoded_address_hex_string(
            namespace_info["ownerAddress"])
        print(f'  Owner address: {owner_address}')
        print(f'  Parent ID: {namespace_info["parentId"]}')
        print(f'  Depth: {namespace_info["depth"]}')
        print(f'  Level 0: {namespace_info["level0"]}')
        if int(namespace_info['depth']) >= 1:
            print(f'  Level 1: {namespace_info["level1"]}')
        if int(namespace_info['depth']) >= 2 and \
                'level2' in namespace_info:
            print(f'  Level 2: {namespace_info["level2"]}')
        print(f'  Start height: {namespace_info["startHeight"]}')
        print(f'  End height: {namespace_info["endHeight"]}')

except Exception as e:
    print(e)

Download source

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

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

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

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

try {
    // Fetch current network time
    const timePath = '/node/time';
    console.log('Fetching current network time from', timePath);
    const timeResponse = await fetch(`${NODE_URL}${timePath}`);
    const timeJSON = await timeResponse.json();
    const receiveTimestamp =
        timeJSON.communicationTimestamps.receiveTimestamp;
    const timestamp = new NetworkTimestamp(receiveTimestamp);
    console.log('  Network time:', timestamp.timestamp,
        'ms since nemesis');

    // Fetch recommended fees
    const feePath = '/network/fees/transaction';
    console.log('Fetching recommended fees from', feePath);
    const feeResponse = await fetch(`${NODE_URL}${feePath}`);
    const feeJSON = await feeResponse.json();
    const medianMult = feeJSON.medianFeeMultiplier;
    const minimumMult = feeJSON.minFeeMultiplier;
    const feeMult = Math.max(medianMult, minimumMult);
    console.log('  Fee multiplier:', feeMult);

    // Build the transaction
    const rootNamespaceName = 'ns_root';
    const childNamespaceName = `sub_${Date.now()}`;
    const fullNamespaceName =
        `${rootNamespaceName}.${childNamespaceName}`;
    console.log('Creating child namespace:', fullNamespaceName);

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

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

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

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

    // Announce transaction
    console.log('Announcing namespace registration to /transactions');
    const announceResponse = await fetch(`${NODE_URL}/transactions`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    console.log('  Response:', await announceResponse.text());

    // Wait for confirmation
    console.log('Waiting for namespace registration confirmation...');
    for (let attempt = 0; attempt < 60; attempt++) {
        await new Promise(resolve => setTimeout(resolve, 1000));

        try {
            const statusUrl =
                `${NODE_URL}/transactionStatus/${transactionHash}`;
            const statusResponse = await fetch(statusUrl);

            if (!statusResponse.ok) {
                console.log('  Transaction status: unknown');
                continue;
            }

            const status = await statusResponse.json();
            console.log('  Transaction status:', status.group);

            if (status.group === 'confirmed') {
                console.log('Namespace registration confirmed in',
                    attempt, 'seconds');
                break;
            }

            if (status.group === 'failed') {
                throw new Error(
                    `Namespace registration failed: ${status.code}`);
            }
        } catch (error) {
            if (error.message.includes('failed:')) {
                throw error;
            }
            console.log('  Transaction status: unknown');
        }
    }

    // Retrieve the namespace
    const namespaceId = generateNamespaceId(
        childNamespaceName, parentId);
    console.log(
        'Child namespace ID:',
        `${namespaceId} (0x${namespaceId.toString(16)})`);

    const namespacePath =
        `/namespaces/${namespaceId.toString(16)}`;
    console.log(
        'Fetching namespace information from', namespacePath);
    const namespaceResponse =
        await fetch(`${NODE_URL}${namespacePath}`);
    const namespaceJSON = await namespaceResponse.json();
    const namespaceInfo = namespaceJSON.namespace;
    console.log('Namespace information:');
    console.log(
        '  Registration type:', namespaceInfo.registrationType);
    const ownerAddress = Address.fromDecodedAddressHexString(
        namespaceInfo.ownerAddress);
    console.log('  Owner address:', ownerAddress.toString());
    console.log('  Parent ID:', namespaceInfo.parentId);
    console.log('  Depth:', namespaceInfo.depth);
    console.log('  Level 0:', namespaceInfo.level0);
    if (namespaceInfo.depth >= 1) {
        console.log('  Level 1:', namespaceInfo.level1);
    }
    if (namespaceInfo.depth >= 2 &&
            namespaceInfo.level2) {
        console.log('  Level 2:', namespaceInfo.level2);
    }
    console.log('  Start height:', namespaceInfo.startHeight);
    console.log('  End height:', namespaceInfo.endHeight);
} catch (e) {
    console.error(e.message);
}

Download source

コード解説⚓︎

コードは ルートネームスペースの登録 チュートリアルと同じパターンに従います。 このセクションでは、主な相違点のみに焦点を当てます。

共通のステップ(アカウントの設定、ネットワーク時間と手数料の取得、およびアナウンス)の詳細な説明については、 ルートネームスペースの登録 を参照してください。

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

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

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

    transaction = facade.transaction_factory.create({
        'type': 'namespace_registration_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'registration_type': 'child',
        'parent_id': parent_id,
        'name': child_namespace_name
    })
    transaction.fee = Amount(fee_mult * transaction.size)
    // Build the transaction
    const rootNamespaceName = 'ns_root';
    const childNamespaceName = `sub_${Date.now()}`;
    const fullNamespaceName =
        `${rootNamespaceName}.${childNamespaceName}`;
    console.log('Creating child namespace:', fullNamespaceName);

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

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

サブネームスペースを登録する際の主な違いは、トランザクション記述子にあります:

  • 登録タイプ: child という値は、サブネームスペース(子ネームスペース)が作成されることを示します。 代わりに ルートネームスペースを登録 するには root を使用してください。

  • 親 ID (Parent ID): 有効期間を指定する代わりに、親ネームスペースの ID を提供します。 親は、ルートネームスペースまたは(第3レベルのネームスペースを作成する場合は)別の小ネームスペースである必要があります。

    親ネームスペース ID は、その名前から を使用して計算されます。

  • 名前: サブネームスペースの名前。 この名前はルートネームスペースの名前と同じルールに従います:英小文字、数字、ハイフン、アンダースコアを使用でき、数字または文字で始まり、最大64文字までです。

    これはフルパスではなく、サブネームスペース自体の名前であることに注意してください。 例えば、 company がルートである場合に company.product を作成するには、 name: 'product' および parent_id: generateNamespaceId('company') と設定します。

    チュートリアルを複数回実行してもサブネームスペース名が重複しないように、名前にタイムスタンプが付加されています。実用的なプログラムでは、ネームスペースに固定の名前を使用します。

サブネームスペースは、そのルートネームスペースの有効期限を自動的に継承します。 ルートが期限切れになると、すべてのサブネームスペースも同時に期限切れになります。

サブネームスペースのレンタル手数料

標準のトランザクション手数料に加えて、サブネームスペースの登録には レンタル手数料 が必要です。

トランザクション手数料とは異なり、レンタル手数料はトランザクションリクエストには 含まれません

サブネームスペースの場合、この手数料は期間に関係なく固定です。 登録トランザクションが承認されると、ネットワークによってレンタル手数料が自動的に差し引かれるため、トランザクション内で指定する必要はありません。

その後、トランザクションは ルートネームスペースの登録 と同じプロセスに従って署名、アナウンス、承認されます。

サブネームスペースの取得⚓︎

    # Retrieve the namespace
    namespace_id = generate_namespace_id(
        child_namespace_name, parent_id)
    print(f'Child namespace ID: {namespace_id} ({hex(namespace_id)})')

    namespace_path = f'/namespaces/{namespace_id:x}'
    print(f'Fetching namespace information from {namespace_path}')
    with urllib.request.urlopen(
            f'{NODE_URL}{namespace_path}') as response:
        response_json = json.loads(response.read().decode())
        namespace_info = response_json['namespace']
        print('Namespace information:')
        reg_type = namespace_info["registrationType"]
        print(f'  Registration type: {reg_type}')
        owner_address = Address.from_decoded_address_hex_string(
            namespace_info["ownerAddress"])
        print(f'  Owner address: {owner_address}')
        print(f'  Parent ID: {namespace_info["parentId"]}')
        print(f'  Depth: {namespace_info["depth"]}')
        print(f'  Level 0: {namespace_info["level0"]}')
        if int(namespace_info['depth']) >= 1:
            print(f'  Level 1: {namespace_info["level1"]}')
        if int(namespace_info['depth']) >= 2 and \
                'level2' in namespace_info:
            print(f'  Level 2: {namespace_info["level2"]}')
        print(f'  Start height: {namespace_info["startHeight"]}')
        print(f'  End height: {namespace_info["endHeight"]}')
    // Retrieve the namespace
    const namespaceId = generateNamespaceId(
        childNamespaceName, parentId);
    console.log(
        'Child namespace ID:',
        `${namespaceId} (0x${namespaceId.toString(16)})`);

    const namespacePath =
        `/namespaces/${namespaceId.toString(16)}`;
    console.log(
        'Fetching namespace information from', namespacePath);
    const namespaceResponse =
        await fetch(`${NODE_URL}${namespacePath}`);
    const namespaceJSON = await namespaceResponse.json();
    const namespaceInfo = namespaceJSON.namespace;
    console.log('Namespace information:');
    console.log(
        '  Registration type:', namespaceInfo.registrationType);
    const ownerAddress = Address.fromDecodedAddressHexString(
        namespaceInfo.ownerAddress);
    console.log('  Owner address:', ownerAddress.toString());
    console.log('  Parent ID:', namespaceInfo.parentId);
    console.log('  Depth:', namespaceInfo.depth);
    console.log('  Level 0:', namespaceInfo.level0);
    if (namespaceInfo.depth >= 1) {
        console.log('  Level 1:', namespaceInfo.level1);
    }
    if (namespaceInfo.depth >= 2 &&
            namespaceInfo.level2) {
        console.log('  Level 2:', namespaceInfo.level2);
    }
    console.log('  Start height:', namespaceInfo.startHeight);

サブネームスペースが登録されたことを確認するために、コードは /namespaces/{namespaceId} GET エンドポイントを使用してネットワークから情報を取得し、そのプロパティを表示します。

サブネームスペース ID は を使用して計算されます。 この関数はサブネームスペース名と親 ID の両方を受け取り、決定論的なハッシュアルゴリズムを適用して子ネームスペース ID を生成します。

レスポンスが成功すれば、サブネームスペースが登録され、ネットワーク上でアクティブであることが確認されます。

サブネームスペースは登録されましたが、まだリンクされていません

サブネームスペースは、 モザイクアカウント のエイリアスとして機能するときに有用なものとなります。 次のステップ のガイドを使用して、サブネームスペースを識別子にリンクしてください。

出力⚓︎

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

Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Fetching current network time from /node/time
  Network time: 99282637399 ms since nemesis
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100
Creating child namespace: ns_root.sub_1766533103
Parent namespace ID: 0xb0786316d5c1d9dd
Built transaction:
{
  "signature": "9E91F08855D1911BE7208BB3441ACF8AE18EBEB529867D6DAC8552A837E17AA2F05BEBAE510950D5194E0837C4A6E780138CCF29A065F55C05A76CEF6AFA610E",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 16718,
  "fee": "16000",
  "deadline": "99289837399",
  "parent_id": "12716022497607277021",
  "id": "11305240440531381773",
  "registration_type": 1,
  "name": "7375625f31373636353333313033"
}
Transaction hash: 43962A040342198E485F5E91364CA0270F737C56E58909FE03FE3F02AD97CA56
Announcing namespace registration to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for namespace registration confirmation...
  Transaction status: unconfirmed
  Transaction status: confirmed
Namespace registration confirmed in 19 seconds
Child namespace ID: 11305240440531381773 (0x9ce448486e40ee0d)
Fetching namespace information from /namespaces/9ce448486e40ee0d
Namespace information:
  Registration type: 1
  Owner address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
  Parent ID: B0786316D5C1D9DD
  Depth: 2
  Level 0: B0786316D5C1D9DD
  Level 1: 9CE448486E40EE0D
  Start height: 2956766
  End height: 3046046

出力の主なポイント:

  • ネームスペースのフルパス (7行目): サブネームスペース ns_root.sub_1766533103 は、完全な階層名を示しています。

  • 親ネームスペース ID (8, 33行目): 親 ID 0xb0786316d5c1d9dd は、このサブネームスペースをそのルートにリンクします。

  • 手数料 (16行目): 0.016 XYM のトランザクション手数料は、トランザクションサイズに手数料倍率を乗じて計算されます。 レンタル手数料 は、トランザクションが承認された際にネットワークによって別途差し引かれます。

  • ID と名前 (19, 21行目): id フィールドにはサブネームスペース ID が10進数で表示され、 name には16進数としてエンコードされた子要素の部分のみが含まれます(例えば、 7375625f...sub_1... にデコードされます)。

  • 子ネームスペース ID (30行目): 19行目の id フィールドと一致するように、10進数と16進数の両方の表現が表示されます。

  • 登録タイプ (34行目): 値 1 はサブネームスペースであることを示します(ルートネームスペースの場合は 0 )。

  • 所有者アドレス (35行目): ネームスペースを登録したアカウントであり、ルートネームスペースの所有者と同じである必要があります。

  • 深さ (Depth) (36行目): 深さ 2 は、ネームスペース階層に2つのレベルがあることを示します。レベル 0 はルートネームスペース、レベル 1 はこのサブネームスペースです。

  • レベル (Levels) (37-38行目): 完全な階層パスです。 level0 にはルートネームスペース ID が、 level1 には子 ID が含まれます。深さが 3 であれば、 level2 に孫 ID が含まれます。

  • 開始高と終了高 (39-40行目): これらの値はルートネームスペースから継承され、個別に設定されることはありません。

出力されたトランザクション ハッシュ を使用して、 Symbol Testnet Explorer でトランザクションを検索することもできます。

結論⚓︎

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

ステップ 関連ドキュメント
ネームスペース ID を生成する
サブネームスペース登録トランザクションを構築する
サブネームスペースを取得する /namespaces/{namespaceId} GET

次のステップ⚓︎

サブネームスペースを作成したので、以下のことができます: