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

転送トランザクションの作成⚓︎

転送トランザクション は、Symbol における最も基本的なトランザクションのタイプです。 これを使用すると、ある アカウント から別のアカウントへ XYM やその他の任意の モザイク を送信でき、オプションでメッセージを含めることも可能です。

このチュートリアルでは、転送トランザクションを作成、署名、およびアナウンスし、その後トランザクションが承認されるまでステータスをポーリングする方法を説明します。 現在の時間や手数料など、必要なトランザクションパラメータは、最新の値を使用するためにネットワークから取得されます。

前提条件⚓︎

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

完全なコード⚓︎

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

import datetime
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_mosaic_alias_id
from symbolchain.sc import Amount

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')

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())
        timestamp = NetworkTimestamp(int(
            response_json['communicationTimestamps']['receiveTimestamp']))
        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
    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':
            facade.network.public_key_to_address(
                signer_key_pair.public_key),
        'mosaics': [{
            'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
            'amount': 1_000_000 # 1 XYM
        }]
    })
    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))

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

    # Wait for confirmation
    status_path = (
        f'/transactionStatus/{facade.hash_transaction(transaction)}')
    print(f'Waiting for confirmation from {status_path}')
    for attempt in range(60):
        time.sleep(1)
        try:
            with urllib.request.urlopen(
                f'{NODE_URL}{status_path}'
            ) as response:
                status = json.loads(response.read().decode())
                print(f'  Transaction status: {status['group']}')
                if status['group'] == 'confirmed':
                    print(f'Transaction confirmed in {attempt} seconds')
                    break
                if status['group'] == 'failed':
                    print(f'Transaction failed: {status['code']}')
                    break
        except urllib.error.HTTPError as e:
            print(f'  Transaction status: unknown | Cause: ({e.msg})')
    else:
        print('Confirmation took too long.')

except urllib.error.URLError as e:
    print(e.reason)

Download source

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

const 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');

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 timestamp = new NetworkTimestamp(
        timeJSON.communicationTimestamps.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 transaction = facade.transactionFactory.create({
        type: 'transfer_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        recipientAddress: facade.network.publicKeyToAddress(
            signerKeyPair.publicKey).toString(),
        mosaics: [{
            mosaicId: generateMosaicAliasId('symbol.xym'),
            amount: 1_000_000n // 1 XYM
        }]
    });
    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 });

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

    // Wait for confirmation
    const transactionHash =
        facade.hashTransaction(transaction).toString();
    const statusPath = `/transactionStatus/${transactionHash}`;
    console.log('Waiting for confirmation from', statusPath);

    let attempt = 0;

    function pollStatus() {
        attempt++;

        if (attempt > 60) {
            console.warn('Confirmation took too long.');
            return;
        }

        return fetch(`${NODE_URL}${statusPath}`)
            .then(response => {
                if (!response.ok) {
                    console.log('  Transaction status: unknown | Cause:',
                        response.statusText);
                    // HTTP error: schedule a retry
                    return new Promise(resolve =>
                        setTimeout(resolve, 1000)).then(pollStatus);
                }
                return response.json();
            })
            .then(status => {
                // Skip if previous step scheduled a retry
                if (!status) return;

                console.log('  Transaction status:', status.group);

                if (status.group === 'confirmed') {
                    console.log('Transaction confirmed in', attempt,
                        'seconds');
                } else if (status.group === 'failed') {
                    console.log('Transaction failed:', status.code);
                } else {
                    // Transaction unconfirmed: schedule a retry
                    return new Promise(resolve =>
                        setTimeout(resolve, 1000)).then(pollStatus);
                }
            });
    }
    pollStatus();
} catch (e) {
    console.error(e.message, '| Cause:', e.cause?.code ?? 'unknown');
}

Download source

コード全体は、単純なエラー処理を提供するために単一の try ブロックでラップされていますが、実際のアプリケーションではより詳細な制御が必要になるでしょう。

コード解説⚓︎

アカウントのセットアップ⚓︎

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

署名者アカウントは、 SIGNER_PRIVATE_KEY 環境変数から読み込まれます。 指定されていない場合は、デフォルトでテストキーが使用されます。

ネットワーク時間の取得⚓︎

    # 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())
        timestamp = NetworkTimestamp(int(
            response_json['communicationTimestamps']['receiveTimestamp']))
        print(f'  Network time: {timestamp.timestamp} ms since nemesis')
    // 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 timestamp = new NetworkTimestamp(
        timeJSON.communicationTimestamps.receiveTimestamp);
    console.log('  Network time:', timestamp.timestamp,
        'ms since nemesis');

Symbol の トランザクション には、有効期限(deadline)を含める必要があります。これは、ネットワークがトランザクションを破棄する前に承認を試みる期間を定義します。 有効期限は絶対的なネットワーク時間で表されるため、最初のステップは ノード から現在のネットワーク時間を取得することです。

ネットワーク時間とは何ですか?

Symbol では、時間を最初のブロックである ネメシスブロック(他のブロックチェーンではジェネシスブロックとして知られています)の作成から経過した秒数として定義しています。

すべてのトランザクションの有効期限とタイムスタンプは、この起点を基準に計算されます。

UTC などのより人間が理解しやすい形式でタイムスタンプを表示したい場合は、ネットワークプロパティから取得できるネメシスブロックのタイムスタンプを加算する必要があります。

properties_path = '/network/properties'
print(f'{properties_path} からネットワークプロパティを取得しています')
with urllib.request.urlopen(f'{NODE_URL}{properties_path}') as response:
    response_json = json.loads(response.read().decode())
    epoch_adjustment = datetime.datetime.fromtimestamp(
        int(response_json['network']['epochAdjustment'].rstrip('s')))
    print(f'  ネメシスタイムスタンプ: {epoch_adjustment}')
const propertiesPath = '/network/properties';
console.log(propertiesPath, 'からネットワークプロパティを取得しています');
const propertiesResponse = await fetch(`${NODE_URL}${propertiesPath}`);
const propertiesJSON = await propertiesResponse.json();
const epochAdjustment = new Date(parseInt(
  propertiesJSON['network']['epochAdjustment']) * 1000);
console.log('  ネメシスタイムスタンプ:', epochAdjustment);

トランザクションの有効期限が現在のネットワーク時間より前、または2時間以上先の場合、トランザクションは拒否されます。 これを回避するには、 /node/time GET エンドポイントを使用して、トランザクションを構築する前に現在のネットワーク時間を知る必要があります。

ただし、アプリケーションはトランザクションごとにネットワーク時間を照会する必要はありません。 一度取得すれば、必要に応じてローカルのシステムクロックを使用して調整できます。これにより、精度とパフォーマンスのバランスが得られます。

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

Symbol の トランザクションは、ノードにトランザクションをブロックに含めるインセンティブを与えるために手数料を支払う必要があります。 手数料が低すぎると、どのノードもトランザクションを含めない可能性があります。 高すぎると、送信者は資金を無駄にします。 さらに、各ノードは入ってくるトランザクションに対して最低手数料のしきい値を強制する場合があります。

最適な手数料は、ネットワークの現在の状態、特に送信されているトランザクションの数とそれらが提示している手数料に依存します。 手数料の見積もりをサポートするために、Symbol は最近のトランザクション活動に基づく 推奨手数料乗数(recommended fee multiplier) を返す /network/fees/transaction GET エンドポイントを提供しています。

最終的な手数料は、推奨乗数にトランザクションのサイズ(バイト単位)を掛けることで計算されます。 これにより、大きなトランザクションは比例して手数料が高くなり、小さなトランザクションはコスト効率がよいまま維持されます。

アプリケーションは簡略化のために固定の手数料を使用することもできますが、ネットワークの推奨に従う方がより効率的です。 ネットワーク時間と同様に、トランザクションごとに乗数を照会する必要はありませんが、定期的に更新する必要があります。

上記のスニペットは、推奨乗数( medianFeeMultiplier )とノードの最小乗数( minFeeMultiplier )の大きい方を取得し、トランザクションのサイズが判明した後に使用できるように保存します。

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

    # Build the transaction
    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':
            facade.network.public_key_to_address(
                signer_key_pair.public_key),
        'mosaics': [{
            'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
            'amount': 1_000_000 # 1 XYM
        }]
    })
    transaction.fee = Amount(fee_mult * transaction.size)
    // Build the transaction
    const transaction = facade.transactionFactory.create({
        type: 'transfer_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        recipientAddress: facade.network.publicKeyToAddress(
            signerKeyPair.publicKey).toString(),
        mosaics: [{
            mosaicId: generateMosaicAliasId('symbol.xym'),
            amount: 1_000_000n // 1 XYM
        }]
    });
    transaction.fee = new models.Amount(feeMult * transaction.size);

Use a TransferTransactionV1Descriptor to create this transaction in a type-safe way.
See the Typed Descriptors tutorial for details.

転送トランザクションを構築する際には、必要なすべてのトランザクションプロパティを指定する必要があります。 スニペットには以下のフィールドが含まれています。

  • Type (タイプ): 転送トランザクションにはタイプ transfer_transaction_v1 を使用します。

  • Signer public key (署名者の公開鍵): 署名者は手数料を支払うアカウントです。 転送トランザクションでは、転送されるモザイクの送信元でもあります。

  • Deadline (有効期限): この値は現在のネットワーク時間から2時間後に設定されており、これが許容される最大の有効期限です。

  • Recipient address (受信者アドレス): この例では、受信者は送信者と同じです。 これはデモンストレーションとしては有用ですが、実用的ではありません。

  • Mosaics (モザイク): 転送トランザクションは一度に複数のモザイクを送信できるため、これは配列になります。 各エントリにはモザイク IDと金額が含まれます。

    この例では、XYM のモザイク ID は、完全な 16 進数 ID よりも覚えやすいエイリアスである symbol.xym を使用して取得されています。

    金額は絶対単位で表され、これはモザイクの 可分性 に依存します。 XYM の場合、可分性は 6 であるため、 1 XYM は 1_000_000 として表現する必要があります。

記述子には fee フィールドが設定されていないことに注意してください。 代わりに、手数料はトランザクションが構築された後に計算されます。計算には、事前に取得した乗数とトランザクションのサイズ(バイト単位)が使用されます。サイズは記述子が構築されて初めて判明します。

トランザクションにメッセージを含める

トランザクションには、オプションで自由形式のメッセージを含めることができます。 転送トランザクションでのメッセージの送信 で、その方法を説明しています。

署名とシリアライズ⚓︎

    # 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))
    // 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 });

トランザクションが作成されたら、署名するアカウントの秘密鍵を使用して署名する必要があります。 署名により、トランザクションが本物であり、送信者によって承認されていることが保証されます。

は、16 進数文字列としてエンコードされた署名 を返します。

は、署名をトランザクションに追加し、アナウンスのためにノードに直接送信できる状態の JSON ペイロードにシリアライズします。

トランザクションのアナウンス⚓︎

    # Announce the transaction
    announce_path = '/transactions'
    print(f'Announcing transaction to {announce_path}')
    announce_request = urllib.request.Request(
        f'{NODE_URL}{announce_path}',
        data=json_payload.encode(),
        headers={ 'Content-Type': 'application/json' },
        method='PUT'
    )
    with urllib.request.urlopen(announce_request) as response:
        print(f'  Response: {response.read().decode()}')
    // Announce the transaction
    const announcePath = '/transactions';
    console.log('Announcing transaction to', announcePath);
    const announceResponse = await fetch(`${NODE_URL}${announcePath}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    console.log('  Response:', await announceResponse.text());

トランザクションのアナウンスは、任意の SymbolのAPI ノード/transactions PUT エンドポイントへの単純なリクエストです。 ペイロードが正しく形成されていれば、リクエストは HTTP 200 レスポンスで成功します。

しかし、このレスポンスはトランザクションが有効であること、またはネットワークによって受け入れられたことを示すものでは ありません。 検証、手数料の確認、およびその他のルールは、トランザクションが受信された後に非同期に適用されます。

トランザクションが実際に受け入れられ、ブロックに含まれたことを確認するには、次のステップで示すように、そのステータスを個別に監視する必要があります。

承認の待機⚓︎

    # Wait for confirmation
    status_path = (
        f'/transactionStatus/{facade.hash_transaction(transaction)}')
    print(f'Waiting for confirmation from {status_path}')
    for attempt in range(60):
        time.sleep(1)
        try:
            with urllib.request.urlopen(
                f'{NODE_URL}{status_path}'
            ) as response:
                status = json.loads(response.read().decode())
                print(f'  Transaction status: {status['group']}')
                if status['group'] == 'confirmed':
                    print(f'Transaction confirmed in {attempt} seconds')
                    break
                if status['group'] == 'failed':
                    print(f'Transaction failed: {status['code']}')
                    break
        except urllib.error.HTTPError as e:
            print(f'  Transaction status: unknown | Cause: ({e.msg})')
    else:
        print('Confirmation took too long.')
    // Wait for confirmation
    const transactionHash =
        facade.hashTransaction(transaction).toString();
    const statusPath = `/transactionStatus/${transactionHash}`;
    console.log('Waiting for confirmation from', statusPath);

    let attempt = 0;

    function pollStatus() {
        attempt++;

        if (attempt > 60) {
            console.warn('Confirmation took too long.');
            return;
        }

        return fetch(`${NODE_URL}${statusPath}`)
            .then(response => {
                if (!response.ok) {
                    console.log('  Transaction status: unknown | Cause:',
                        response.statusText);
                    // HTTP error: schedule a retry
                    return new Promise(resolve =>
                        setTimeout(resolve, 1000)).then(pollStatus);
                }
                return response.json();
            })
            .then(status => {
                // Skip if previous step scheduled a retry
                if (!status) return;

                console.log('  Transaction status:', status.group);

                if (status.group === 'confirmed') {
                    console.log('Transaction confirmed in', attempt,
                        'seconds');
                } else if (status.group === 'failed') {
                    console.log('Transaction failed:', status.code);
                } else {
                    // Transaction unconfirmed: schedule a retry
                    return new Promise(resolve =>
                        setTimeout(resolve, 1000)).then(pollStatus);
                }
            });
    }
    pollStatus();

Note

このステップでは、トランザクションが承認されたかどうかを確認するためにポーリングを使用しています。 ポーリングはここでは説明の目的で使用されていますが、実際のアプリケーションには推奨されるアプローチではありません。

WebSocket を使用すると、API 呼び出しを繰り返すオーバーヘッドなしに、よりレスポンスの高いソリューションを提供できます。

さらに、トランザクションステータスを確認するロジックは再利用可能です。 すべてのトランザクションをアナウンスした後に必要となるため、ユーティリティ関数やモジュールに移動させることができます。

上記のスニペットは、送信されたトランザクションのハッシュを使用して、繰り返し /transactionStatus/{hash} GET エンドポイントを照会します。 レスポンスは以下のいずれかの形式をとる可能性があります。

  • HTTP エラー。ノードがまだトランザクションの処理を開始していないことを示します。
  • トランザクションステータスを含む有効な JSON オブジェクト。

ステータスグループが confirmed の場合、トランザクションは受け入れられ、ブロックに含まれています。

ステータスグループが failed の場合、トランザクションは拒否されています。例えば、残高不足などが原因です。

それ以外の場合は、コードは 1 秒待機してから再試行します(最大 60 回まで)。

コードは、最初のステータス確認を実行する 前に 最初の待機を行っていることに注目してください。 これにより、アナウンス後にノードがトランザクションの処理を開始するための時間が確保されます。

出力⚓︎

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

Using node https://reference.symboltest.net:3001
Fetching current network time from /node/time
  Network time: 78235462065n ms since nemesis
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100
Built transaction:
{
  signature: '728D968E14F50EBB2496B560721E938629D6B4C1522B4A22DD659507B469C0EC5125485EBD38D48FBF351FF9DEC9CF3AFD7A5AFC5E945087E53173589B0B6B08',
  signerPublicKey: '87DA603E7BE5656C45692D5FC7F6D0EF8F24BB7A5C10ED5FDA8C5CFBC49FCBC8',
  version: 1,
  network: 152,
  type: 16724,
  fee: '17600',
  deadline: '78242662065',
  recipientAddress: '98F96BD2F803DE1EE39AACFC53A246F4F7A46901A5D0A53E',
  mosaics: [ { mosaicId: '16666583871264174062', amount: '1000000' } ],
  message: ''
}
Announcing transaction to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for confirmation from /transactionStatus/260CD293E05C2853A967874BCF67FAB36FD331CE14925CA611B3877B99BB325D
  Transaction status: unknown | Cause: Not Found
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
Transaction confirmed in 5 seconds

承認までのステータス確認の回数はネットワークの状況によって変化し、ノードがトランザクションの処理を開始する速度によっては、最初の unknown ステータスが表示されたりされなかったりします。

ネットワークの観点からトランザクションを確認するには、 Symbol Testnet Explorer にアクセスし、トランザクションハッシュを検索します。 ハッシュは Waiting for confirmation from /transactionStatus/... と書かれた行に出力されています。 トランザクションが承認プロセスを進む様子をリアルタイムで確認できるはずです。

あるいは、 signerPublicKey を検索して、署名者アカウントの履歴内でトランザクションを表示することもできます。

結論⚓︎

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

ステップ 関連ドキュメント
有効期限情報の取得 /node/time GET
手数料情報の取得 /network/fees/transaction GET
トランザクションの作成
トランザクションの署名
トランザクションのアナウンス /transactions PUT
承認の待機 /transactionStatus/{hash} GET

他のトランザクションタイプも、同じ一般的なプロセスに従います。