コンテンツにスキップ

徴収手数料付きモザイクを作成する⚓︎

上級

モザイク にはオプションの 徴収手数料 を含められます。 徴収手数料は転送ごとに送信者へ課され、指定されたアカウントへ付与される手数料です。

徴収手数料の一般的な用途は、資産の背後にあるアカウントへ資金を提供することです。例えば、すべての転送にコミッションやロイヤリティを課します。

このチュートリアルでは、徴収手数料付きモザイクを作成する方法を説明します。

前提条件⚓︎

始める前に、次の準備をしてください。

さらに、モザイク定義の構築、アナウンス、承認の方法を理解するため、モザイクを作成する チュートリアルを確認してください。

完全なコード⚓︎

import json
import os
import time
import urllib.request

from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.NemFacade import NemFacade
from symbolchain.nc import Amount
from symbolchain.nem.FeeCalculator import (
    calculate_mosaic_rental_fee,
    calculate_transaction_fee
)
from symbolchain.nem.Network import NetworkTimestamp

NODE_URL = os.getenv('NODE_URL', 'http://libertalia.nemtest.net:7890')
print(f'Using node {NODE_URL}')

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

facade = NemFacade('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', 'my_namespace')
mosaic_name = os.getenv('MOSAIC', f'token_{int(time.time())}')
mosaic_id = f'{namespace_name}:{mosaic_name}'
print(f'Creating mosaic: {mosaic_id}')

try:
    # Fetch current network time
    time_path = '/time-sync/network-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())
        network_time = response_json['receiveTimeStamp'] // 1000
        print(f'  Network time: {network_time} s since the nemesis block')

    # Derived fields from network time
    timestamp = NetworkTimestamp(network_time)
    deadline = timestamp.add_hours(2)

    # Describe the levy
    LEVY_RECIPIENT = os.getenv(
        'LEVY_RECIPIENT',
        'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4')

    levy = {
        'transfer_fee_type': 'absolute',
        'recipient_address': LEVY_RECIPIENT,
        'mosaic_id': {
            'namespace_id': {'name': 'nem'},
            'name': 'xem'
        },
        'fee': 1_000_000
    }
    levy_mosaic_id = levy['mosaic_id']
    print('Levy:')
    print(f'  Type: {levy["transfer_fee_type"]}')
    print(f'  Recipient: {levy["recipient_address"]}')
    print(f'  Mosaic: {levy_mosaic_id["namespace_id"]["name"]}:'
        f'{levy_mosaic_id["name"]}')
    print(f'  Fee: {levy["fee"]}')

    # Build the mosaic definition transaction
    rental_fee = calculate_mosaic_rental_fee()
    print(f'  Mosaic creation fee: {rental_fee / 1_000_000} XEM')

    transaction = facade.transaction_factory.create({
        'type': 'mosaic_definition_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'rental_fee_sink': 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        'rental_fee': rental_fee,
        'mosaic_definition': {
            'owner_public_key': signer_key_pair.public_key,
            'id': {
                'namespace_id': {'name': namespace_name},
                'name': mosaic_name
            },
            'description': 'My tutorial mosaic with a levy',
            'properties': [
                {'property_': {
                    'name': b'divisibility', 'value': b'2'}},
                {'property_': {
                    'name': b'initialSupply', 'value': b'1000'}},
                {'property_': {
                    'name': b'supplyMutable', 'value': b'true'}},
                {'property_': {
                    'name': b'transferable', 'value': b'true'}}
            ],
            'levy': levy
        }
    })

    # Calculate and attach the transaction fee
    fee = calculate_transaction_fee(transaction)
    transaction.fee = Amount(fee)
    print(f'  Transaction fee: {fee / 1_000_000} XEM')

    # Sign, announce and wait for confirmation
    signature = facade.sign_transaction(signer_key_pair, transaction)
    json_payload = facade.transaction_factory.attach_signature(
        transaction, signature)
    print('Built mosaic definition transaction:')
    print(json.dumps(transaction.to_json(), indent=2))

    announce_path = '/transaction/announce'
    print(f'Announcing mosaic definition to {announce_path}')
    announce_request = urllib.request.Request(
        f'{NODE_URL}{announce_path}',
        data=json_payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    with urllib.request.urlopen(announce_request) as response:
        announce_result = json.loads(response.read().decode())
    print(f'  Result: {announce_result["message"]}')

    if 'SUCCESS' == announce_result['message']:
        transaction_hash = facade.hash_transaction(transaction)
        status_path = f'/transaction/get?hash={transaction_hash}'
        print(f'Waiting for confirmation from {status_path}')
        is_confirmed = False
        for attempt in range(120):
            try:
                with urllib.request.urlopen(
                    f'{NODE_URL}{status_path}'
                ) as response:
                    confirmed = json.loads(response.read().decode())
                    height = confirmed['meta']['height']
                    print(f'Transaction confirmed in block {height}')
                    is_confirmed = True
                    break
            except urllib.error.HTTPError:
                print('  Transaction status: pending')
            time.sleep(1)
        if not is_confirmed:
            print('Confirmation took too long.')
    else:
        print(f'Transaction rejected: {announce_result["message"]}')

    # Retrieve the levy
    definition_path = f'/mosaic/definition?mosaicId={mosaic_id}'
    print(f'Fetching mosaic information from {definition_path}')
    with urllib.request.urlopen(
        f'{NODE_URL}{definition_path}'
    ) as response:
        mosaic_info = json.loads(response.read().decode())
        levy_info = mosaic_info['levy']
        levy_mosaic_id = levy_info['mosaicId']
        levy_type = 'absolute' if 1 == levy_info['type'] else 'percentile'
        print('Levy information:')
        print(f'  Type: {levy_type}')
        print(f'  Recipient: {levy_info["recipient"]}')
        print(f'  Mosaic: '
            f'{levy_mosaic_id["namespaceId"]}:{levy_mosaic_id["name"]}')
        print(f'  Fee: {levy_info["fee"]}')

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

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    NemFacade,
    NetworkTimestamp,
    calculateMosaicRentalFee,
    calculateTransactionFee,
    models
} from 'symbol-sdk/nem';

const NODE_URL = process.env.NODE_URL ||
    'http://libertalia.nemtest.net:7890';
console.log('Using node', NODE_URL);

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

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

const namespaceName = process.env.NAMESPACE || 'my_namespace';
const mosaicName = process.env.MOSAIC ||
    `token_${Math.floor(Date.now() / 1000)}`;
const mosaicId = `${namespaceName}:${mosaicName}`;
console.log('Creating mosaic:', mosaicId);

try {
    // Fetch current network time
    const timePath = '/time-sync/network-time';
    console.log('Fetching current network time from', timePath);
    const timeResponse = await fetch(`${NODE_URL}${timePath}`);
    const timeJSON = await timeResponse.json();
    const networkTime = Math.floor(timeJSON.receiveTimeStamp / 1000);
    console.log('  Network time:', networkTime,
        's since the nemesis block');

    // Derived fields from network time
    const timestamp = new NetworkTimestamp(networkTime);
    const deadline = timestamp.addHours(2);

    // Describe the levy
    const LEVY_RECIPIENT = process.env.LEVY_RECIPIENT ||
        'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4';

    const levy = {
        transferFeeType: 'absolute',
        recipientAddress: LEVY_RECIPIENT,
        mosaicId: {
            namespaceId: { name: 'nem' },
            name: 'xem'
        },
        fee: 1_000_000
    };
    console.log('Levy:');
    console.log('  Type:', levy.transferFeeType);
    console.log('  Recipient:', levy.recipientAddress);
    console.log('  Mosaic:',
        `${levy.mosaicId.namespaceId.name}:${levy.mosaicId.name}`);
    console.log('  Fee:', levy.fee);

    // Build the mosaic definition transaction
    const rentalFee = calculateMosaicRentalFee();
    console.log('  Mosaic creation fee:',
        `${Number(rentalFee) / 1_000_000} XEM`);

    const transaction = facade.transactionFactory.create({
        type: 'mosaic_definition_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        timestamp: timestamp.timestamp,
        deadline: deadline.timestamp,
        rentalFeeSink: 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        rentalFee,
        mosaicDefinition: {
            ownerPublicKey: signerKeyPair.publicKey.toString(),
            id: {
                namespaceId: { name: namespaceName },
                name: mosaicName
            },
            description: 'My tutorial mosaic with a levy',
            properties: [
                { property: { name: 'divisibility', value: '2' } },
                { property: { name: 'initialSupply', value: '1000' } },
                { property: { name: 'supplyMutable', value: 'true' } },
                { property: { name: 'transferable', value: 'true' } }
            ],
            levy
        }
    });

    // Calculate and attach the transaction fee
    const fee = calculateTransactionFee(transaction);
    transaction.fee = new models.Amount(fee);
    console.log('  Transaction fee:', `${Number(fee) / 1_000_000} XEM`);

    // Sign, announce and wait for confirmation
    const signature = facade.signTransaction(signerKeyPair, transaction);
    const jsonPayload = facade.transactionFactory.static.attachSignature(
        transaction, signature);
    console.log('Built mosaic definition transaction:');
    console.dir(transaction.toJson(), { colors: true });

    const announcePath = '/transaction/announce';
    console.log('Announcing mosaic definition to', announcePath);
    const announceResponse = await fetch(`${NODE_URL}${announcePath}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    const announceResult = await announceResponse.json();
    console.log('  Result:', announceResult.message);

    if ('SUCCESS' === announceResult.message) {
        const transactionHash = facade.hashTransaction(transaction)
            .toString();
        const statusPath = `/transaction/get?hash=${transactionHash}`;
        console.log('Waiting for confirmation from', statusPath);

        let isConfirmed = false;
        for (let attempt = 1; 120 >= attempt; ++attempt) {
            const response = await fetch(`${NODE_URL}${statusPath}`);

            if (response.ok) {
                const confirmed = await response.json();
                console.log('Transaction confirmed in block',
                    confirmed.meta.height);
                isConfirmed = true;
                break;
            }
            console.log('  Transaction status: pending');
            await new Promise(resolve => { setTimeout(resolve, 1000); });
        }
        if (!isConfirmed)
            console.warn('Confirmation took too long.');
    } else {
        console.log('Transaction rejected:', announceResult.message);
    }

    // Retrieve the levy
    const definitionPath = `/mosaic/definition?mosaicId=${mosaicId}`;
    console.log('Fetching mosaic information from', definitionPath);
    const definitionResponse = await fetch(
        `${NODE_URL}${definitionPath}`);
    const mosaicInfo = await definitionResponse.json();
    const levyInfo = mosaicInfo.levy;
    const levyMosaicId = levyInfo.mosaicId;
    const levyType = 1 === levyInfo.type ? 'absolute' : 'percentile';
    console.log('Levy information:');
    console.log('  Type:', levyType);
    console.log('  Recipient:', levyInfo.recipient);
    console.log('  Mosaic:',
        `${levyMosaicId.namespaceId}:${levyMosaicId.name}`);
    console.log('  Fee:', levyInfo.fee);

} catch (e) {
    console.error(e.message, '| Cause:', e.cause?.code ?? 'unknown');
}

Download source

コードの説明⚓︎

アカウントとモザイクをセットアップする⚓︎

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

facade = NemFacade('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', 'my_namespace')
mosaic_name = os.getenv('MOSAIC', f'token_{int(time.time())}')
mosaic_id = f'{namespace_name}:{mosaic_name}'
print(f'Creating mosaic: {mosaic_id}')
const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const signerKeyPair = new NemFacade.KeyPair(
    new PrivateKey(SIGNER_PRIVATE_KEY));

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

const namespaceName = process.env.NAMESPACE || 'my_namespace';
const mosaicName = process.env.MOSAIC ||
    `token_${Math.floor(Date.now() / 1000)}`;
const mosaicId = `${namespaceName}:${mosaicName}`;
console.log('Creating mosaic:', mosaicId);

スニペットは、署名者の秘密鍵を SIGNER_PRIVATE_KEY 環境変数から読み込みます。 設定されていない場合は、テスト用のデフォルトキーを使用します。 このアカウントがトランザクションに署名してモザイクの所有者になるため、モザイクを置くネームスペースも所有していなければなりません。 モザイク識別子は、そのネームスペースとモザイク名から組み立てます。 命名規則については、テキストブックの 名前 を参照してください。

チュートリアルを複数回実行したときの衝突を避けるため、モザイク名にタイムスタンプを追加します。 ただし、実際のプログラムでは、モザイクに固定名を使用します。 NAMESPACEMOSAIC 環境変数を使うと、チュートリアルで固定名を使用できます。

署名者が所有するネームスペースを使用してください

デフォルトでは、コードは SIGNER_PRIVATE_KEY が参照するテストアカウントと、my_namespace という名前のネームスペースを使用します。

ルートネームスペースを登録する チュートリアルから続けている場合は、SIGNER_PRIVATE_KEYNAMESPACE 環境変数を、そこで作成したアカウントとネームスペース、または署名者が所有する別のネームスペースに合わせて設定してください。

    # Fetch current network time
    time_path = '/time-sync/network-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())
        network_time = response_json['receiveTimeStamp'] // 1000
        print(f'  Network time: {network_time} s since the nemesis block')

    # Derived fields from network time
    timestamp = NetworkTimestamp(network_time)
    deadline = timestamp.add_hours(2)
    // Fetch current network time
    const timePath = '/time-sync/network-time';
    console.log('Fetching current network time from', timePath);
    const timeResponse = await fetch(`${NODE_URL}${timePath}`);
    const timeJSON = await timeResponse.json();
    const networkTime = Math.floor(timeJSON.receiveTimeStamp / 1000);
    console.log('  Network time:', networkTime,
        's since the nemesis block');

    // Derived fields from network time
    const timestamp = new NetworkTimestamp(networkTime);
    const deadline = timestamp.addHours(2);

ネットワーク時刻は /time-sync/network-time GET から取得し、XEM を送信する チュートリアルで説明されている手順に従って、トランザクションの timestampdeadline フィールドを導出します。

徴収手数料を記述する⚓︎

    # Describe the levy
    LEVY_RECIPIENT = os.getenv(
        'LEVY_RECIPIENT',
        'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4')

    levy = {
        'transfer_fee_type': 'absolute',
        'recipient_address': LEVY_RECIPIENT,
        'mosaic_id': {
            'namespace_id': {'name': 'nem'},
            'name': 'xem'
        },
        'fee': 1_000_000
    }
    levy_mosaic_id = levy['mosaic_id']
    print('Levy:')
    print(f'  Type: {levy["transfer_fee_type"]}')
    print(f'  Recipient: {levy["recipient_address"]}')
    print(f'  Mosaic: {levy_mosaic_id["namespace_id"]["name"]}:'
        f'{levy_mosaic_id["name"]}')
    print(f'  Fee: {levy["fee"]}')
    // Describe the levy
    const LEVY_RECIPIENT = process.env.LEVY_RECIPIENT ||
        'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4';

    const levy = {
        transferFeeType: 'absolute',
        recipientAddress: LEVY_RECIPIENT,
        mosaicId: {
            namespaceId: { name: 'nem' },
            name: 'xem'
        },
        fee: 1_000_000
    };
    console.log('Levy:');
    console.log('  Type:', levy.transferFeeType);
    console.log('  Recipient:', levy.recipientAddress);
    console.log('  Mosaic:',
        `${levy.mosaicId.namespaceId.name}:${levy.mosaicId.name}`);
    console.log('  Fee:', levy.fee);

徴収手数料は、次の 4 つのフィールドを持つ MosaicLevy 構造体です。

  • : 徴収手数料の金額を計算する方法。

    • absolute: 転送量に関係なく、すべての転送に課す固定量。
    • percentile: 転送量に比例する量。

    このチュートリアルでは absolute の徴収手数料を使うため、すべての転送に同じ金額が課されます。

  • : 転送ごとに徴収手数料を受け取るアカウント。 モザイクの作成者でも、別のアカウントでも構いません。

  • : 徴収手数料を支払うモザイク。 このチュートリアルでは nem:xem で徴収手数料を課すため、送信者はネットワーク通貨で支払います。

    定義するモザイク自身で徴収手数料を支払うこともできます。 その他の徴収手数料用モザイクはネットワーク上にすでに存在し、転送可能 でなければなりません。

  • : 徴収手数料の金額。 absolute の徴収手数料では、徴収手数料用モザイクの 原子単位 で表します。 nem:xem可分性 は 6 なので、1'000'000 という値は転送ごとに 1 XEM を課します。

    percentile の徴収手数料では、手数料は代わりにベーシスポイントで解釈されます。fee100 なら、転送量の 1% が課されます。 完全なルールについては、テキストブックの パーセンタイル徴収手数料の計算 を参照してください。

モザイク定義に徴収手数料を付加する⚓︎

    # Build the mosaic definition transaction
    rental_fee = calculate_mosaic_rental_fee()
    print(f'  Mosaic creation fee: {rental_fee / 1_000_000} XEM')

    transaction = facade.transaction_factory.create({
        'type': 'mosaic_definition_transaction_v1',
        'signer_public_key': signer_key_pair.public_key,
        'timestamp': timestamp.timestamp,
        'deadline': deadline.timestamp,
        'rental_fee_sink': 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        'rental_fee': rental_fee,
        'mosaic_definition': {
            'owner_public_key': signer_key_pair.public_key,
            'id': {
                'namespace_id': {'name': namespace_name},
                'name': mosaic_name
            },
            'description': 'My tutorial mosaic with a levy',
            'properties': [
                {'property_': {
                    'name': b'divisibility', 'value': b'2'}},
                {'property_': {
                    'name': b'initialSupply', 'value': b'1000'}},
                {'property_': {
                    'name': b'supplyMutable', 'value': b'true'}},
                {'property_': {
                    'name': b'transferable', 'value': b'true'}}
            ],
            'levy': levy
        }
    })

    # Calculate and attach the transaction fee
    fee = calculate_transaction_fee(transaction)
    transaction.fee = Amount(fee)
    print(f'  Transaction fee: {fee / 1_000_000} XEM')
    // Build the mosaic definition transaction
    const rentalFee = calculateMosaicRentalFee();
    console.log('  Mosaic creation fee:',
        `${Number(rentalFee) / 1_000_000} XEM`);

    const transaction = facade.transactionFactory.create({
        type: 'mosaic_definition_transaction_v1',
        signerPublicKey: signerKeyPair.publicKey.toString(),
        timestamp: timestamp.timestamp,
        deadline: deadline.timestamp,
        rentalFeeSink: 'TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC',
        rentalFee,
        mosaicDefinition: {
            ownerPublicKey: signerKeyPair.publicKey.toString(),
            id: {
                namespaceId: { name: namespaceName },
                name: mosaicName
            },
            description: 'My tutorial mosaic with a levy',
            properties: [
                { property: { name: 'divisibility', value: '2' } },
                { property: { name: 'initialSupply', value: '1000' } },
                { property: { name: 'supplyMutable', value: 'true' } },
                { property: { name: 'transferable', value: 'true' } }
            ],
            levy
        }
    });

    // Calculate and attach the transaction fee
    const fee = calculateTransactionFee(transaction);
    transaction.fee = new models.Amount(fee);
    console.log('  Transaction fee:', `${Number(fee) / 1_000_000} XEM`);

徴収手数料はモザイク定義の一部なので、モザイクを作成する で使う MosaicDefinitionTransactionV1 と同じものを使って設定します。 このチュートリアルでは同じトランザクションを再利用し、 フィールドに徴収手数料を追加します。

作成手数料 は 10 XEM、トランザクション手数料は固定の 0.15 XEM で、手数料表 に示されています。

モザイク定義を送信する⚓︎

    # Sign, announce and wait for confirmation
    signature = facade.sign_transaction(signer_key_pair, transaction)
    json_payload = facade.transaction_factory.attach_signature(
        transaction, signature)
    print('Built mosaic definition transaction:')
    print(json.dumps(transaction.to_json(), indent=2))

    announce_path = '/transaction/announce'
    print(f'Announcing mosaic definition to {announce_path}')
    announce_request = urllib.request.Request(
        f'{NODE_URL}{announce_path}',
        data=json_payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    with urllib.request.urlopen(announce_request) as response:
        announce_result = json.loads(response.read().decode())
    print(f'  Result: {announce_result["message"]}')

    if 'SUCCESS' == announce_result['message']:
        transaction_hash = facade.hash_transaction(transaction)
        status_path = f'/transaction/get?hash={transaction_hash}'
        print(f'Waiting for confirmation from {status_path}')
        is_confirmed = False
        for attempt in range(120):
            try:
                with urllib.request.urlopen(
                    f'{NODE_URL}{status_path}'
                ) as response:
                    confirmed = json.loads(response.read().decode())
                    height = confirmed['meta']['height']
                    print(f'Transaction confirmed in block {height}')
                    is_confirmed = True
                    break
            except urllib.error.HTTPError:
                print('  Transaction status: pending')
            time.sleep(1)
        if not is_confirmed:
            print('Confirmation took too long.')
    else:
        print(f'Transaction rejected: {announce_result["message"]}')
    // Sign, announce and wait for confirmation
    const signature = facade.signTransaction(signerKeyPair, transaction);
    const jsonPayload = facade.transactionFactory.static.attachSignature(
        transaction, signature);
    console.log('Built mosaic definition transaction:');
    console.dir(transaction.toJson(), { colors: true });

    const announcePath = '/transaction/announce';
    console.log('Announcing mosaic definition to', announcePath);
    const announceResponse = await fetch(`${NODE_URL}${announcePath}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: jsonPayload
    });
    const announceResult = await announceResponse.json();
    console.log('  Result:', announceResult.message);

    if ('SUCCESS' === announceResult.message) {
        const transactionHash = facade.hashTransaction(transaction)
            .toString();
        const statusPath = `/transaction/get?hash=${transactionHash}`;
        console.log('Waiting for confirmation from', statusPath);

        let isConfirmed = false;
        for (let attempt = 1; 120 >= attempt; ++attempt) {
            const response = await fetch(`${NODE_URL}${statusPath}`);

            if (response.ok) {
                const confirmed = await response.json();
                console.log('Transaction confirmed in block',
                    confirmed.meta.height);
                isConfirmed = true;
                break;
            }
            console.log('  Transaction status: pending');
            await new Promise(resolve => { setTimeout(resolve, 1000); });
        }
        if (!isConfirmed)
            console.warn('Confirmation took too long.');
    } else {
        console.log('Transaction rejected:', announceResult.message);
    }

XEM を送信する チュートリアルと同じ手順で、トランザクションに署名し、アナウンスして、承認を待ちます。

徴収手数料を検証する⚓︎

    # Retrieve the levy
    definition_path = f'/mosaic/definition?mosaicId={mosaic_id}'
    print(f'Fetching mosaic information from {definition_path}')
    with urllib.request.urlopen(
        f'{NODE_URL}{definition_path}'
    ) as response:
        mosaic_info = json.loads(response.read().decode())
        levy_info = mosaic_info['levy']
        levy_mosaic_id = levy_info['mosaicId']
        levy_type = 'absolute' if 1 == levy_info['type'] else 'percentile'
        print('Levy information:')
        print(f'  Type: {levy_type}')
        print(f'  Recipient: {levy_info["recipient"]}')
        print(f'  Mosaic: '
            f'{levy_mosaic_id["namespaceId"]}:{levy_mosaic_id["name"]}')
        print(f'  Fee: {levy_info["fee"]}')
    // Retrieve the levy
    const definitionPath = `/mosaic/definition?mosaicId=${mosaicId}`;
    console.log('Fetching mosaic information from', definitionPath);
    const definitionResponse = await fetch(
        `${NODE_URL}${definitionPath}`);
    const mosaicInfo = await definitionResponse.json();
    const levyInfo = mosaicInfo.levy;
    const levyMosaicId = levyInfo.mosaicId;
    const levyType = 1 === levyInfo.type ? 'absolute' : 'percentile';
    console.log('Levy information:');
    console.log('  Type:', levyType);
    console.log('  Recipient:', levyInfo.recipient);
    console.log('  Mosaic:',
        `${levyMosaicId.namespaceId}:${levyMosaicId.name}`);
    console.log('  Fee:', levyInfo.fee);

徴収手数料付きモザイクが作成されたことを確認するため、コードは /mosaic/definition GET エンドポイントからモザイク定義を取得します。レスポンスにはモザイクプロパティとともに徴収手数料が返されます。

レスポンスに徴収手数料が含まれていれば、今後のモザイク転送に徴収手数料が課されることを確認できます。

徴収手数料が課される仕組み⚓︎

モザイクが作成された後、徴収手数料はモザイクのすべての 転送 に適用され、転送トランザクションに追加のフィールドは必要ありません。

徴収手数料は保証された徴収ではありません

徴収手数料は再帰的ではないため、回避できます。 テキストブックの を参照してください。

徴収手数料は転送量に上乗せして課されます。そのため、このチュートリアルで作成したモザイクを 50 単位転送する送信者からは、次の金額が引き落とされます。

  • モザイク 50 単位。転送の受取人に付与されます。
  • 1 XEM。徴収手数料の受取人に付与されます。
  • トランザクション手数料。ハーベスターアカウント に付与されます。

送信者が転送量と徴収手数料の両方を支払えない場合、ネットワークは転送を拒否します。 この徴収手数料は nem:xem で支払うため、送信者は徴収手数料とトランザクション手数料の両方をカバーする十分な XEM も保有しなければなりません。 徴収手数料を別のモザイクで支払う場合、送信者はそのモザイクも十分な残高を保有しなければなりません。

出力⚓︎

以下は、プログラムの実行時の出力例です。

Using node http://libertalia.nemtest.net:7890
Signer address: TBONKWCOWBZYZB2I5JD3LSDBQVBYHB757VN3SKPP
Creating mosaic: my_namespace:token_1784625719
Fetching current network time from /time-sync/network-time
  Network time: 357038135 s since the nemesis block
Levy:
  Type: absolute
  Recipient: TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4
  Mosaic: nem:xem
  Fee: 1000000
  Mosaic creation fee: 10.0 XEM
  Transaction fee: 0.15 XEM
Built mosaic definition transaction:
{
  "type": 16385,
  "version": 1,
  "network": 152,
  "timestamp": 357038135,
  "signer_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
  "signature": "3B240EE7E5B6EDEEDA676ECA2D54039E45089CEE7DE2958350F9A63E168F6DFF0B8380B2B0917C4BDE662E6531C3151EFB999DA3E0330B5C0A6895CD04A40808",
  "fee": "150000",
  "deadline": 357045335,
  "mosaic_definition": {
    "owner_public_key": "462EE976890916E54FA825D26BDD0235F5EB5B6A143C199AB0AE5EE9328E08CE",
    "id": {
      "namespace_id": {
        "name": "6d795f6e616d657370616365"
      },
      "name": "746f6b656e5f31373834363235373139"
    },
    "description": "4d79207475746f7269616c206d6f7361696320776974682061206c657679",
    "properties": [
      {
        "property": {
          "name": "64697669736962696c697479",
          "value": "32"
        }
      },
      {
        "property": {
          "name": "696e697469616c537570706c79",
          "value": "31303030"
        }
      },
      {
        "property": {
          "name": "737570706c794d757461626c65",
          "value": "74727565"
        }
      },
      {
        "property": {
          "name": "7472616e7366657261626c65",
          "value": "74727565"
        }
      }
    ],
    "levy": {
      "transfer_fee_type": 1,
      "recipient_address": "5442554C4541554732435A51495355523434324857413655414B47574958484441424A5649505334",
      "mosaic_id": {
        "namespace_id": {
          "name": "6e656d"
        },
        "name": "78656d"
      },
      "fee": "1000000"
    }
  },
  "rental_fee_sink": "54424D4F534149434F443446353445453543444D523233434342474F414D3258534A4252354F4C43",
  "rental_fee": "10000000"
}
Announcing mosaic definition to /transaction/announce
  Result: SUCCESS
Waiting for confirmation from /transaction/get?hash=190340818E859F24FD570CD5D54153DD3E5E192F2026F23E079522AC9BA56456
  Transaction status: pending
  Transaction status: pending
  Transaction status: pending
Transaction confirmed in block 710705
Fetching mosaic information from /mosaic/definition?mosaicId=my_namespace:token_1784625719
Levy information:
  Type: absolute
  Recipient: TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4
  Mosaic: nem:xem
  Fee: 1000000

出力の要点は次のとおりです。

  • モザイク ID(3 行目): モザイクは、ネームスペース my_namespace とタイムスタンプ付きモザイク名を組み合わせた完全修飾名で識別されます。 NEM テストネットエクスプローラー でこの名前を検索すると、モザイクの詳細を確認できます。

  • 徴収手数料のフィールド(6~10 行目): 作成する徴収手数料。nem:xem の原子単位 1'000'000(1 XEM)の absolute 手数料で、転送ごとに徴収手数料の受取人へ支払われます。

  • トランザクション内の徴収手数料(58~68 行目): 徴収手数料はモザイク定義の内部で定義されます。受取人アドレス、徴収手数料のモザイク名、モザイク名はペイロード内で 16 進数にエンコードされ、この absolute 徴収手数料は原子単位で表されます。

  • 検証された徴収手数料(82~85 行目): ネットワークからモザイクを取得し、徴収手数料のタイプ、受取人、支払いに使うモザイク、その金額を確認します。

まとめ⚓︎

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

手順 関連ドキュメント
徴収手数料を記述する MosaicLevy
モザイクに徴収手数料を付加する MosaicDefinitionTransactionV1
徴収手数料を検証する /mosaic/definition GET

次のステップ⚓︎

徴収手数料付きモザイクを作成できたので、次のことを行えます。