モザイクの回収(リボーク)
初級
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進数識別子を指定します 。
アカウントが保持しているモザイクを一覧表示するには、アカウント残高の照会 を参照してください 。
推奨手数料の取得
転送トランザクション チュートリアルで説明されているプロセスに従い、推奨手数料を /network/fees/transaction GET から取得します。
初期残高の確認
回収を行う前に、ヘルパー関数 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);
|
回収トランザクションは、ソースアカウントからモザイクユニットを回収し、作成者の残高に戻します 。
部分的な回収
数量はソースアカウントの全残高と一致する必要はありません 。
単一のトランザクションで、ソースの現在の保有量までの任意の数量を回収できます 。
回収の送信
| # 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");
|
回収トランザクションは、転送トランザクション チュートリアルと同じプロセスに従って署名され、アナウンスされます。
コードはその後、ステータスが confirmed に変わるまで /transactionStatus/{hash} GET エンドポイントをポーリングして、トランザクションが承認されるのを待ちます 。
回収の検証
回収を検証するために、ヘルパー関数 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 でトランザクションを検索できます 。
結論
このチュートリアルでは、以下の方法を説明しました。