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

アグリゲートボンデッドトランザクションの作成⚓︎

このチュートリアルでは、アグリゲートボンデッドトランザクション を使用してアセットスワップを作成する方法を説明します。

この例では、アカウント A が アカウント B に 10XYM を送信し、同時に アカウント B が 1 つのカスタム モザイク をアカウント A に送り返します。

%3clusterAggregateアグリゲートボンデッドトランザクションclusterT1埋め込み転送 1clusterT2埋め込み転送 2A2アカウント AB2アカウント BA2->B210 XYMA1アカウント AB1アカウント BA1->B11 カスタムモザイク

2種類のアグリゲートトランザクション

アグリゲートトランザクション は、複数の トランザクション を単一の操作にグループ化し、関係するすべてのアカウントからの 署名 を必要とします。

「アグリゲートボンデッドトランザクション」は、アナウンスされた後にオンチェーンで署名を収集します。これは、オフチェーンでの調整が実用的でない場合に有効です。

例:

  • 共有インフラがない: 当事者間で共通のシステムを通じた調整ができないため、ブロックチェーンを共通のインターフェースとして使用します。
  • 非同期ワークフロー: 連署者が同時に対応できない、またはリアルタイムでの調整ができません。

スパムを防止するため、アグリゲートボンデッドには「ハッシュロック」が必要です(10 XYM のデポジット)。すべての 連署 が届き、トランザクションが承認されると、ネットワークはこのデポジットを返却します。

当事者がオフチェーンで通信して署名を交換できる場合、アグリゲートコンプリートトランザクション を使用すれば、このデポジットは不要です。

前提条件⚓︎

開始する前に、開発環境がセットアップされていることを確認してください。 開発環境のセットアップ を参照してください。

また、スワップを完了させるために、XYM を持つ2つの アカウント と1つのカスタム モザイク が必要です。便宜上、事前に資金供給されたアカウントが提供されていますが、これらはメンテナンスされておらず資金が不足している可能性があります。

自身のアカウントを使用する場合は、以下の手順を完了してください:

  • アグリゲートトランザクションを開始するためのアカウント(アカウント A)を、コード または ウォレット を使用して作成します。
  • スワップに参加するための2つ目のアカウント(アカウント B)を作成します。
  • トランザクション手数料、転送量、およびハッシュロックのデポジットを支払うための XYM をアカウント A で入手します。蛇口 (Faucet) からテストネットの通貨を入手する を参照してください。
  • スワップのためにアカウント B が所有するモザイクを作成します。モザイクの作成 を参照してください。

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

完全なコード⚓︎

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

import json
import os
import time
import urllib.request

from symbolchain.CryptoTypes import Hash256, PrivateKey
from symbolchain.facade.SymbolFacade import SymbolFacade
from symbolchain.sc import Amount
from symbolchain.symbol.IdGenerator import generate_mosaic_alias_id
from symbolchain.symbol.Network import NetworkTimestamp

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

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

# Helper function to wait for transaction status
def wait_for_status(hash_value, expected_status, label):
    print(f'Waiting for {label} to reach {expected_status} status...')
    attempts = 0
    max_attempts = 60

    while attempts < max_attempts:
        try:
            url = f'{NODE_URL}/transactionStatus/{hash_value}'
            with urllib.request.urlopen(url) as response:
                status = json.loads(response.read().decode())

                print(f'  Transaction status: {status["group"]}')

                if status['group'] == 'failed':
                    raise Exception(f'{label} failed: {status["code"]}')

                if status['group'] == expected_status:
                    print(f'{label} {expected_status} ' +
                    f'in {attempts} seconds')
                    return

        except urllib.error.HTTPError as e:
            if e.code != 404:
                raise
            print('  Transaction status: not yet available')

        attempts += 1
        time.sleep(1)

    raise Exception(
        f'{label} not {expected_status} after {max_attempts} attempts'
    )

# Account A (initiates the aggregate tx and sends XYM to Account B)
ACCOUNT_A_PRIVATE_KEY = os.getenv(
    'ACCOUNT_A_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000')
account_a_key_pair = SymbolFacade.KeyPair(
    PrivateKey(ACCOUNT_A_PRIVATE_KEY))

# Account B (sends custom mosaic to Account A)
ACCOUNT_B_PRIVATE_KEY = os.getenv(
    'ACCOUNT_B_PRIVATE_KEY',
    '1111111111111111111111111111111111111111111111111111111111111111')
account_b_key_pair = SymbolFacade.KeyPair(
    PrivateKey(ACCOUNT_B_PRIVATE_KEY))

facade = SymbolFacade('testnet')
account_a_address = facade.network.public_key_to_address(
    account_a_key_pair.public_key)
account_b_address = facade.network.public_key_to_address(
    account_b_key_pair.public_key)
print(f'Account A: {account_a_address}')
print(f'Account B: {account_b_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}')

    # Embedded tx 1: Account A transfers 10 XYM to Account B
    embedded_transaction_1 = facade.transaction_factory.create_embedded({
        'type': 'transfer_transaction_v1',
        'signer_public_key': account_a_key_pair.public_key,
        'recipient_address': account_b_address,
        'mosaics': [{
            'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
            'amount': 10_000_000  # 10 XYM (divisibility = 6)
        }]
    })

    # Embedded tx 2: Account B transfers 1 custom mosaic to Account A
    custom_mosaic_id = 0x6D1314BE751B62C2
    embedded_transaction_2 = facade.transaction_factory.create_embedded({
        'type': 'transfer_transaction_v1',
        'signer_public_key': account_b_key_pair.public_key,
        'recipient_address': account_a_address,
        'mosaics': [{
            'mosaic_id': custom_mosaic_id,
            'amount': 1  # 1 custom mosaic (divisibility = 0)
        }]
    })

    # Build the bonded aggregate transaction
    embedded_transactions = [
        embedded_transaction_1, embedded_transaction_2]
    bonded_transaction = facade.transaction_factory.create({
        'type': 'aggregate_bonded_transaction_v3',
        'signer_public_key': account_a_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'transactions_hash': facade.hash_embedded_transactions(
            embedded_transactions),
        'transactions': embedded_transactions
    })
    # Reserve space for one cosignature (104 bytes)
    # and calculate fee for the final transaction size
    bonded_transaction.fee = Amount(
        fee_mult * (bonded_transaction.size + 104))
    print('Built aggregate without signatures:')
    print(json.dumps(bonded_transaction.to_json(), indent=2))

    # --- ACCOUNT A (Initiator) ---
    # Sign the bonded aggregate transaction
    print('[Account A] Signing the bonded aggregate...')
    bonded_signature = facade.sign_transaction(
        account_a_key_pair, bonded_transaction)
    bonded_json_payload = facade.transaction_factory.attach_signature(
        bonded_transaction, bonded_signature)
    bonded_hash = facade.hash_transaction(bonded_transaction)
    print(f'Bonded aggregate transaction hash: {bonded_hash}')

    # Create hash lock transaction
    print('Creating hash lock transaction...')
    hash_lock = facade.transaction_factory.create({
        'type': 'hash_lock_transaction_v1',
        'signer_public_key': account_a_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'mosaic': {
            'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
            'amount': 10_000_000  # 10 XYM deposit
        },
        'duration': 100,  # Lock duration in blocks
        'hash': bonded_hash
    })
    hash_lock.fee = Amount(fee_mult * hash_lock.size)

    # Sign hash lock
    print('[Account A] Signing the hash lock...')
    hash_lock_signature = facade.sign_transaction(
        account_a_key_pair, hash_lock)
    hash_lock_payload = facade.transaction_factory.attach_signature(
        hash_lock, hash_lock_signature)
    hash_lock_hash = facade.hash_transaction(hash_lock)
    print(f'Hash lock transaction hash: {hash_lock_hash}')

    # Announce hash lock and wait for confirmation
    announce_transaction(hash_lock_payload, '/transactions', 'Hash lock')
    wait_for_status(hash_lock_hash, 'confirmed', 'Hash lock')

    # Announce bonded aggregate and wait for partial status
    announce_transaction(
        bonded_json_payload, '/transactions/partial',
        'Bonded aggregate transaction'
    )
    wait_for_status(
        bonded_hash, 'partial',
        'Bonded aggregate transaction'
    )

    # --- ACCOUNT B (Cosigner) ---
    # Retrieves partial transactions waiting for signature
    partial_path = f'/transactions/partial?address={account_b_address}'
    print(
        '[Account B] Checking for partial transactions from '
        '/transactions/partial'
    )
    with urllib.request.urlopen(f'{NODE_URL}{partial_path}') as response:
        partial_txs = json.loads(response.read().decode())
        if not partial_txs['data']:
            raise Exception('No partial transactions found')

    print(f'Found {len(partial_txs["data"])} partial transaction(s)')

    # Find the transaction matching the expected hash
    found = any(
        tx['meta']['hash'] == str(bonded_hash)
        for tx in partial_txs['data']
    )
    if not found:
        raise Exception(
            f'Expected transaction {bonded_hash} not found in '
            f'partial transactions')
    print(f'Found matching transaction: {bonded_hash}')

    # Fetch full transaction details using the hash
    detail_path = f'/transactions/partial/{bonded_hash}'
    with urllib.request.urlopen(f'{NODE_URL}{detail_path}') as response:
        partial_tx_json = json.loads(response.read().decode())

    # Verify transaction content before cosigning
    tx_data = partial_tx_json['transaction']
    print(
        f'[Account B] Verifying transaction: '
        f'{len(tx_data["transactions"])} embedded transactions'
    )

    # Submit Account B's cosignature using the transaction hash
    cosignature_path = '/transactions/cosignature'
    print('[Account B] Cosigning the bonded aggregate...')
    cosignature = facade.cosign_transaction_hash(
        account_b_key_pair, bonded_hash, True)
    cosignature_payload = json.dumps({
        'version': str(cosignature.version),
        'signerPublicKey': str(cosignature.signer_public_key),
        'signature': str(cosignature.signature),
        'parentHash': str(cosignature.parent_hash)
    })

    # Announce cosignature
    announce_transaction(
        cosignature_payload, cosignature_path, 'cosignature'
    )

    # Wait for final confirmation
    wait_for_status(
        bonded_hash, 'confirmed',
        'Bonded aggregate transaction'
    )

except Exception as e:
    print(e)

Download source

import { Hash256, PrivateKey } from 'symbol-sdk';
import {
    generateMosaicAliasId,
    models,
    NetworkTimestamp,
    SymbolFacade
} 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 transaction
async function announceTransaction(payload, endpoint, label) {
    console.log(`Announcing ${label} to ${endpoint}`);
    const response = await fetch(`${NODE_URL}${endpoint}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: payload
    });
    console.log('  Response:', await response.text());
}

// Helper function to wait for transaction status
async function waitForStatus(hash, expectedStatus, label) {
    console.log(
        `Waiting for ${label} to reach ${expectedStatus} status...`);
    let attempts = 0;
    const maxAttempts = 60;

    while (attempts < maxAttempts) {
        try {
            const url = `${NODE_URL}/transactionStatus/${hash}`;
            const response = await fetch(url);

            if (!response.ok) {
                const error = new Error(
                    `HTTP ${response.status}: ${response.statusText}`);
                error.status = response.status;
                throw error;
            }

            const status = await response.json();

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

            if (status.group === 'failed') {
                throw new Error(`${label} failed: ${status.code}`);
            }

            if (status.group === expectedStatus) {
                console.log(
                    `${label} ${expectedStatus} in ${attempts} seconds`
                );
                return;
            }

        } catch (error) {
            if (error.status === 404) {
                console.log('  Transaction status: not yet available');
            } else {
                throw error;
            }
        }

        attempts++;
        await new Promise(resolve => setTimeout(resolve, 1000));
    }

    throw new Error(
        `${label} not ${expectedStatus} after ${maxAttempts} attempts`
    );
}

// Account A (initiates the aggregate tx and sends XYM to Account B)
const ACCOUNT_A_PRIVATE_KEY = process.env.ACCOUNT_A_PRIVATE_KEY || (
    '0000000000000000000000000000000000000000000000000000000000000000');
const accountAKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(ACCOUNT_A_PRIVATE_KEY));

// Account B (sends custom mosaic to Account A)
const ACCOUNT_B_PRIVATE_KEY = process.env.ACCOUNT_B_PRIVATE_KEY || (
    '1111111111111111111111111111111111111111111111111111111111111111');
const accountBKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(ACCOUNT_B_PRIVATE_KEY));

const facade = new SymbolFacade('testnet');
const accountAAddress = facade.network.publicKeyToAddress(
    accountAKeyPair.publicKey);
const accountBAddress = facade.network.publicKeyToAddress(
    accountBKeyPair.publicKey);
console.log('Account A:', accountAAddress.toString());
console.log('Account B:', accountBAddress.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 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);

    // Embedded tx 1: Account A transfers 10 XYM to Account B
    const embeddedTransaction1 = facade.transactionFactory
        .createEmbedded({
            type: 'transfer_transaction_v1',
            signerPublicKey: accountAKeyPair.publicKey.toString(),
            recipientAddress: accountBAddress.toString(),
            mosaics: [{
                mosaicId: generateMosaicAliasId('symbol.xym'),
                amount: 10_000_000n  // 10 XYM (divisibility = 6)
            }]
        });

    // Embedded tx 2: Account B transfers 1 custom mosaic to Account A
    const customMosaicId = 0x6D1314BE751B62C2n;
    const embeddedTransaction2 = facade.transactionFactory
        .createEmbedded({
            type: 'transfer_transaction_v1',
            signerPublicKey: accountBKeyPair.publicKey.toString(),
            recipientAddress: accountAAddress.toString(),
            mosaics: [{
                mosaicId: customMosaicId,
                amount: 1n  // 1 custom mosaic (divisibility = 0)
            }]
        });

    // Build the bonded aggregate transaction
    const embeddedTransactions = [
        embeddedTransaction1, embeddedTransaction2];
    const bondedTransaction = facade.transactionFactory.create({
        type: 'aggregate_bonded_transaction_v3',
        signerPublicKey: accountAKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        transactionsHash: facade.static.hashEmbeddedTransactions(
            embeddedTransactions),
        transactions: embeddedTransactions
    });
    // Reserve space for one cosignature (104 bytes)
    // and calculate fee for the final transaction size
    bondedTransaction.fee = new models.Amount(
        feeMult * (bondedTransaction.size + 104)
    );
    console.log('Built aggregate without signatures:');
    console.log(JSON.stringify(bondedTransaction.toJson(), null, 2));

    // --- ACCOUNT A (Initiator) ---
    // Sign the bonded aggregate transaction
    console.log('[Account A] Signing the bonded aggregate...');
    const bondedSignature = facade.signTransaction(
        accountAKeyPair, bondedTransaction);
    const bondedJsonPayload = facade.transactionFactory.static
        .attachSignature(bondedTransaction, bondedSignature);
    const bondedHash = facade.hashTransaction(
        bondedTransaction).toString();
    console.log('Bonded aggregate transaction hash:', bondedHash);

    // Create hash lock transaction
    console.log('Creating hash lock transaction...');
    const hashLock = facade.transactionFactory.create({
        type: 'hash_lock_transaction_v1',
        signerPublicKey: accountAKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        mosaic: {
            mosaicId: generateMosaicAliasId('symbol.xym'),
            amount: 10_000_000n  // 10 XYM deposit
        },
        duration: 100n,  // Lock duration in blocks
        hash: bondedHash
    });
    hashLock.fee = new models.Amount(feeMult * hashLock.size);

    // Sign hash lock
    console.log('[Account A] Signing the hash lock...');
    const hashLockSignature = facade.signTransaction(
        accountAKeyPair, hashLock);
    const hashLockPayload = facade.transactionFactory.static
        .attachSignature(hashLock, hashLockSignature);
    const hashLockHash = facade.hashTransaction(hashLock)
        .toString();
    console.log('Hash lock transaction hash:', hashLockHash);

    // Announce hash lock and wait for confirmation
    await announceTransaction(
        hashLockPayload, '/transactions', 'Hash lock'
    );
    await waitForStatus(hashLockHash, 'confirmed', 'Hash lock');

    // Announce bonded aggregate and wait for partial status
    await announceTransaction(
        bondedJsonPayload, '/transactions/partial',
        'Bonded aggregate transaction'
    );
    await waitForStatus(
        bondedHash, 'partial', 'Bonded aggregate transaction'
    );

    // --- ACCOUNT B (Cosigner) ---
    // Retrieves partial transactions waiting for signature
    const partialPath =
        `/transactions/partial?address=${accountBAddress}`;
    console.log(
        '[Account B] Checking for partial transactions from ' +
        '/transactions/partial'
    );
    const partialResponse = await fetch(`${NODE_URL}${partialPath}`);
    const partialTxs = await partialResponse.json();
    if (!partialTxs.data || partialTxs.data.length === 0) {
        throw new Error('No partial transactions found');
    }

    console.log(`Found ${partialTxs.data.length} partial transaction(s)`);

    // Find the transaction matching the expected hash
    const found = partialTxs.data.some(
        tx => tx.meta.hash === bondedHash
    );
    if (!found) {
        throw new Error(
            `Expected transaction ${bondedHash} not found in ` +
            `partial transactions`
        );
    }
    console.log(`Found matching transaction: ${bondedHash}`);

    // Fetch full transaction details using the hash
    const detailPath = `/transactions/partial/${bondedHash}`;
    const detailResponse = await fetch(`${NODE_URL}${detailPath}`);
    const partialTxJson = await detailResponse.json();

    // Verify transaction content before cosigning
    const txData = partialTxJson.transaction;
    console.log(
        `[Account B] Verifying transaction: ` +
        `${txData.transactions.length} embedded transactions`
    );

    // Submit Account B's cosignature using the transaction hash
    const cosignaturePath = '/transactions/cosignature';
    console.log('[Account B] Cosigning the bonded aggregate...');
    const cosignature = SymbolFacade.cosignTransactionHash(
        accountBKeyPair, new Hash256(bondedHash), true
    );
    const cosignaturePayload = JSON.stringify({
        version: cosignature.version.toString(),
        signerPublicKey: cosignature.signerPublicKey.toString(),
        signature: cosignature.signature.toString(),
        parentHash: cosignature.parentHash.toString()
    });

    // Announce cosignature
    await announceTransaction(
        cosignaturePayload, cosignaturePath, 'cosignature'
    );

    // Wait for final confirmation
    await waitForStatus(
        new Hash256(bondedHash), 'confirmed',
        'Bonded aggregate transaction'
    );
} catch (e) {
    console.error(e.message, '| Cause:', e.cause?.code ?? 'unknown');
}

Download source

アグリゲートボンデッドトランザクションには、2つの異なる役割が含まれます。アグリゲートを構築・署名・アナウンスする 開始者(アカウント A)と、保留中のトランザクションをポーリングし、内容を確認した後に署名を追加する1人以上の 連署者(アカウント B)です。

実際には、それぞれの役割は別々のマシンの別々のプログラムとして動作します。このチュートリアルでは、簡略化のため両方の役割を1つのスクリプトにまとめています。

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

アカウント A: 開始者のワークフロー⚓︎

アカウントの設定⚓︎

# Account A (initiates the aggregate tx and sends XYM to Account B)
ACCOUNT_A_PRIVATE_KEY = os.getenv(
    'ACCOUNT_A_PRIVATE_KEY',
    '0000000000000000000000000000000000000000000000000000000000000000')
account_a_key_pair = SymbolFacade.KeyPair(
    PrivateKey(ACCOUNT_A_PRIVATE_KEY))

# Account B (sends custom mosaic to Account A)
ACCOUNT_B_PRIVATE_KEY = os.getenv(
    'ACCOUNT_B_PRIVATE_KEY',
    '1111111111111111111111111111111111111111111111111111111111111111')
account_b_key_pair = SymbolFacade.KeyPair(
    PrivateKey(ACCOUNT_B_PRIVATE_KEY))

facade = SymbolFacade('testnet')
account_a_address = facade.network.public_key_to_address(
    account_a_key_pair.public_key)
account_b_address = facade.network.public_key_to_address(
    account_b_key_pair.public_key)
print(f'Account A: {account_a_address}')
print(f'Account B: {account_b_address}')
const ACCOUNT_A_PRIVATE_KEY = process.env.ACCOUNT_A_PRIVATE_KEY || (
    '0000000000000000000000000000000000000000000000000000000000000000');
const accountAKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(ACCOUNT_A_PRIVATE_KEY));

// Account B (sends custom mosaic to Account A)
const ACCOUNT_B_PRIVATE_KEY = process.env.ACCOUNT_B_PRIVATE_KEY || (
    '1111111111111111111111111111111111111111111111111111111111111111');
const accountBKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(ACCOUNT_B_PRIVATE_KEY));

const facade = new SymbolFacade('testnet');
const accountAAddress = facade.network.publicKeyToAddress(
    accountAKeyPair.publicKey);
const accountBAddress = facade.network.publicKeyToAddress(
    accountBKeyPair.publicKey);
console.log('Account A:', accountAAddress.toString());
console.log('Account B:', accountBAddress.toString());

この例では、簡略化のため1つのスクリプトに両方の 秘密鍵 を含めています。実際には、各当事者が自身のマシンで署名します。 アカウント A は、埋め込みトランザクションの署名者としてアカウント B を設定し、B の アドレス を派生させるために、アカウント B の 公開鍵 のみを必要とします。

環境変数 ACCOUNT_A_PRIVATE_KEYACCOUNT_B_PRIVATE_KEY で各アカウントの鍵を設定します。提供されない場合は、デフォルトのテストキーが使用されます。 自身の鍵を使用する場合は、アカウント A が XYM を持ち、アカウント B がスワップ用のカスタムモザイクを保持していることを確認してください。

両方のアカウントのアドレスは、ファサードのネットワーク設定を使用して公開鍵から派生します。

ネットワーク時間と手数料の取得⚓︎

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

転送トランザクション チュートリアルで説明されているプロセスに従い、ネットワーク時間と推奨手数料をそれぞれ /node/time GET および /network/fees/transaction GET から取得します。

埋め込みトランザクションの作成⚓︎

    # Embedded tx 1: Account A transfers 10 XYM to Account B
    embedded_transaction_1 = facade.transaction_factory.create_embedded({
        'type': 'transfer_transaction_v1',
        'signer_public_key': account_a_key_pair.public_key,
        'recipient_address': account_b_address,
        'mosaics': [{
            'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
            'amount': 10_000_000  # 10 XYM (divisibility = 6)
        }]
    })

    # Embedded tx 2: Account B transfers 1 custom mosaic to Account A
    custom_mosaic_id = 0x6D1314BE751B62C2
    embedded_transaction_2 = facade.transaction_factory.create_embedded({
        'type': 'transfer_transaction_v1',
        'signer_public_key': account_b_key_pair.public_key,
        'recipient_address': account_a_address,
        'mosaics': [{
            'mosaic_id': custom_mosaic_id,
            'amount': 1  # 1 custom mosaic (divisibility = 0)
        }]
    })
    const embeddedTransaction1 = facade.transactionFactory
        .createEmbedded({
            type: 'transfer_transaction_v1',
            signerPublicKey: accountAKeyPair.publicKey.toString(),
            recipientAddress: accountBAddress.toString(),
            mosaics: [{
                mosaicId: generateMosaicAliasId('symbol.xym'),
                amount: 10_000_000n  // 10 XYM (divisibility = 6)
            }]
        });

    // Embedded tx 2: Account B transfers 1 custom mosaic to Account A
    const customMosaicId = 0x6D1314BE751B62C2n;
    const embeddedTransaction2 = facade.transactionFactory
        .createEmbedded({
            type: 'transfer_transaction_v1',
            signerPublicKey: accountBKeyPair.publicKey.toString(),
            recipientAddress: accountAAddress.toString(),
            mosaics: [{
                mosaicId: customMosaicId,
                amount: 1n  // 1 custom mosaic (divisibility = 0)
            }]
        });

埋め込みトランザクション は、アトミック(不可分)に実行される操作を定義します。各埋め込みトランザクションでは以下を指定します。

  • タイプ: すべてのトランザクションタイプをアグリゲート内に埋め込むことができます(他のアグリゲートを除く)。埋め込み転送の場合は、通常の転送トランザクションと同じ transfer_transaction_v1 を使用します。

  • 署名者の公開鍵: このトランザクションが単独でアナウンスされた場合に署名するアカウントです。

  • トランザクション固有のフィールド: トランザクションタイプに固有のすべてのフィールドを指定する必要があります。転送の場合は、受信者アドレスと送信する モザイク が含まれます。

埋め込みトランザクションには、手数料(fee)や有効期限(deadline)のフィールドは 含まれない ことに注意してください。これらは、それらを包むアグリゲートトランザクションから継承されます。

この例では、スワップのために2つの 転送トランザクション を作成します:

  • 1つ目の転送は、アカウント A からアカウント B へ 10 XYM を送信します。
  • 2つ目の転送は、アカウント B からアカウント A へ 1 つのカスタムモザイクを送信します。

カスタムモザイクについて

ID 0x6D1314BE751B62C2 のカスタムモザイクはこのチュートリアルのために作成されました。スワップが正常に実行されるよう、デフォルトのアカウント B にはこのモザイクが事前に配布されています。

自身のアカウントを使用する場合は、アカウント B がカスタムモザイクを保持していることを確認し、コード内のモザイク ID を更新してください。

アグリゲートトランザクションの構築⚓︎

    # Build the bonded aggregate transaction
    embedded_transactions = [
        embedded_transaction_1, embedded_transaction_2]
    bonded_transaction = facade.transaction_factory.create({
        'type': 'aggregate_bonded_transaction_v3',
        'signer_public_key': account_a_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'transactions_hash': facade.hash_embedded_transactions(
            embedded_transactions),
        'transactions': embedded_transactions
    })
    # Reserve space for one cosignature (104 bytes)
    # and calculate fee for the final transaction size
    bonded_transaction.fee = Amount(
        fee_mult * (bonded_transaction.size + 104))
    print('Built aggregate without signatures:')
    print(json.dumps(bonded_transaction.to_json(), indent=2))
    const embeddedTransactions = [
        embeddedTransaction1, embeddedTransaction2];
    const bondedTransaction = facade.transactionFactory.create({
        type: 'aggregate_bonded_transaction_v3',
        signerPublicKey: accountAKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        transactionsHash: facade.static.hashEmbeddedTransactions(
            embeddedTransactions),
        transactions: embeddedTransactions
    });
    // Reserve space for one cosignature (104 bytes)
    // and calculate fee for the final transaction size
    bondedTransaction.fee = new models.Amount(
        feeMult * (bondedTransaction.size + 104)
    );
    console.log('Built aggregate without signatures:');
    console.log(JSON.stringify(bondedTransaction.toJson(), null, 2));

埋め込みトランザクションの準備ができたら、それらをラップするアグリゲートボンデッドトランザクションを作成します:

  • タイプ: aggregate_bonded_transaction_v3 を使用します。

  • 署名者の公開鍵: アグリゲートを開始するアカウントです。このアカウントがトランザクションをアナウンスし、トランザクション手数料を支払います。

    トランザクション手数料の共有

    署名者が全額を前払いで支払いますが、他の参加者がアグリゲート内で費用を負担することも可能です。詳細は 他のアカウントの代理でのトランザクション手数料の支払い を参照してください。

  • 有効期限 (Deadline): トランザクションが失効し、承認されなくなる ネットワーク時間 上のタイムスタンプです。

  • トランザクションハッシュ: すべての埋め込みトランザクションから計算されるハッシュです。これにより、署名後に埋め込みトランザクションが変更されないことが保証されます。この値の計算には を使用します。

  • トランザクション: 実行する埋め込みトランザクションの配列です。

手数料は、アグリゲートの総サイズに基づいて計算されます。これには、すべての埋め込みトランザクションと、1つの連署(104バイト)用に予約されたスペースが含まれます。

ボンデッドトランザクションの署名⚓︎

    # --- ACCOUNT A (Initiator) ---
    # Sign the bonded aggregate transaction
    print('[Account A] Signing the bonded aggregate...')
    bonded_signature = facade.sign_transaction(
        account_a_key_pair, bonded_transaction)
    bonded_json_payload = facade.transaction_factory.attach_signature(
        bonded_transaction, bonded_signature)
    bonded_hash = facade.hash_transaction(bonded_transaction)
    print(f'Bonded aggregate transaction hash: {bonded_hash}')
    // Sign the bonded aggregate transaction
    console.log('[Account A] Signing the bonded aggregate...');
    const bondedSignature = facade.signTransaction(
        accountAKeyPair, bondedTransaction);
    const bondedJsonPayload = facade.transactionFactory.static
        .attachSignature(bondedTransaction, bondedSignature);
    const bondedHash = facade.hashTransaction(
        bondedTransaction).toString();
    console.log('Bonded aggregate transaction hash:', bondedHash);

アカウント A がボンデッドトランザクションに署名し、メインの署名を作成してトランザクションハッシュを確定させます。

このハッシュは、次のステップであるハッシュロックトランザクションの作成に必要です。

ハッシュロックの作成⚓︎

    # Create hash lock transaction
    print('Creating hash lock transaction...')
    hash_lock = facade.transaction_factory.create({
        'type': 'hash_lock_transaction_v1',
        'signer_public_key': account_a_key_pair.public_key,
        'deadline': timestamp.add_hours(2).timestamp,
        'mosaic': {
            'mosaic_id': generate_mosaic_alias_id('symbol.xym'),
            'amount': 10_000_000  # 10 XYM deposit
        },
        'duration': 100,  # Lock duration in blocks
        'hash': bonded_hash
    })
    hash_lock.fee = Amount(fee_mult * hash_lock.size)

    # Sign hash lock
    print('[Account A] Signing the hash lock...')
    hash_lock_signature = facade.sign_transaction(
        account_a_key_pair, hash_lock)
    hash_lock_payload = facade.transaction_factory.attach_signature(
        hash_lock, hash_lock_signature)
    hash_lock_hash = facade.hash_transaction(hash_lock)
    print(f'Hash lock transaction hash: {hash_lock_hash}')

    # Announce hash lock and wait for confirmation
    announce_transaction(hash_lock_payload, '/transactions', 'Hash lock')
    wait_for_status(hash_lock_hash, 'confirmed', 'Hash lock')
    console.log('Creating hash lock transaction...');
    const hashLock = facade.transactionFactory.create({
        type: 'hash_lock_transaction_v1',
        signerPublicKey: accountAKeyPair.publicKey.toString(),
        deadline: timestamp.addHours(2).timestamp,
        mosaic: {
            mosaicId: generateMosaicAliasId('symbol.xym'),
            amount: 10_000_000n  // 10 XYM deposit
        },
        duration: 100n,  // Lock duration in blocks
        hash: bondedHash
    });
    hashLock.fee = new models.Amount(feeMult * hashLock.size);

    // Sign hash lock
    console.log('[Account A] Signing the hash lock...');
    const hashLockSignature = facade.signTransaction(
        accountAKeyPair, hashLock);
    const hashLockPayload = facade.transactionFactory.static
        .attachSignature(hashLock, hashLockSignature);
    const hashLockHash = facade.hashTransaction(hashLock)
        .toString();
    console.log('Hash lock transaction hash:', hashLockHash);

    // Announce hash lock and wait for confirmation
    await announceTransaction(
        hashLockPayload, '/transactions', 'Hash lock'
    );
    await waitForStatus(hashLockHash, 'confirmed', 'Hash lock');

アグリゲートボンデッドをアナウンスする前に、ハッシュロックトランザクションを作成し、承認させる必要があります。ハッシュロックは、スパムを防止し、未完了の 部分トランザクション によってネットワークリソースが枯渇しないようにするためのデポジットとして機能します。

ハッシュロックトランザクションでは以下を指定します。

  • タイプ: hash_lock_transaction_v1 を使用します。

  • モザイク: デポジット額(10 XYM)。このデポジットは連署を待つ間、一時的にロックされます。

  • 期間 (Duration): デポジットがロックされるブロック数(この例では 100 ブロック)。期間が切れる前にすべての連署が収集され、アグリゲートボンデッドが承認されれば、デポジットは返却されます。そうでない場合は没収されます。

  • ハッシュ: ロック対象となるアグリゲートボンデッドトランザクションのハッシュです。

ハッシュロックは を使用して署名され、ヘルパー関数 announce_transaction を使用してアナウンスされます。アグリゲートボンデッドをアナウンスするには、ハッシュロックが先に承認されている必要があります。

その後、wait_for_status ヘルパー関数が承認されるまでトランザクションステータスをポーリングします。

ボンデッドトランザクションのアナウンス⚓︎

    # Announce bonded aggregate and wait for partial status
    announce_transaction(
        bonded_json_payload, '/transactions/partial',
        'Bonded aggregate transaction'
    )
    wait_for_status(
        bonded_hash, 'partial',
        'Bonded aggregate transaction'
    )
    await announceTransaction(
        bondedJsonPayload, '/transactions/partial',
        'Bonded aggregate transaction'
    );
    await waitForStatus(
        bondedHash, 'partial', 'Bonded aggregate transaction'
    );

ハッシュロックが承認されたら、アグリゲートボンデッドを announce_transaction ヘルパーを使用して /transactions/partial PUT にアナウンスします。

ノード はトランザクションを検証し、有効なハッシュロックが存在することを確認して、それを「部分的(partial)」な状態に置きます。wait_for_status ヘルパーはこの状態に達するまでトランザクションを監視し、その時点で連署を収集できるようになります。

アカウント B: 連署者のワークフロー⚓︎

トランザクションの復元⚓︎

    # --- ACCOUNT B (Cosigner) ---
    # Retrieves partial transactions waiting for signature
    partial_path = f'/transactions/partial?address={account_b_address}'
    print(
        '[Account B] Checking for partial transactions from '
        '/transactions/partial'
    )
    with urllib.request.urlopen(f'{NODE_URL}{partial_path}') as response:
        partial_txs = json.loads(response.read().decode())
        if not partial_txs['data']:
            raise Exception('No partial transactions found')

    print(f'Found {len(partial_txs["data"])} partial transaction(s)')

    # Find the transaction matching the expected hash
    found = any(
        tx['meta']['hash'] == str(bonded_hash)
        for tx in partial_txs['data']
    )
    if not found:
        raise Exception(
            f'Expected transaction {bonded_hash} not found in '
            f'partial transactions')
    print(f'Found matching transaction: {bonded_hash}')
    // Retrieves partial transactions waiting for signature
    const partialPath =
        `/transactions/partial?address=${accountBAddress}`;
    console.log(
        '[Account B] Checking for partial transactions from ' +
        '/transactions/partial'
    );
    const partialResponse = await fetch(`${NODE_URL}${partialPath}`);
    const partialTxs = await partialResponse.json();
    if (!partialTxs.data || partialTxs.data.length === 0) {
        throw new Error('No partial transactions found');
    }

    console.log(`Found ${partialTxs.data.length} partial transaction(s)`);

    // Find the transaction matching the expected hash
    const found = partialTxs.data.some(
        tx => tx.meta.hash === bondedHash
    );
    if (!found) {
        throw new Error(
            `Expected transaction ${bondedHash} not found in ` +
            `partial transactions`
        );
    }
    console.log(`Found matching transaction: ${bondedHash}`);

トランザクションペイロードがオフチェーンで共有されるアグリゲートコンプリートとは異なり、アグリゲートボンデッドはオンチェーンでの調整を可能にします。

まず、アカウント B は address パラメータを指定して /transactions/partial GET をポーリングし、自身の署名を待っているトランザクションを探します。これにより、アカウント B が関与している部分的トランザクションのリストが返されます。

この例では、両方のアカウントが同じスクリプトで動作しているため、特定のトランザクションハッシュを探しています。実際には、アカウント B はポーリングによって保留中のトランザクションを発見し、その内容に基づいてどれに連署するかを決定します。

トランザクションの検証⚓︎

    # Fetch full transaction details using the hash
    detail_path = f'/transactions/partial/{bonded_hash}'
    with urllib.request.urlopen(f'{NODE_URL}{detail_path}') as response:
        partial_tx_json = json.loads(response.read().decode())

    # Verify transaction content before cosigning
    tx_data = partial_tx_json['transaction']
    print(
        f'[Account B] Verifying transaction: '
        f'{len(tx_data["transactions"])} embedded transactions'
    )
    const detailPath = `/transactions/partial/${bondedHash}`;
    const detailResponse = await fetch(`${NODE_URL}${detailPath}`);
    const partialTxJson = await detailResponse.json();

    // Verify transaction content before cosigning
    const txData = partialTxJson.transaction;
    console.log(
        `[Account B] Verifying transaction: ` +
        `${txData.transactions.length} embedded transactions`
    );

トランザクションが見つかったら、アカウント B はそのハッシュを使用して、/transactions/partial/{transactionId} GET から詳細(埋め込みトランザクションを含む)を完全に取得します。

連署する前に、アカウント B は埋め込みトランザクションが期待される操作と一致しているか検証する必要があります。この例では、単に埋め込みトランザクションの数をログに記録していますが、実際には金額、受信者、モザイクを確認して、スワップ条件が正しいことを確認します。

連署する前に検証すること

連署する前に必ずトランザクション内容を検査してください。連署は拘束力を持ち、取り消すことはできません。

トランザクションへの連署⚓︎

    # Submit Account B's cosignature using the transaction hash
    cosignature_path = '/transactions/cosignature'
    print('[Account B] Cosigning the bonded aggregate...')
    cosignature = facade.cosign_transaction_hash(
        account_b_key_pair, bonded_hash, True)
    cosignature_payload = json.dumps({
        'version': str(cosignature.version),
        'signerPublicKey': str(cosignature.signer_public_key),
        'signature': str(cosignature.signature),
        'parentHash': str(cosignature.parent_hash)
    })

    # Announce cosignature
    announce_transaction(
        cosignature_payload, cosignature_path, 'cosignature'
    )
    const cosignaturePath = '/transactions/cosignature';
    console.log('[Account B] Cosigning the bonded aggregate...');
    const cosignature = SymbolFacade.cosignTransactionHash(
        accountBKeyPair, new Hash256(bondedHash), true
    );
    const cosignaturePayload = JSON.stringify({
        version: cosignature.version.toString(),
        signerPublicKey: cosignature.signerPublicKey.toString(),
        signature: cosignature.signature.toString(),
        parentHash: cosignature.parentHash.toString()
    });

    // Announce cosignature
    await announceTransaction(
        cosignaturePayload, cosignaturePath, 'cosignature'
    );

アカウント B は、トランザクションハッシュと detached パラメータを true に設定した を使用して、トランザクションに連署します。

デタッチド署名は、ネットワークに単独で送信できるスタンドアロンのオブジェクトです。連署者はノードに直接送信するため、ボンデッドアグリゲートではこれが必要です。

生成されたデタッチド署名のペイロードには以下が含まれます:

  • バージョン: 連署形式のバージョン。
  • 署名者の公開鍵: 誰が連署したかを識別するための、アカウント B の公開鍵。
  • 署名: トランザクションハッシュとアカウント B の秘密鍵から計算された署名。
  • 親ハッシュ: 連署対象となるボンデッドトランザクションのハッシュ。

連署ペイロードは、announce_transaction ヘルパー関数を使用して /transactions/cosignature PUT に送信されます。ネットワークは連署を検証し、それを部分的トランザクションに付加します。

すべての埋め込みトランザクションを満たすのに十分な連署が収集されると、ネットワークは自動的にボンデッドアグリゲートを処理し、ブロックに含めます。

承認の待機⚓︎

    # Wait for final confirmation
    wait_for_status(
        bonded_hash, 'confirmed',
        'Bonded aggregate transaction'
    )
    await waitForStatus(
        new Hash256(bondedHash), 'confirmed',
        'Bonded aggregate transaction'
    );
} catch (e) {

wait_for_status ヘルパー関数が、トランザクションが承認されるか失敗するまで /transactionStatus/{hash} GET をポーリングします。

期限前に必要なすべての連署が収集されれば、トランザクションは承認され、両方の転送が実行され、ハッシュロックのデポジットはアカウント A に返却されます。

期限が切れるか、連署のいずれかが無効な場合、トランザクションは失敗し、デポジットは没収されます。

出力⚓︎

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

Using node https://reference.symboltest.net:3001
Account A: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Account B: TCWYXKVYBMO4NBCUF3AXKJMXCGVSYQOS7ZG2TLI
Fetching current network time from /node/time
  Network time: 96983954275 ms since nemesis
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100
Built aggregate without signatures:
{
  "signature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 3,
  "network": 152,
  "type": 16961,
  "fee": "46400",
  "deadline": "96991154275",
  "transactions_hash": "C2F8137E86FA6CD2B5A6E0CBBEA5485DCC427C5F1C9AC98F7A35E84CF0A28877",
  "transactions": [
    {
      "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
      "version": 1,
      "network": 152,
      "type": 16724,
      "recipient_address": "98AD8BAAB80B1DC684542EC175259711AB2C41D2FE4DA9AD",
      "mosaics": [
        {
          "mosaic_id": "16666583871264174062",
          "amount": "10000000"
        }
      ],
      "message": ""
    },
    {
      "signer_public_key": "D04AB232742BB4AB3A1368BD4615E4E6D0224AB71A016BAF8520A332C9778737",
      "version": 1,
      "network": 152,
      "type": 16724,
      "recipient_address": "988E1191A25A88142C2FB3F69787576E3DC713EFC1CE4DE9",
      "mosaics": [
        {
          "mosaic_id": "7859648582932718274",
          "amount": "1"
        }
      ],
      "message": ""
    }
  ],
  "cosignatures": []
}
[Account A] Signing the bonded aggregate...
Bonded aggregate transaction hash: 59CD54BA6EFC220C9C0C8DA6C7615F2A7F23A302836A69ACE29D48569D8CBAF8
Creating hash lock transaction...
[Account A] Signing the hash lock...
Hash lock transaction hash: 8E7AB27A920195BB783F822C39E14A42E2BA965250114E619D011CFE437ACC68
Announcing Hash lock to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for Hash lock to reach confirmed status...
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
Hash lock confirmed in 24 seconds
Announcing Bonded aggregate transaction to /transactions/partial
  Response: {"message":"packet 256 was pushed to the network via /transactions/partial"}
Waiting for Bonded aggregate transaction to reach partial status...
  Transaction status: partial
Bonded aggregate transaction partial in 1 seconds
[Account B] Checking for partial transactions from /transactions/partial
Found 1 partial transaction(s)
Found matching transaction: 59CD54BA6EFC220C9C0C8DA6C7615F2A7F23A302836A69ACE29D48569D8CBAF8
[Account B] Verifying transaction: 2 embedded transactions
[Account B] Cosigning the bonded aggregate...
Announcing cosignature to /transactions/cosignature
  Response: {"message":"packet 257 was pushed to the network via /transactions/cosignature"}
Waiting for Bonded aggregate transaction to reach confirmed status...
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
Bonded aggregate transaction confirmed in 16 seconds

出力の主なポイント:

  • 14行目 ("type": 16961): これが aggregate_bonded_transaction_v3 であることを示します。
  • 18行目 ("transactions"): アトミックに実行される2つの埋め込み転送が含まれています。
  • 48行目 ("cosignatures": []): 最初は空です。署名はアナウンス後にオンチェーンで送信されます。
  • 51行目 (Bonded aggregate transaction hash:): ハッシュロックの作成とトランザクションのアナウンスに必要な、ボンデッドアグリゲートのハッシュです。
  • 54行目 (Announcing Hash lock to /transactions): ボンデッドアグリゲートの前にハッシュロックをアナウンスし、承認させる必要があります。
  • 63行目 (Announcing Bonded aggregate transaction to /transactions/partial): ボンデッドアグリゲートは通常のトランザクションとは異なるエンドポイントを使用します。
  • 67行目 (Bonded aggregate transaction partial in 1 seconds): ボンデッドアグリゲートは現在、連署がオンチェーンで送信されるのを待っています。
  • 71行目 ([Account B] Verifying transaction: 2 embedded transactions): アカウント B は連署する前にトランザクション内容を検査し、すべての操作に同意できるか確認しています。
  • 73行目 (Announcing cosignature to /transactions/cosignature): 連署がネットワークに送信されます。

アグリゲートトランザクションは、ネットワークによって単一のアトミックな単位として扱われます。スワップは完全に実行される(アカウント A がカスタムモザイクを受け取り、アカウント B が XYM を受け取る)か、トランザクション全体が失敗してアセットが一切転送されないかのどちらかとなります。

結論⚓︎

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

ステップ 関連ドキュメント
埋め込みトランザクションの作成
アグリゲートの構築
ボンデッドトランザクションの署名
ハッシュロックの作成
/transactions PUT
ボンデッドトランザクションのアナウンス /transactions/partial PUT
トランザクションの復元 /transactions/partial GET
トランザクションの検証 /transactions/partial/{transactionId} GET
トランザクションへの連署
/transactions/cosignature PUT
承認の待機 /transactionStatus/{hash} GET

次のステップ⚓︎