コンテンツにスキップ

モザイク定義の変更⚓︎

中級

ネットワーク上に モザイク のユニットが存在しない限り、作成者は同じ識別子を使用して2回目の定義トランザクションを送信することで、その定義プロパティ(フラグ、可分性、および有効期間)を変更できます。

例えば、配布前に初期設定されていなかったフラグを追加したり、可分性を変更したり、有効期間を延長したりすることが可能です。

供給量ゼロの要件

モザイク定義を変更できるのは、総供給量が 0 の場合のみです。 ネットワーク上のどこにもそのモザイクのユニットが存在してはなりません。存在する場合、トランザクションは Failure_Mosaic_Modification_Disallowed で失敗します。

このチュートリアルでは、既存のモザイクのフラグを変更する方法を説明します。定義ではなくモザイクの供給量を変更したい場合は、モザイク供給量の変更 を参照してください。

代わりに新しいモザイクを作成することを検討してください

既存のモザイクを修正するよりも、新しいモザイクを作成する 方が簡単です。 修正の仕組みを理解しておくことは、モザイクの定義がいつ編集できなくなるかを知るために重要です。

前提条件⚓︎

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

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

完全なコード⚓︎

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

import json
import os
import time
import urllib.request

from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.SymbolFacade import SymbolFacade
from symbolchain.symbol.IdGenerator import generate_mosaic_id

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


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


# Helper function to wait for transaction confirmation
def wait_for_confirmation(tx_hash, label):
    print(f'Waiting for {label} confirmation...')
    for attempt in range(60):
        time.sleep(1)
        try:
            url = f'{NODE_URL}/transactionStatus/{tx_hash}'
            with urllib.request.urlopen(url) as confirm_response:
                status = json.loads(confirm_response.read().decode())
                print(f'  Transaction status: {status["group"]}')
                if status['group'] == 'confirmed':
                    print(f'{label} confirmed in {attempt} seconds')
                    return
                if status['group'] == 'failed':
                    raise RuntimeError(
                        f'{label} failed: {status["code"]}')
        except urllib.error.HTTPError:
            print('  Transaction status: unknown')
    raise TimeoutError(f'{label} not confirmed after 60 seconds')


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

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

try:
    # Fetch 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_multiplier = response_json['medianFeeMultiplier']
        minimum_multiplier = response_json['minFeeMultiplier']
        fee_multiplier = max(median_multiplier, minimum_multiplier)
        print(f'  Fee multiplier: {fee_multiplier}')

    # Build the modification transaction
    MOSAIC_NONCE = int(os.getenv('MOSAIC_NONCE', '0'))
    print(f'Mosaic nonce: {MOSAIC_NONCE}')

    mosaic_id = generate_mosaic_id(signer_address, MOSAIC_NONCE)
    print(f'Mosaic ID: {mosaic_id} (0x{mosaic_id:016X})')

    modify_tx = facade.create_transaction_from_descriptor(
        {
            'type': 'mosaic_definition_transaction_v1',
            'duration': 0,
            'divisibility': 0,
            'nonce': MOSAIC_NONCE,
            'flags': 'revokable'
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)

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

    modify_hash = facade.hash_transaction(modify_tx)
    print(f'Transaction hash: {modify_hash}')

    # Announce transaction
    announce_transaction(json_payload, 'mosaic modification')

    # Wait for confirmation
    wait_for_confirmation(modify_hash, 'mosaic modification')

    # Retrieve the mosaic
    mosaic_id_hex = f'{mosaic_id:016X}'
    mosaic_path = f'/mosaics/{mosaic_id_hex}'
    print(f'Fetching mosaic information from {mosaic_path}')
    with urllib.request.urlopen(f'{NODE_URL}{mosaic_path}') as response:
        response_json = json.loads(response.read().decode())
        mosaic_info = response_json['mosaic']
        print('Mosaic information:')
        print(f'  Mosaic ID: {mosaic_info["id"]}')
        print(f'  Supply: {mosaic_info["supply"]}')
        print(f'  Divisibility: {mosaic_info["divisibility"]}')
        print(f'  Flags: {mosaic_info["flags"]}')
        print(f'  Duration: {mosaic_info["duration"]}')

except Exception as e:
    print(e)

Download source

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

// Helper function to wait for transaction confirmation
async function waitForConfirmation(transactionHash, label) {
    console.log(`Waiting for ${label} confirmation...`);
    for (let attempt = 0; 60 > attempt; attempt++) {
        await new Promise(resolve => { setTimeout(resolve, 1000); });
        const response = await fetch(
            `${NODE_URL}/transactionStatus/${transactionHash}`);
        if (!response.ok) {
            if (404 === response.status) {
                console.log('  Transaction status: unknown');
                continue;
            }
            throw new Error(`HTTP ${response.status}`);
        }
        const status = await response.json();
        console.log('  Transaction status:', status.group);
        if ('confirmed' === status.group) {
            console.log(`${label} confirmed in`, attempt, 'seconds');
            return;
        }
        if ('failed' === status.group)
            throw new Error(`${label} failed: ${status.code}`);
    }
    throw new Error(`${label} not confirmed after 60 seconds`);
}


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

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

try {
    // Fetch 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 medianMultiplier = feeJSON.medianFeeMultiplier;
    const minimumMultiplier = feeJSON.minFeeMultiplier;
    const feeMultiplier = Math.max(medianMultiplier, minimumMultiplier);
    console.log('  Fee multiplier:', feeMultiplier);

    // Build the modification transaction
    const MOSAIC_NONCE = parseInt(process.env.MOSAIC_NONCE || '0', 10);
    console.log('Mosaic nonce:', MOSAIC_NONCE);

    const mosaicId = generateMosaicId(signerAddress, MOSAIC_NONCE);
    const mosaicIdHex = mosaicId.toString(16)
        .toUpperCase().padStart(16, '0');
    console.log(`Mosaic ID: ${mosaicId} (0x${mosaicIdHex})`);

    const modifyTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicDefinitionTransactionV1Descriptor(
            new models.MosaicId(0n),
            new models.BlockDuration(0n),
            new models.MosaicNonce(MOSAIC_NONCE),
            models.MosaicFlags.REVOKABLE,
            0),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);

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

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

    // Announce transaction
    await announceTransaction(jsonPayload, 'mosaic modification');

    // Wait for confirmation
    await waitForConfirmation(modifyHash, 'mosaic modification');

    // Retrieve the mosaic
    const mosaicPath = `/mosaics/${mosaicIdHex}`;
    console.log('Fetching mosaic information from', mosaicPath);
    const mosaicResponse = await fetch(`${NODE_URL}${mosaicPath}`);
    const mosaicJSON = await mosaicResponse.json();
    const mosaicInfo = mosaicJSON.mosaic;
    console.log('Mosaic information:');
    console.log('  Mosaic ID:', mosaicInfo.id);
    console.log('  Supply:', mosaicInfo.supply);
    console.log('  Divisibility:', mosaicInfo.divisibility);
    console.log('  Flags:', mosaicInfo.flags);
    console.log('  Duration:', mosaicInfo.duration);
} catch (e) {
    console.error(e.message);
}

Download source

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

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 com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import org.symbol.sdk.CryptoTypes;
import org.symbol.sdk.facade.SymbolFacade;
import org.symbol.sdk.symbol.Address;
import org.symbol.sdk.symbol.IdGenerator;
import org.symbol.sdk.symbol.KeyPair;
import org.symbol.sdk.symbol.SymbolTransactionFactory;
import org.symbol.sdk.symbol.descriptors.*;
import org.symbol.sdk.symbol.models.*;

public final class ModifyMosaicDefinition {
    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", "https://reference.symboltest.net:3001");

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

    private void announceTransaction(
        final String payload,
        final String label
    ) throws IOException, InterruptedException {
        System.out.printf("Announcing %s to /transactions%n", label);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + "/transactions"))
            .header("Content-Type", "application/json")
            .PUT(HttpRequest.BodyPublishers.ofString(payload))
            .build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        System.out.printf("  Response: %s%n", response.body());
    }

    private void waitForConfirmation(
        final String transactionHash,
        final String label
    ) throws IOException, InterruptedException {
        System.out.printf("Waiting for %s confirmation...%n", label);
        for (int attempt = 0; 60 > attempt; ++attempt) {
            Thread.sleep(1000);
            final String statusPath =
                "/transactionStatus/" + transactionHash;
            final HttpRequest statusRequest = HttpRequest.newBuilder(
                URI.create(nodeUrl + statusPath)).GET().build();
            final HttpResponse<String> statusResponse = HTTP_CLIENT
                .send(statusRequest, BodyHandlers.ofString());
            if (404 == statusResponse.statusCode()) {
                System.out.println("  Transaction status: unknown");
                continue;
            }
            if (2 != statusResponse.statusCode() / 100)
                throw new IOException(
                    "HTTP " + statusResponse.statusCode());

            final JsonNode status =
                JSON_MAPPER.readTree(statusResponse.body());
            final String group = status.get("group").asText();
            System.out.printf("  Transaction status: %s%n", group);
            if ("confirmed".equals(group)) {
                System.out.printf("%s confirmed in %d seconds%n",
                    label, attempt);
                return;
            }
            if ("failed".equals(group))
                throw new IOException(String.format("%s failed: %s",
                    label, status.get("code").asText()));
        }
        throw new IOException(String.format(
            "%s not confirmed after 60 seconds", label));
    }

    public static void main(final String[] args) {
        try {
            new ModifyMosaicDefinition().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 privateKeyString = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKeyString));

        final Address signerAddress = facade.network.publicKeyToAddress(
            signerKeyPair.getPublicKey());
        System.out.printf("Signer address: %s%n", signerAddress);


        // Fetch recommended fees
        final String feePath = "/network/fees/transaction";
        System.out.printf("Fetching recommended fees from %s%n", feePath);
        final HttpRequest feeRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + feePath)).GET().build();
        final HttpResponse<String> feeResponse = HTTP_CLIENT.send(
            feeRequest, BodyHandlers.ofString());
        final JsonNode feeJson = JSON_MAPPER.readTree(feeResponse.body());
        final long feeMultiplier = Math.max(
            feeJson.get("medianFeeMultiplier").asLong(),
            feeJson.get("minFeeMultiplier").asLong());
        System.out.printf("  Fee multiplier: %d%n", feeMultiplier);


        // Build the modification transaction
        final long mosaicNonce = Long.parseUnsignedLong(
            System.getenv().getOrDefault("MOSAIC_NONCE", "0"));
        System.out.printf("Mosaic nonce: %d%n", mosaicNonce);

        final long mosaicId = IdGenerator.generateMosaicId(
            signerAddress, mosaicNonce);
        System.out.printf("Mosaic ID: %d (0x%016X)%n",
            mosaicId, mosaicId);

        final Transaction modifyTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicDefinitionTransactionV1Descriptor(
                    new MosaicId(0),
                    new BlockDuration(0),
                    new MosaicNonce(mosaicNonce),
                    MosaicFlags.REVOKABLE,
                    0),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

        // Sign and generate final payload
        final CryptoTypes.Signature signature = facade.signTransaction(
            signerKeyPair, modifyTx);
        final String jsonPayload = SymbolTransactionFactory
            .attachSignature(modifyTx, signature);
        System.out.println("Built mosaic modification transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(modifyTx.toJson()));

        final String modifyHash =
            facade.hashTransaction(modifyTx).toString();
        System.out.printf("Transaction hash: %s%n", modifyHash);

        // Announce transaction
        announceTransaction(jsonPayload, "mosaic modification");

        // Wait for confirmation
        waitForConfirmation(modifyHash, "mosaic modification");

        // Retrieve the mosaic
        final String mosaicIdHex = "%016X".formatted(mosaicId);
        final String mosaicPath = "/mosaics/" + mosaicIdHex;
        System.out.printf("Fetching mosaic information from %s%n",
            mosaicPath);
        final HttpRequest mosaicRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + mosaicPath)).GET().build();
        final HttpResponse<String> mosaicResponse = HTTP_CLIENT.send(
            mosaicRequest, BodyHandlers.ofString());
        final JsonNode mosaicInfo = JSON_MAPPER.readTree(
            mosaicResponse.body()).get("mosaic");
        System.out.println("Mosaic information:");
        System.out.printf("  Mosaic ID: %s%n",
            mosaicInfo.get("id").asText());
        System.out.printf("  Supply: %s%n",
            mosaicInfo.get("supply").asText());
        System.out.printf("  Divisibility: %s%n",
            mosaicInfo.get("divisibility").asText());
        System.out.printf("  Flags: %s%n",
            mosaicInfo.get("flags").asText());
        System.out.printf("  Duration: %s%n",
            mosaicInfo.get("duration").asText());

    }
}

Download source

コード解説⚓︎

アカウントの設定⚓︎

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

facade = SymbolFacade('testnet')
signer_address = facade.network.public_key_to_address(
    signer_key_pair.public_key)
print(f'Signer address: {signer_address}')
const SIGNER_PRIVATE_KEY = process.env.SIGNER_PRIVATE_KEY ||
    '0000000000000000000000000000000000000000000000000000000000000000';
const signerKeyPair = new SymbolFacade.KeyPair(
    new PrivateKey(SIGNER_PRIVATE_KEY));

const facade = new SymbolFacade('testnet');
const signerAddress =
    facade.network.publicKeyToAddress(signerKeyPair.publicKey);
console.log('Signer address:', signerAddress.toString());
        final String privateKeyString = System.getenv().getOrDefault(
            "SIGNER_PRIVATE_KEY", "0".repeat(64));
        final KeyPair signerKeyPair = new KeyPair(
            new CryptoTypes.PrivateKey(privateKeyString));

        final Address signerAddress = facade.network.publicKeyToAddress(
            signerKeyPair.getPublicKey());
        System.out.printf("Signer address: %s%n", signerAddress);

このスニペットは、署名者の 秘密鍵SIGNER_PRIVATE_KEY 環境変数から読み取ります。設定されていない場合はデフォルトのテストキーが使用されます。 署名者の アドレス公開鍵 から派生します。 この アカウント は、そのモザイクの元の作成者である必要があります。

    # 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_multiplier = response_json['medianFeeMultiplier']
        minimum_multiplier = response_json['minFeeMultiplier']
        fee_multiplier = max(median_multiplier, minimum_multiplier)
        print(f'  Fee multiplier: {fee_multiplier}')
    // 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 medianMultiplier = feeJSON.medianFeeMultiplier;
    const minimumMultiplier = feeJSON.minFeeMultiplier;
    const feeMultiplier = Math.max(medianMultiplier, minimumMultiplier);
    console.log('  Fee multiplier:', feeMultiplier);
        // Fetch recommended fees
        final String feePath = "/network/fees/transaction";
        System.out.printf("Fetching recommended fees from %s%n", feePath);
        final HttpRequest feeRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + feePath)).GET().build();
        final HttpResponse<String> feeResponse = HTTP_CLIENT.send(
            feeRequest, BodyHandlers.ofString());
        final JsonNode feeJson = JSON_MAPPER.readTree(feeResponse.body());
        final long feeMultiplier = Math.max(
            feeJson.get("medianFeeMultiplier").asLong(),
            feeJson.get("minFeeMultiplier").asLong());
        System.out.printf("  Fee multiplier: %d%n", feeMultiplier);

転送トランザクション チュートリアルで説明されているプロセスに従い、推奨手数料を /network/fees/transaction GET から取得します。

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

モザイク定義を変更するには、現在の値(フラグ、可分性、有効期間)を知る必要があります。なぜなら、ネットワークはそれらを置き換えるのではなく、トランザクション内の値と組み合わせるからです。 モザイクの情報は /mosaics/{mosaicId} GET エンドポイントを使用して取得するか、Symbol エクスプローラー で検索できます。

このチュートリアルでは、前のチュートリアルでモザイクが 作成および取得 された直後であるため、現在の値は既知であると仮定します。

    # Build the modification transaction
    MOSAIC_NONCE = int(os.getenv('MOSAIC_NONCE', '0'))
    print(f'Mosaic nonce: {MOSAIC_NONCE}')

    mosaic_id = generate_mosaic_id(signer_address, MOSAIC_NONCE)
    print(f'Mosaic ID: {mosaic_id} (0x{mosaic_id:016X})')

    modify_tx = facade.create_transaction_from_descriptor(
        {
            'type': 'mosaic_definition_transaction_v1',
            'duration': 0,
            'divisibility': 0,
            'nonce': MOSAIC_NONCE,
            'flags': 'revokable'
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)
    // Build the modification transaction
    const MOSAIC_NONCE = parseInt(process.env.MOSAIC_NONCE || '0', 10);
    console.log('Mosaic nonce:', MOSAIC_NONCE);

    const mosaicId = generateMosaicId(signerAddress, MOSAIC_NONCE);
    const mosaicIdHex = mosaicId.toString(16)
        .toUpperCase().padStart(16, '0');
    console.log(`Mosaic ID: ${mosaicId} (0x${mosaicIdHex})`);

    const modifyTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicDefinitionTransactionV1Descriptor(
            new models.MosaicId(0n),
            new models.BlockDuration(0n),
            new models.MosaicNonce(MOSAIC_NONCE),
            models.MosaicFlags.REVOKABLE,
            0),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);
        // Build the modification transaction
        final long mosaicNonce = Long.parseUnsignedLong(
            System.getenv().getOrDefault("MOSAIC_NONCE", "0"));
        System.out.printf("Mosaic nonce: %d%n", mosaicNonce);

        final long mosaicId = IdGenerator.generateMosaicId(
            signerAddress, mosaicNonce);
        System.out.printf("Mosaic ID: %d (0x%016X)%n",
            mosaicId, mosaicId);

        final Transaction modifyTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicDefinitionTransactionV1Descriptor(
                    new MosaicId(0),
                    new BlockDuration(0),
                    new MosaicNonce(mosaicNonce),
                    MosaicFlags.REVOKABLE,
                    0),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

MOSAIC_NONCE 環境変数は、変更するモザイクの ノンス を指定します。同じモザイクを対象とするために、モザイクの作成 時に使用したものと一致させる必要があります。

変更トランザクションは、最初の作成時と同じ MosaicDefinitionTransactionV1 タイプを使用します。 大きな違いは、ノンスが新規作成ではなく既存のモザイクを指している 点です。

トランザクションを処理する際、各プロパティは以下のルールに従って現在の値と組み合わされます。

  • フラグ は、現在のフラグと XOR(排他的論理和)演算されます。 すでにアクティブなフラグを設定すると削除され、アクティブでないフラグを設定すると追加されます。 利用可能な各フラグの説明については、モザイク定義トランザクションの構築 を参照してください。
  • 可分性 は、現在の可分性と XOR 演算されます。 結果の値は 0 から 6 の間である必要があります。
  • 有効期間 は、現在の残り期間に加算されます。 このフィールドは無符号(unsigned)であるため、期間は延長のみ可能で、短縮はできません。 値 0 は期間を変更しません。 無期限のモザイク(期間 0)は、有効期間を変更できません。 結果の期間は 10,512,000 ブロック(約10年)を超えることはできません。

この例では、既存のモザイクはフラグ transferable restrictable数値 6)を持っています。 変更によって flags: 'revokable'(数値 8)を設定します。 XOR 演算により 6 ⊕ 8 = 14 となり、これは transferable restrictable revokable に対応します。

以下の表は、XOR が個別のフラグにどのように影響するかを示しています。

フラグ 現在 変更内容 結果 (XOR)
transferable オン オフ オン
restrictable オン オフ オン
revokable オフ オン オン

divisibility(可分性)を 0 に設定すると 2 ⊕ 0 = 2 となり(変更なし)、 duration(有効期間)を 0 に設定すると現在の期間には何も加算されません。

レンタル手数料

モザイクの作成時と同様に、変更時にも XYM で支払う レンタル手数料 が全額発生します。 これは、標準の トランザクション手数料 に加えて、毎回発生する固定の手数料です。 レンタル手数料の額は /network/fees/rental GET エンドポイント( effectiveMosaicRentalFee プロパティ)から照会できます。

変更の送信⚓︎

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

    modify_hash = facade.hash_transaction(modify_tx)
    print(f'Transaction hash: {modify_hash}')

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

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

    // Announce transaction
    await announceTransaction(jsonPayload, 'mosaic modification');
        // Sign and generate final payload
        final CryptoTypes.Signature signature = facade.signTransaction(
            signerKeyPair, modifyTx);
        final String jsonPayload = SymbolTransactionFactory
            .attachSignature(modifyTx, signature);
        System.out.println("Built mosaic modification transaction:");
        System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter()
            .writeValueAsString(modifyTx.toJson()));

        final String modifyHash =
            facade.hashTransaction(modifyTx).toString();
        System.out.printf("Transaction hash: %s%n", modifyHash);

        // Announce transaction
        announceTransaction(jsonPayload, "mosaic modification");

変更トランザクションは、転送トランザクション チュートリアルと同じプロセスに従って署名され、アナウンスされます。

    # Wait for confirmation
    wait_for_confirmation(modify_hash, 'mosaic modification')
    // Wait for confirmation
    await waitForConfirmation(modifyHash, 'mosaic modification');
        // Wait for confirmation
        waitForConfirmation(modifyHash, "mosaic modification");

コードはその後、ステータスが confirmed に変わるまで /transactionStatus/{hash} GET エンドポイントをポーリングして、トランザクションが承認されるのを待ちます。

モザイクの取得⚓︎

    # Retrieve the mosaic
    mosaic_id_hex = f'{mosaic_id:016X}'
    mosaic_path = f'/mosaics/{mosaic_id_hex}'
    print(f'Fetching mosaic information from {mosaic_path}')
    with urllib.request.urlopen(f'{NODE_URL}{mosaic_path}') as response:
        response_json = json.loads(response.read().decode())
        mosaic_info = response_json['mosaic']
        print('Mosaic information:')
        print(f'  Mosaic ID: {mosaic_info["id"]}')
        print(f'  Supply: {mosaic_info["supply"]}')
        print(f'  Divisibility: {mosaic_info["divisibility"]}')
        print(f'  Flags: {mosaic_info["flags"]}')
        print(f'  Duration: {mosaic_info["duration"]}')
    // Retrieve the mosaic
    const mosaicPath = `/mosaics/${mosaicIdHex}`;
    console.log('Fetching mosaic information from', mosaicPath);
    const mosaicResponse = await fetch(`${NODE_URL}${mosaicPath}`);
    const mosaicJSON = await mosaicResponse.json();
    const mosaicInfo = mosaicJSON.mosaic;
    console.log('Mosaic information:');
    console.log('  Mosaic ID:', mosaicInfo.id);
    console.log('  Supply:', mosaicInfo.supply);
    console.log('  Divisibility:', mosaicInfo.divisibility);
    console.log('  Flags:', mosaicInfo.flags);
    console.log('  Duration:', mosaicInfo.duration);
        // Retrieve the mosaic
        final String mosaicIdHex = "%016X".formatted(mosaicId);
        final String mosaicPath = "/mosaics/" + mosaicIdHex;
        System.out.printf("Fetching mosaic information from %s%n",
            mosaicPath);
        final HttpRequest mosaicRequest = HttpRequest.newBuilder(
            URI.create(nodeUrl + mosaicPath)).GET().build();
        final HttpResponse<String> mosaicResponse = HTTP_CLIENT.send(
            mosaicRequest, BodyHandlers.ofString());
        final JsonNode mosaicInfo = JSON_MAPPER.readTree(
            mosaicResponse.body()).get("mosaic");
        System.out.println("Mosaic information:");
        System.out.printf("  Mosaic ID: %s%n",
            mosaicInfo.get("id").asText());
        System.out.printf("  Supply: %s%n",
            mosaicInfo.get("supply").asText());
        System.out.printf("  Divisibility: %s%n",
            mosaicInfo.get("divisibility").asText());
        System.out.printf("  Flags: %s%n",
            mosaicInfo.get("flags").asText());
        System.out.printf("  Duration: %s%n",
            mosaicInfo.get("duration").asText());

変更が適用されたことを確認するために、コードは /mosaics/{mosaicId} GET エンドポイントを使用してネットワークからモザイクを取得し、更新されたプロパティを表示します。

レスポンスが成功すれば、モザイクが期待通りのフラグ値を持っていることが確認されます。

出力⚓︎

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

Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100
Mosaic nonce: 1770998662
Mosaic ID: 6619508144549180335 (0x5BDD3795F7A8B3AF)
Built mosaic modification transaction:
{
  "signature": "729A40BAD69DA8B3CE38D442CFCD1258CA41B7933CF80EFE24A7A354773E0B08D7E2796DED2D93A5F1C16A5EB3298A2113601348A436A4340BFFC38BD7EEBF07",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 16717,
  "fee": "15000",
  "deadline": "103755503591",
  "id": "6619508144549180335",
  "duration": "0",
  "nonce": 1770998662,
  "flags": 8,
  "divisibility": 0
}
Transaction hash: 110D3B362D76020EBCD96A5A2E7C621ADE53D98BFACEEE273DA224D8E8B07225
Announcing mosaic modification to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for mosaic modification confirmation...
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
mosaic modification confirmed in 11 seconds
Fetching mosaic information from /mosaics/5BDD3795F7A8B3AF
Mosaic information:
  Mosaic ID: 5BDD3795F7A8B3AF
  Supply: 0
  Divisibility: 2
  Flags: 14
  Duration: 0

出力の主なポイント:

  • モザイクノンス(5行目): ノンス 1770998662 はモザイク作成時に使用されたものと一致しており、同じモザイクを変更対象としています。

  • モザイクID(6行目): モザイクID 0x5BDD3795F7A8B3AF はノンスと署名者のアドレスから派生しており、正しいモザイクが変更されていることが確認できます。

  • トランザクションプロパティ(17、19-20行目): 変更により duration0(変更なし)、 flags8revokable )、 divisibility0(変更なし)に設定されています。

  • 更新されたプロパティ(34-36行目): モザイクの可分性は 2 のままです( 0 との XOR により不変)。 フラグは 14 になっており、これは transferable (2) + restrictable (4) + revokable (8) に対応します。 有効期間は 0(無期限、不変)のままです。

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

結論⚓︎

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

ステップ 関連ドキュメント
モザイクIDを生成する
モザイクフラグを変更する , MosaicDefinitionTransactionV1
更新されたモザイクを検証する /mosaics/{mosaicId} GET