コンテンツにスキップ

モザイクの回収(リボーク)⚓︎

初級

revokable(回収可能)フラグを設定して作成された モザイク は、作成者が任意のアカウントからモザイクを回収し、自身の アカウント 残高に戻すことができます。これは、契約条項の執行、未使用トークンの回収、または誤った配布の修正に役立ちます。

このチュートリアルでは、他のアカウントからモザイクを回収する方法を説明します。

前提条件⚓︎

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

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

回収可能性の詳細については、テキストブックの 回収可能性 を参照してください。

完全なコード⚓︎

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

import json
import os
import time
import urllib.request

from symbolchain.CryptoTypes import PrivateKey
from symbolchain.facade.SymbolFacade import SymbolFacade

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


# Helper function to fetch account mosaic balances
def get_account_mosaics(address):
    account_path = f'/accounts/{address}'
    print(f'Fetching account information from {account_path}')
    with urllib.request.urlopen(f'{NODE_URL}{account_path}') as resp:
        resp_json = json.loads(resp.read().decode())
        return resp_json['account']['mosaics']


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

SOURCE_ADDRESS = os.getenv('SOURCE_ADDRESS',
    'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA')
print(f'Source address: {SOURCE_ADDRESS}')

MOSAIC_ID_HEX = os.getenv('MOSAIC_ID', '7AED3D514C986941')
mosaic_id = int(MOSAIC_ID_HEX, 16)
print(f'Mosaic ID: {mosaic_id} (0x{mosaic_id:016X})')

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

    # --- CHECKING INITIAL BALANCE ---
    print('\n--- Checking initial balance ---')
    mosaics = get_account_mosaics(SOURCE_ADDRESS)
    for mosaic in mosaics:
        if mosaic['id'] == MOSAIC_ID_HEX.upper():
            print(f'  Mosaic ID: {mosaic["id"]},'
                f' Amount: {mosaic["amount"]}')

    # --- REVOKING MOSAIC ---
    print('\n--- Revoking mosaic ---')

    revoke_tx = facade.create_transaction_from_descriptor(
        {
            'type': 'mosaic_supply_revocation_transaction_v1',
            'source_address': SOURCE_ADDRESS,
            'mosaic': {
                'mosaic_id': mosaic_id,
                'amount': 7_00
            }
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)

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

    revoke_hash = facade.hash_transaction(revoke_tx)
    print(f'Transaction hash: {revoke_hash}')

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

    # Wait for confirmation
    wait_for_confirmation(revoke_hash, 'mosaic revocation')

    # --- VERIFYING REVOCATION ---
    print('\n--- Verifying revocation ---')
    mosaics = get_account_mosaics(SOURCE_ADDRESS)
    for mosaic in mosaics:
        if mosaic['id'] == MOSAIC_ID_HEX.upper():
            print(f'  Mosaic ID: {mosaic["id"]},'
                f' Amount: {mosaic["amount"]}')

except Exception as e:
    print(e)

Download source

import { PrivateKey } from 'symbol-sdk';
import {
    SymbolFacade,
    descriptors,
    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`);
}

// Helper function to fetch account mosaic balances
async function getAccountMosaics(address) {
    const accountPath = `/accounts/${address}`;
    console.log('Fetching account information from', accountPath);
    const response = await fetch(`${NODE_URL}${accountPath}`);
    const responseJSON = await response.json();
    return responseJSON.account.mosaics;
}

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());

const SOURCE_ADDRESS = process.env.SOURCE_ADDRESS ||
    'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA';
console.log('Source address:', SOURCE_ADDRESS);

const MOSAIC_ID_HEX = process.env.MOSAIC_ID ||
    '7AED3D514C986941';
const mosaicId = BigInt(`0x${MOSAIC_ID_HEX}`);
const mosaicIdHex = mosaicId.toString(16)
    .toUpperCase().padStart(16, '0');
console.log(
    `Mosaic ID: ${mosaicId} (0x${mosaicIdHex})`);

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

    // --- CHECKING INITIAL BALANCE ---
    console.log('\n--- Checking initial balance ---');
    let mosaics = await getAccountMosaics(SOURCE_ADDRESS);
    for (const mosaic of mosaics) {
        if (mosaic.id === MOSAIC_ID_HEX.toUpperCase()) {
            console.log(`  Mosaic ID: ${mosaic.id},` +
                ` Amount: ${mosaic.amount}`);
        }
    }

    // --- REVOKING MOSAIC ---
    console.log('\n--- Revoking mosaic ---');

    const revokeTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicSupplyRevocationTransactionV1Descriptor(
            new SymbolFacade.Address(SOURCE_ADDRESS),
            new descriptors.UnresolvedMosaicDescriptor(
                new models.UnresolvedMosaicId(mosaicId),
                new models.Amount(7_00n))),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);

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

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

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

    // Wait for confirmation
    await waitForConfirmation(revokeHash, 'mosaic revocation');

    // --- VERIFYING REVOCATION ---
    console.log('\n--- Verifying revocation ---');
    mosaics = await getAccountMosaics(SOURCE_ADDRESS);
    for (const mosaic of mosaics) {
        if (mosaic.id === MOSAIC_ID_HEX.toUpperCase()) {
            console.log(`  Mosaic ID: ${mosaic.id},` +
                ` Amount: ${mosaic.amount}`);
        }
    }
} 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.KeyPair;
import org.symbol.sdk.symbol.SymbolTransactionFactory;
import org.symbol.sdk.symbol.descriptors.*;
import org.symbol.sdk.symbol.models.*;

public final class RevokeMosaic {
    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));
    }

    private JsonNode getAccountMosaics(
        final Address address
    ) throws IOException, InterruptedException {
        final String accountPath = "/accounts/" + address;
        System.out.printf("Fetching account information from %s%n",
            accountPath);
        final HttpRequest request = HttpRequest.newBuilder(
            URI.create(nodeUrl + accountPath)).GET().build();
        final HttpResponse<String> response = HTTP_CLIENT.send(
            request, BodyHandlers.ofString());
        return JSON_MAPPER.readTree(response.body())
            .get("account").get("mosaics");
    }

    public static void main(final String[] args) {
        try {
            new RevokeMosaic().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);

        final String sourceAddressString = System.getenv().getOrDefault(
            "SOURCE_ADDRESS", "TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA");
        final Address sourceAddress = new Address(sourceAddressString);
        System.out.printf("Source address: %s%n", sourceAddress);

        final String mosaicIdHex = System.getenv().getOrDefault(
            "MOSAIC_ID", "7AED3D514C986941");
        final long mosaicId = Long.parseUnsignedLong(mosaicIdHex, 16);
        System.out.printf("Mosaic ID: %d (0x%016X)%n",
            mosaicId, mosaicId);


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

        // --- CHECKING INITIAL BALANCE ---
        System.out.println("\n--- Checking initial balance ---");

        JsonNode mosaics = getAccountMosaics(sourceAddress);
        for (final JsonNode mosaic : mosaics) {
            if (mosaic.get("id").asText().equals(
                    mosaicIdHex.toUpperCase()))
                System.out.printf("  Mosaic ID: %s, Amount: %s%n",
                    mosaic.get("id").asText(),
                    mosaic.get("amount").asText());
        }

        // --- REVOKING MOSAIC ---
        System.out.println("\n--- Revoking mosaic ---");

        final Transaction revokeTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicSupplyRevocationTransactionV1Descriptor(
                    sourceAddress,
                    new UnresolvedMosaicDescriptor(
                        new UnresolvedMosaicId(mosaicId),
                        new Amount(7_00))),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

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

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

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

        // Wait for confirmation
        waitForConfirmation(revokeHash, "mosaic revocation");

        // --- VERIFYING REVOCATION ---
        System.out.println("\n--- Verifying revocation ---");

        mosaics = getAccountMosaics(sourceAddress);
        for (final JsonNode mosaic : mosaics) {
            if (mosaic.get("id").asText().equals(
                    mosaicIdHex.toUpperCase()))
                System.out.printf("  Mosaic ID: %s, Amount: %s%n",
                    mosaic.get("id").asText(),
                    mosaic.get("amount").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}')

SOURCE_ADDRESS = os.getenv('SOURCE_ADDRESS',
    'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA')
print(f'Source address: {SOURCE_ADDRESS}')

MOSAIC_ID_HEX = os.getenv('MOSAIC_ID', '7AED3D514C986941')
mosaic_id = int(MOSAIC_ID_HEX, 16)
print(f'Mosaic ID: {mosaic_id} (0x{mosaic_id:016X})')
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());

const SOURCE_ADDRESS = process.env.SOURCE_ADDRESS ||
    'TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA';
console.log('Source address:', SOURCE_ADDRESS);

const MOSAIC_ID_HEX = process.env.MOSAIC_ID ||
    '7AED3D514C986941';
const mosaicId = BigInt(`0x${MOSAIC_ID_HEX}`);
const mosaicIdHex = mosaicId.toString(16)
    .toUpperCase().padStart(16, '0');
console.log(
    `Mosaic ID: ${mosaicId} (0x${mosaicIdHex})`);
        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);

        final String sourceAddressString = System.getenv().getOrDefault(
            "SOURCE_ADDRESS", "TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA");
        final Address sourceAddress = new Address(sourceAddressString);
        System.out.printf("Source address: %s%n", sourceAddress);

        final String mosaicIdHex = System.getenv().getOrDefault(
            "MOSAIC_ID", "7AED3D514C986941");
        final long mosaicId = Long.parseUnsignedLong(mosaicIdHex, 16);
        System.out.printf("Mosaic ID: %d (0x%016X)%n",
            mosaicId, mosaicId);

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

SOURCE_ADDRESS 環境変数は、モザイクユニットが回収されるアカウントのアドレスを指定します 。

MOSAIC_ID 環境変数は、回収するモザイクの16進数識別子を指定します 。 アカウントが保持しているモザイクを一覧表示するには、アカウント残高の照会 を参照してください 。

    # 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 = get_account_mosaics(SOURCE_ADDRESS)
    for mosaic in mosaics:
        if mosaic['id'] == MOSAIC_ID_HEX.upper():
            print(f'  Mosaic ID: {mosaic["id"]},'
                f' Amount: {mosaic["amount"]}')
    let mosaics = await getAccountMosaics(SOURCE_ADDRESS);
    for (const mosaic of mosaics) {
        if (mosaic.id === MOSAIC_ID_HEX.toUpperCase()) {
            console.log(`  Mosaic ID: ${mosaic.id},` +
                ` Amount: ${mosaic.amount}`);
        }
    }
        JsonNode mosaics = getAccountMosaics(sourceAddress);
        for (final JsonNode mosaic : mosaics) {
            if (mosaic.get("id").asText().equals(
                    mosaicIdHex.toUpperCase()))
                System.out.printf("  Mosaic ID: %s, Amount: %s%n",
                    mosaic.get("id").asText(),
                    mosaic.get("amount").asText());
        }

回収を行う前に、ヘルパー関数 get_account_mosaics/accounts/{accountId} GET エンドポイントからソースアカウントの対象モザイクの現在残高を取得します 。 これにより、回収後の結果と比較するための基準が得られます 。

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

    revoke_tx = facade.create_transaction_from_descriptor(
        {
            'type': 'mosaic_supply_revocation_transaction_v1',
            'source_address': SOURCE_ADDRESS,
            'mosaic': {
                'mosaic_id': mosaic_id,
                'amount': 7_00
            }
        },
        signer_key_pair.public_key,
        fee_multiplier,
        2 * 60 * 60)
    const revokeTx = facade.createTransactionFromTypedDescriptor(
        new descriptors.MosaicSupplyRevocationTransactionV1Descriptor(
            new SymbolFacade.Address(SOURCE_ADDRESS),
            new descriptors.UnresolvedMosaicDescriptor(
                new models.UnresolvedMosaicId(mosaicId),
                new models.Amount(7_00n))),
        signerKeyPair.publicKey,
        feeMultiplier,
        2 * 60 * 60);
        final Transaction revokeTx =
            facade.createTransactionFromTypedDescriptor(
                new MosaicSupplyRevocationTransactionV1Descriptor(
                    sourceAddress,
                    new UnresolvedMosaicDescriptor(
                        new UnresolvedMosaicId(mosaicId),
                        new Amount(7_00))),
                signerKeyPair.getPublicKey(),
                feeMultiplier,
                2 * 60 * 60);

回収トランザクションは、ソースアカウントからモザイクユニットを回収し、作成者の残高に戻します 。

  • Type: モザイク供給回収トランザクションにはタイプ MosaicSupplyRevocationTransactionV1 を使用します 。

  • ソースアドレス: 回収するモザイクユニットを保持しているアカウントのアドレス 。 これは、指定されたモザイクのユニットを現在保持している任意のアカウントを指定できます 。

  • モザイク: モザイク ID と回収する量を含むオブジェクト 。

  • 数量: ソースアカウントから回収する絶対単位の数 。 モザイクの 可分性 を確認するには、 /mosaics/{mosaicId} GET エンドポイントを照会してください 。 例えば可分性が 2 の場合、数量 7007.00 全体単位(700 / \(10^2\))を表します 。

部分的な回収

数量はソースアカウントの全残高と一致する必要はありません 。 単一のトランザクションで、ソースの現在の保有量までの任意の数量を回収できます 。

回収の送信⚓︎

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

    revoke_hash = facade.hash_transaction(revoke_tx)
    print(f'Transaction hash: {revoke_hash}')

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

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

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

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

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

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

    # Wait for confirmation
    wait_for_confirmation(revoke_hash, 'mosaic revocation')
    // Wait for confirmation
    await waitForConfirmation(revokeHash, 'mosaic revocation');
        // Wait for confirmation
        waitForConfirmation(revokeHash, "mosaic revocation");

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

回収の検証⚓︎

    mosaics = get_account_mosaics(SOURCE_ADDRESS)
    for mosaic in mosaics:
        if mosaic['id'] == MOSAIC_ID_HEX.upper():
            print(f'  Mosaic ID: {mosaic["id"]},'
                f' Amount: {mosaic["amount"]}')
    mosaics = await getAccountMosaics(SOURCE_ADDRESS);
    for (const mosaic of mosaics) {
        if (mosaic.id === MOSAIC_ID_HEX.toUpperCase()) {
            console.log(`  Mosaic ID: ${mosaic.id},` +
                ` Amount: ${mosaic.amount}`);
        }
    }
        mosaics = getAccountMosaics(sourceAddress);
        for (final JsonNode mosaic : mosaics) {
            if (mosaic.get("id").asText().equals(
                    mosaicIdHex.toUpperCase()))
                System.out.printf("  Mosaic ID: %s, Amount: %s%n",
                    mosaic.get("id").asText(),
                    mosaic.get("amount").asText());
        }

回収を検証するために、ヘルパー関数 get_account_mosaics がソースアカウントの残高を再度取得します 。 残高は 初期残高 よりも回収量分だけ少なくなっているはずです 。

出力⚓︎

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

Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Source address: TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
Mosaic ID: 8857803461494335809 (0x7AED3D514C986941)
Fetching recommended fees from /network/fees/transaction
  Fee multiplier: 100

--- Checking initial balance ---
Fetching account information from /accounts/TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
  Mosaic ID: 7AED3D514C986941, Amount: 1000

--- Revoking mosaic ---
Built mosaic revocation transaction:
{
  "signature": "225719859B43C8B9FCB04432E83FA95258C15A64B60974754DC2E4CAF9E58110995F2ECB5552049C3F79632FFAF19F4B90247012A7777E79D3AF0A17507A9F0D",
  "signer_public_key": "3B6A27BCCEB6A42D62A3A8D02A6F0D73653215771DE243A63AC048A18B59DA29",
  "version": 1,
  "network": 152,
  "type": 17229,
  "fee": "16800",
  "deadline": "104790468507",
  "source_address": "987D075454716222F609929E883174AD8C996D5828C938BC",
  "mosaic": {
    "mosaic_id": "8857803461494335809",
    "amount": "700"
  }
}
Transaction hash: C0E59A5E36FC50CC5BDF8A16EABAB791611737B64E3590D43576374B803771E6
Announcing mosaic revocation to /transactions
  Response: {"message":"packet 9 was pushed to the network via /transactions"}
Waiting for mosaic revocation confirmation...
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: unconfirmed
  Transaction status: confirmed
mosaic revocation confirmed in 8 seconds

--- Verifying revocation ---
Fetching account information from /accounts/TB6QOVCUOFRCF5QJSKPIQMLUVWGJS3KYFDETRPA
  Mosaic ID: 7AED3D514C986941, Amount: 300

出力の主なポイント:

  • モザイクID (4行目): モザイクID 8857803461494335809 (0x7AED3D514C986941) は、回収対象のモザイクを識別します 。

  • 初期残高 (10行目): 回収前、ソースアカウントはそのモザイクを 1000 絶対単位保持しています 。

  • ソースアドレス (22行目): source_address フィールドは、ユニットが回収されるアカウントを識別します 。 これは、3行目に示されている Base32 アドレス の16進数エンコード形式です 。

  • 回収量 (24-25行目): mosaic オブジェクトは、10進数形式のモザイクIDと数量 700 を指定しています 。 10進数の値は、4行目に示されている16進数IDに対応します 。

  • 検証された残高 (45行目): 回収後、ソースアカウントの残高は 300 になっており、 700 絶対単位が正常に回収されたことが確認されました 。

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

結論⚓︎

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

ステップ 関連ドキュメント
アカウント残高を確認する /accounts/{accountId} GET
モザイクユニットを回収する , MosaicSupplyRevocationTransactionV1
回収を検証する /accounts/{accountId} GET