コンテンツにスキップ

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

上級

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

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

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

前提条件⚓︎

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

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

完全なコード⚓︎

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

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
)

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


# Helper function to announce a transaction
def announce_transaction(payload, label):
    announce_path = '/transaction/announce'
    print(f'Announcing {label} to {announce_path}')
    request = urllib.request.Request(
        f'{NODE_URL}{announce_path}',
        data=payload.encode(),
        headers={'Content-Type': 'application/json'},
        method='POST'
    )
    with urllib.request.urlopen(request) as announce_response:
        result = json.loads(announce_response.read().decode())
    print(f'  Result: {result["message"]}')
    return result['message']


# Helper function to wait for transaction confirmation
def wait_for_confirmation(tx_hash, label):
    status_path = f'/transaction/get?hash={tx_hash}'
    print(f'Waiting for {label} confirmation from {status_path}')
    is_confirmed = False
    for _ in range(120):
        try:
            with urllib.request.urlopen(
                f'{NODE_URL}{status_path}'
            ) as status_response:
                confirmed = json.loads(status_response.read().decode())
                height = confirmed['meta']['height']
                print(f'{label} confirmed in block {height}')
                is_confirmed = True
                break
        except urllib.error.HTTPError:
            print('  Transaction status: pending')
        time.sleep(1)
    if not is_confirmed:
        print(f'{label} confirmation took too long.')



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:
    # 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.create_transaction_from_descriptor({
        'type': 'mosaic_definition_transaction_v1',
        '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
        }
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)

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

    transaction_hash = facade.hash_transaction(transaction)
    print(f'Transaction hash: {transaction_hash}')
    announce_result = announce_transaction(
        json_payload, 'mosaic definition')
    if 'SUCCESS' == announce_result:
        wait_for_confirmation(transaction_hash, 'mosaic definition')
    else:
        print(f'Transaction rejected: {announce_result}')

    # 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 Exception as error:
    print(error)

Download source

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

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

// Helper function to announce a transaction
async function announceTransaction(payload, label) {
    const announcePath = '/transaction/announce';
    console.log(`Announcing ${label} to ${announcePath}`);
    const response = await fetch(`${NODE_URL}${announcePath}`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: payload
    });
    if (!response.ok)
        throw new Error(`HTTP ${response.status}`);
    const result = await response.json();
    console.log('  Result:', result.message);
    return result.message;
}

// Helper function to wait for transaction confirmation
async function waitForConfirmation(transactionHash, label) {
    const statusPath = `/transaction/get?hash=${transactionHash}`;
    console.log(`Waiting for ${label} confirmation from`, statusPath);
    let isConfirmed = false;
    for (let attempt = 1; 120 >= attempt; ++attempt) {
        const response = await fetch(`${NODE_URL}${statusPath}`);
        if (!response.ok) {
            console.log('  Transaction status: pending');
            await new Promise(resolve => { setTimeout(resolve, 1000); });
        } else {
            const confirmed = await response.json();
            console.log(`${label} confirmed in block`,
                confirmed.meta.height);
            isConfirmed = true;
            break;
        }
    }
    if (!isConfirmed)
        console.warn(`${label} confirmation took too long.`);
}


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 {
    // Describe the levy
    const LEVY_RECIPIENT = process.env.LEVY_RECIPIENT ||
        'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4';
    const levyFee = 1_000_000;
    const levy = new descriptors.MosaicLevyDescriptor(
        models.MosaicTransferFeeType.ABSOLUTE,
        new Address(LEVY_RECIPIENT),
        new descriptors.MosaicIdDescriptor(
            new descriptors.NamespaceIdDescriptor('nem'), 'xem'),
        new models.Amount(BigInt(levyFee)));
    console.log('Levy:');
    console.log('  Type: absolute');
    console.log('  Recipient:', LEVY_RECIPIENT);
    console.log('  Mosaic: nem:xem');
    console.log('  Fee:', levyFee);

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

    const transaction = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicDefinitionTransactionV1Descriptor(
            new descriptors.MosaicDefinitionDescriptor(
                signerKeyPair.publicKey,
                new descriptors.MosaicIdDescriptor(
                    new descriptors.NamespaceIdDescriptor(namespaceName),
                    mosaicName),
                'My tutorial mosaic with a levy',
                [
                    ['divisibility', '2'],
                    ['initialSupply', '1000'],
                    ['supplyMutable', 'true'],
                    ['transferable', 'true']
                ].map(([name, value]) =>
                    new descriptors.SizePrefixedMosaicPropertyDescriptor(
                        new descriptors.MosaicPropertyDescriptor(
                            name, value))),
                levy),
            new Address('TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC'),
            new models.Amount(rentalFee)),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);

    // 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 transactionHash = facade.hashTransaction(transaction).toString();
    console.log('Transaction hash:', transactionHash);
    const announceResult = await announceTransaction(
        jsonPayload, 'mosaic definition');
    if ('SUCCESS' === announceResult)
        await waitForConfirmation(transactionHash, 'mosaic definition');
    else
        console.log('Transaction rejected:', announceResult);

    // 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

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

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.List;
import java.util.Map;

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

import org.symbol.sdk.CryptoTypes;
import org.symbol.sdk.facade.NemFacade;
import org.symbol.sdk.nem.Address;
import org.symbol.sdk.nem.FeeCalculator;
import org.symbol.sdk.nem.KeyPair;
import org.symbol.sdk.nem.NemTransactionFactory;
import org.symbol.sdk.nem.descriptors.*;
import org.symbol.sdk.nem.models.Amount;
import org.symbol.sdk.nem.models.MosaicTransferFeeType;
import org.symbol.sdk.nem.models.Transaction;

public final class MosaicLevy {
    private static final ObjectMapper JSON_MAPPER = new ObjectMapper();

    private static final HttpClient HTTP_CLIENT =
        HttpClient.newHttpClient();

    private final String nodeUrl = System.getenv().getOrDefault(
        "NODE_URL", "http://libertalia.nemtest.net:7890");

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

    private String announceTransaction(
        final String payload,
        final String label
    ) throws IOException, InterruptedException {
        final String announcePath = "/transaction/announce";
        System.out.printf("Announcing %s to %s%n", label, announcePath);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + announcePath))
            .header("Content-Type", "application/json")
            .POST(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        if (2 != response.statusCode() / 100)
            throw new IOException("HTTP " + response.statusCode());
        final String result = JSON_MAPPER.readTree(response.body())
            .get("message").asText();
        System.out.printf("  Result: %s%n", result);
        return result;
    }

    private void waitForConfirmation(
        final String transactionHash,
        final String label
    ) throws IOException, InterruptedException {
        final String statusPath = String.format(
            "/transaction/get?hash=%s", transactionHash);
        System.out.printf("Waiting for %s confirmation from %s%n",
            label, statusPath);
        boolean isConfirmed = false;
        for (int attempt = 1; 120 >= attempt; ++attempt) {
            final HttpRequest request = HttpRequest.newBuilder(
                URI.create(nodeUrl + statusPath)).GET().build();
            final HttpResponse<String> response = HTTP_CLIENT.send(
                request, BodyHandlers.ofString());
            if (2 != response.statusCode() / 100) {
                System.out.println("  Transaction status: pending");
                Thread.sleep(1000);
            } else {
                final JsonNode confirmed = JSON_MAPPER.readTree(
                    response.body());
                System.out.printf("%s confirmed in block %s%n", label,
                    confirmed.get("meta").get("height").asText());
                isConfirmed = true;
                break;
            }
        }
        if (!isConfirmed)
            System.out.printf("%s confirmation took too long.%n", label);
    }

    public static void main(final String[] args) {
        try {
            new MosaicLevy().run();
        } catch (final Exception ex) {
            System.out.println(null == ex.getMessage()
                ? ex.toString()
                : ex.getMessage());
        }
    }

    private void run() throws IOException, InterruptedException {
        System.out.printf("Using node %s%n", nodeUrl);


        final String privateKey = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKey));
        System.out.printf("Signer address: %s%n",
            facade.network.publicKeyToAddress(
                signerKeyPair.getPublicKey()));
        final String namespaceName = System.getenv().getOrDefault(
            "NAMESPACE", "my_namespace");
        final String mosaicName = System.getenv().getOrDefault(
            "MOSAIC", "token_" + System.currentTimeMillis() / 1000);
        final String mosaicId = namespaceName + ":" + mosaicName;
        System.out.printf("Creating mosaic: %s%n", mosaicId);

        // Describe the levy
        final String levyRecipient = System.getenv().getOrDefault(
            "LEVY_RECIPIENT",
            "TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4");
        final long levyFee = 1_000_000;
        final MosaicLevyDescriptor levy = new MosaicLevyDescriptor(
            MosaicTransferFeeType.ABSOLUTE,
            new Address(levyRecipient),
            new MosaicIdDescriptor(
                new NamespaceIdDescriptor("nem".getBytes(UTF_8)),
                "xem".getBytes(UTF_8)),
            new Amount(levyFee));
        System.out.println("Levy:");
        System.out.println("  Type: absolute");
        System.out.printf("  Recipient: %s%n", levyRecipient);
        System.out.println("  Mosaic: nem:xem");
        System.out.printf("  Fee: %d%n", levyFee);

        // Build the mosaic definition transaction
        final MosaicDefinitionDescriptor mosaicDefinition =
            new MosaicDefinitionDescriptor(
                signerKeyPair.getPublicKey(),
                new MosaicIdDescriptor(
                    new NamespaceIdDescriptor(
                        namespaceName.getBytes(UTF_8)),
                    mosaicName.getBytes(UTF_8)),
                "My tutorial mosaic with a levy".getBytes(UTF_8),
                List.of(
                    Map.entry("divisibility", "2"),
                    Map.entry("initialSupply", "1000"),
                    Map.entry("supplyMutable", "true"),
                    Map.entry("transferable", "true"))
                    .stream()
                    .map(property ->
                        new SizePrefixedMosaicPropertyDescriptor(
                            new MosaicPropertyDescriptor(
                                property.getKey().getBytes(UTF_8),
                                property.getValue().getBytes(UTF_8))))
                    .toList(),
                levy);
        final long rentalFee = FeeCalculator.calculateMosaicRentalFee();
        System.out.printf("  Mosaic creation fee: %s XEM%n",
            (double) rentalFee / 1_000_000);
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new MosaicDefinitionTransactionV1Descriptor(
                    mosaicDefinition,
                    new Address(
                        "TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC"),
                    new Amount(rentalFee)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);

        // Calculate and attach the transaction fee
        final long fee = FeeCalculator.calculateTransactionFee(
            transaction);
        transaction.setFee(new Amount(fee));
        System.out.printf("  Transaction fee: %s XEM%n",
            (double) fee / 1_000_000);

        // Sign, announce and wait for confirmation
        final CryptoTypes.Signature signature = facade.signTransaction(
            signerKeyPair, transaction);
        final String payload = NemTransactionFactory.attachSignature(
            transaction, signature);
        System.out.println("Built mosaic definition transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(transaction.toJson()));
        final String transactionHash = facade.hashTransaction(transaction)
            .toString();
        System.out.printf("Transaction hash: %s%n", transactionHash);
        final String result = announceTransaction(
            payload, "mosaic definition");
        if ("SUCCESS".equals(result)) {
            waitForConfirmation(
                transactionHash, "mosaic definition");
        } else {
            System.out.printf("Transaction rejected: %s%n", result);
        }

        // Retrieve the levy
        final String path = "/mosaic/definition?mosaicId=" + mosaicId;
        System.out.printf("Fetching mosaic information from %s%n", path);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        final JsonNode info = JSON_MAPPER.readTree(response.body());
        final JsonNode levyInfo = info.get("levy");
        final JsonNode levyMosaicId = levyInfo.get("mosaicId");
        System.out.println("Levy information:");
        System.out.printf("  Type: %s%n",
            1 == levyInfo.get("type").asInt()
                ? "absolute" : "percentile");
        System.out.printf("  Recipient: %s%n",
            levyInfo.get("recipient").asText());
        System.out.printf("  Mosaic: %s:%s%n",
            levyMosaicId.get("namespaceId").asText(),
            levyMosaicId.get("name").asText());
        System.out.printf("  Fee: %s%n", levyInfo.get("fee").asText());

    }
}

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);
        final String privateKey = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKey));
        System.out.printf("Signer address: %s%n",
            facade.network.publicKeyToAddress(
                signerKeyPair.getPublicKey()));
        final String namespaceName = System.getenv().getOrDefault(
            "NAMESPACE", "my_namespace");
        final String mosaicName = System.getenv().getOrDefault(
            "MOSAIC", "token_" + System.currentTimeMillis() / 1000);
        final String mosaicId = namespaceName + ":" + mosaicName;
        System.out.printf("Creating mosaic: %s%n", mosaicId);

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

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

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

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

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

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

    # 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 levyFee = 1_000_000;
    const levy = new descriptors.MosaicLevyDescriptor(
        models.MosaicTransferFeeType.ABSOLUTE,
        new Address(LEVY_RECIPIENT),
        new descriptors.MosaicIdDescriptor(
            new descriptors.NamespaceIdDescriptor('nem'), 'xem'),
        new models.Amount(BigInt(levyFee)));
    console.log('Levy:');
    console.log('  Type: absolute');
    console.log('  Recipient:', LEVY_RECIPIENT);
    console.log('  Mosaic: nem:xem');
    console.log('  Fee:', levyFee);
        // Describe the levy
        final String levyRecipient = System.getenv().getOrDefault(
            "LEVY_RECIPIENT",
            "TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4");
        final long levyFee = 1_000_000;
        final MosaicLevyDescriptor levy = new MosaicLevyDescriptor(
            MosaicTransferFeeType.ABSOLUTE,
            new Address(levyRecipient),
            new MosaicIdDescriptor(
                new NamespaceIdDescriptor("nem".getBytes(UTF_8)),
                "xem".getBytes(UTF_8)),
            new Amount(levyFee));
        System.out.println("Levy:");
        System.out.println("  Type: absolute");
        System.out.printf("  Recipient: %s%n", levyRecipient);
        System.out.println("  Mosaic: nem:xem");
        System.out.printf("  Fee: %d%n", levyFee);

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

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

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

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

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

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

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

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

    percentile の徴収手数料では、手数料は代わりにベーシスポイントで解釈されます。fee が 100 なら、転送量の 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.create_transaction_from_descriptor({
        'type': 'mosaic_definition_transaction_v1',
        '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
        }
    }, signer_key_pair.public_key, 0, 2 * 60 * 60)

    # 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.createTransactionFromTypedDescriptor(
        new descriptors.MosaicDefinitionTransactionV1Descriptor(
            new descriptors.MosaicDefinitionDescriptor(
                signerKeyPair.publicKey,
                new descriptors.MosaicIdDescriptor(
                    new descriptors.NamespaceIdDescriptor(namespaceName),
                    mosaicName),
                'My tutorial mosaic with a levy',
                [
                    ['divisibility', '2'],
                    ['initialSupply', '1000'],
                    ['supplyMutable', 'true'],
                    ['transferable', 'true']
                ].map(([name, value]) =>
                    new descriptors.SizePrefixedMosaicPropertyDescriptor(
                        new descriptors.MosaicPropertyDescriptor(
                            name, value))),
                levy),
            new Address('TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC'),
            new models.Amount(rentalFee)),
        signerKeyPair.publicKey,
        0n,
        2 * 60 * 60);

    // 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`);
        // Build the mosaic definition transaction
        final MosaicDefinitionDescriptor mosaicDefinition =
            new MosaicDefinitionDescriptor(
                signerKeyPair.getPublicKey(),
                new MosaicIdDescriptor(
                    new NamespaceIdDescriptor(
                        namespaceName.getBytes(UTF_8)),
                    mosaicName.getBytes(UTF_8)),
                "My tutorial mosaic with a levy".getBytes(UTF_8),
                List.of(
                    Map.entry("divisibility", "2"),
                    Map.entry("initialSupply", "1000"),
                    Map.entry("supplyMutable", "true"),
                    Map.entry("transferable", "true"))
                    .stream()
                    .map(property ->
                        new SizePrefixedMosaicPropertyDescriptor(
                            new MosaicPropertyDescriptor(
                                property.getKey().getBytes(UTF_8),
                                property.getValue().getBytes(UTF_8))))
                    .toList(),
                levy);
        final long rentalFee = FeeCalculator.calculateMosaicRentalFee();
        System.out.printf("  Mosaic creation fee: %s XEM%n",
            (double) rentalFee / 1_000_000);
        final Transaction transaction =
            facade.createTransactionFromTypedDescriptor(
                new MosaicDefinitionTransactionV1Descriptor(
                    mosaicDefinition,
                    new Address(
                        "TBMOSAICOD4F54EE5CDMR23CCBGOAM2XSJBR5OLC"),
                    new Amount(rentalFee)),
                signerKeyPair.getPublicKey(), 0, 2 * 60 * 60);

        // Calculate and attach the transaction fee
        final long fee = FeeCalculator.calculateTransactionFee(
            transaction);
        transaction.setFee(new Amount(fee));
        System.out.printf("  Transaction fee: %s XEM%n",
            (double) fee / 1_000_000);

徴収手数料はモザイク定義の一部なので、モザイクを作成する で使う MosaicDefinitionTransactionV1 と同じものを使って設定します。 このチュートリアルでは同じトランザクションを再利用し、 フィールドに徴収手数料を追加します。 ファサードは署名者を追加し、2 時間のデッドライン期間からタイムスタンプとデッドラインを導出します。

作成手数料 は 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))

    transaction_hash = facade.hash_transaction(transaction)
    print(f'Transaction hash: {transaction_hash}')
    announce_result = announce_transaction(
        json_payload, 'mosaic definition')
    if 'SUCCESS' == announce_result:
        wait_for_confirmation(transaction_hash, 'mosaic definition')
    else:
        print(f'Transaction rejected: {announce_result}')
    // 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 transactionHash = facade.hashTransaction(transaction).toString();
    console.log('Transaction hash:', transactionHash);
    const announceResult = await announceTransaction(
        jsonPayload, 'mosaic definition');
    if ('SUCCESS' === announceResult)
        await waitForConfirmation(transactionHash, 'mosaic definition');
    else
        console.log('Transaction rejected:', announceResult);
        // Sign, announce and wait for confirmation
        final CryptoTypes.Signature signature = facade.signTransaction(
            signerKeyPair, transaction);
        final String payload = NemTransactionFactory.attachSignature(
            transaction, signature);
        System.out.println("Built mosaic definition transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(transaction.toJson()));
        final String transactionHash = facade.hashTransaction(transaction)
            .toString();
        System.out.printf("Transaction hash: %s%n", transactionHash);
        final String result = announceTransaction(
            payload, "mosaic definition");
        if ("SUCCESS".equals(result)) {
            waitForConfirmation(
                transactionHash, "mosaic definition");
        } else {
            System.out.printf("Transaction rejected: %s%n", result);
        }

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);
        // Retrieve the levy
        final String path = "/mosaic/definition?mosaicId=" + mosaicId;
        System.out.printf("Fetching mosaic information from %s%n", path);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + path)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        final JsonNode info = JSON_MAPPER.readTree(response.body());
        final JsonNode levyInfo = info.get("levy");
        final JsonNode levyMosaicId = levyInfo.get("mosaicId");
        System.out.println("Levy information:");
        System.out.printf("  Type: %s%n",
            1 == levyInfo.get("type").asInt()
                ? "absolute" : "percentile");
        System.out.printf("  Recipient: %s%n",
            levyInfo.get("recipient").asText());
        System.out.printf("  Mosaic: %s:%s%n",
            levyMosaicId.get("namespaceId").asText(),
            levyMosaicId.get("name").asText());
        System.out.printf("  Fee: %s%n", levyInfo.get("fee").asText());

徴収手数料付きモザイクが作成されたことを確認するため、コードは /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
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"
}
Transaction hash: 190340818E859F24FD570CD5D54153DD3E5E192F2026F23E079522AC9BA56456
Announcing mosaic definition to /transaction/announce
  Result: SUCCESS
Waiting for mosaic definition confirmation from /transaction/get?hash=190340818E859F24FD570CD5D54153DD3E5E192F2026F23E079522AC9BA56456
  Transaction status: pending
  Transaction status: pending
  Transaction status: pending
mosaic definition 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 テストネットエクスプローラー でこの名前を検索すると、モザイクの詳細を確認できます。

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

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

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

まとめ⚓︎

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

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

次のステップ⚓︎

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