中級
モザイク定義の変更
ネットワーク上に モザイク のユニットが存在しない限り、作成者は同じ識別子を使用して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.Network import NetworkTimestamp
from symbolchain.symbol.IdGenerator import generate_mosaic_id
from symbolchain.sc import Amount
NODE_URL = os . getenv (
'NODE_URL' , 'https://reference.symboltest.net:3001' )
print ( f 'Using node { NODE_URL } ' )
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 current network time
time_path = '/node/time'
print ( f 'Fetching current network time from { time_path } ' )
with urllib . request . urlopen (
f ' { NODE_URL }{ time_path } ' ) as response :
response_json = json . loads ( response . read () . decode ())
receive_timestamp = (
response_json [ 'communicationTimestamps' ][ 'receiveTimestamp' ])
timestamp = NetworkTimestamp ( int ( receive_timestamp ))
print ( f ' Network time: { timestamp . timestamp } ms since nemesis' )
# 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_mult = response_json [ 'medianFeeMultiplier' ]
minimum_mult = response_json [ 'minFeeMultiplier' ]
fee_mult = max ( median_mult , minimum_mult )
print ( f ' Fee multiplier: { fee_mult } ' )
# 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 } ( { hex ( mosaic_id ) } )' )
modify_tx = facade . transaction_factory . create ({
'type' : 'mosaic_definition_transaction_v1' ,
'signer_public_key' : signer_key_pair . public_key ,
'deadline' : timestamp . add_hours ( 2 ) . timestamp ,
'duration' : 0 ,
'divisibility' : 0 ,
'nonce' : MOSAIC_NONCE ,
'flags' : 'revokable'
})
modify_tx . fee = Amount ( fee_mult * modify_tx . size )
# 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
print ( 'Announcing mosaic modification to /transactions' )
request = urllib . request . Request (
f ' { NODE_URL } /transactions' ,
data = json_payload . encode (),
headers = { 'Content-Type' : 'application/json' },
method = 'PUT'
)
with urllib . request . urlopen ( request ) as response :
print ( f ' Response: { response . read () . decode () } ' )
# Wait for confirmation
print ( 'Waiting for mosaic modification confirmation...' )
for attempt in range ( 60 ):
time . sleep ( 1 )
try :
status_url = (
f ' { NODE_URL } /transactionStatus/ { modify_hash } ' )
with urllib . request . urlopen ( status_url ) as response :
status = json . loads ( response . read () . decode ())
print ( f ' Transaction status: { status [ "group" ] } ' )
if status [ 'group' ] == 'confirmed' :
print ( 'Mosaic modification confirmed in' ,
attempt , 'seconds' )
break
if status [ 'group' ] == 'failed' :
raise Exception (
'Mosaic modification failed:' , status [ 'code' ])
except urllib . error . HTTPError :
print ( ' Transaction status: unknown' )
# Retrieve the mosaic
mosaic_id_hex = f ' { mosaic_id : x } '
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 ,
NetworkTimestamp ,
models ,
generateMosaicId
} from 'symbol-sdk/symbol' ;
const NODE_URL = process . env . NODE_URL
|| 'https://reference.symboltest.net:3001' ;
console . log ( 'Using node' , NODE_URL );
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 current network time
const timePath = '/node/time' ;
console . log ( 'Fetching current network time from' , timePath );
const timeResponse = await fetch ( ` ${ NODE_URL }${ timePath } ` );
const timeJSON = await timeResponse . json ();
const timestamp = new NetworkTimestamp (
timeJSON . communicationTimestamps . receiveTimestamp );
console . log (
' Network time:' , timestamp . timestamp , 'ms since nemesis' );
// 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 medianMult = feeJSON . medianFeeMultiplier ;
const minimumMult = feeJSON . minFeeMultiplier ;
const feeMult = Math . max ( medianMult , minimumMult );
console . log ( ' Fee multiplier:' , feeMult );
// 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 );
console . log (
`Mosaic ID: ${ mosaicId } (0x ${ mosaicId . toString ( 16 ) } )` );
const modifyTx = facade . transactionFactory . create ({
type : 'mosaic_definition_transaction_v1' ,
signerPublicKey : signerKeyPair . publicKey . toString (),
deadline : timestamp . addHours ( 2 ). timestamp ,
duration : 0n ,
divisibility : 0 ,
nonce : MOSAIC_NONCE ,
flags : 'revokable'
});
modifyTx . fee = new models . Amount ( feeMult * modifyTx . size );
// 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
console . log (
'Announcing mosaic modification to /transactions' );
const announceResponse = await fetch (
` ${ NODE_URL } /transactions` , {
method : 'PUT' ,
headers : { 'Content-Type' : 'application/json' },
body : jsonPayload
});
console . log ( ' Response:' , await announceResponse . text ());
// Wait for confirmation
console . log ( 'Waiting for mosaic modification confirmation...' );
for ( let attempt = 0 ; attempt < 60 ; attempt ++ ) {
await new Promise ( resolve => setTimeout ( resolve , 1000 ));
try {
const statusUrl =
` ${ NODE_URL } /transactionStatus/ ${ modifyHash } ` ;
const statusResponse = await fetch ( statusUrl );
if ( ! statusResponse . ok ) {
console . log ( ' Transaction status: unknown' );
continue ;
}
const status = await statusResponse . json ();
console . log ( ' Transaction status:' , status . group );
if ( status . group === 'confirmed' ) {
console . log ( 'Mosaic modification confirmed in' ,
attempt , 'seconds' );
break ;
}
if ( status . group === 'failed' ) {
throw new Error (
`Mosaic modification failed: ${ status . code } ` );
}
} catch ( error ) {
if ( error . message . includes ( 'failed:' )) {
throw error ;
}
console . log ( ' Transaction status: unknown' );
}
}
// Retrieve the mosaic
const mosaicIdHex = mosaicId . toString ( 16 );
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
コード解説
アカウントの設定
このスニペットは、署名者の 秘密鍵 を SIGNER_PRIVATE_KEY 環境変数から読み取ります。設定されていない場合はデフォルトのテストキーが使用されます。
署名者の アドレス は 公開鍵 から派生します。
この アカウント は、そのモザイクの元の作成者である必要があります。
ネットワーク時間と手数料の取得
転送トランザクション チュートリアルで説明されているプロセスに従い、ネットワーク時間と推奨手数料をそれぞれ /node/time GET および /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 } ( { hex ( mosaic_id ) } )' )
modify_tx = facade . transaction_factory . create ({
'type' : 'mosaic_definition_transaction_v1' ,
'signer_public_key' : signer_key_pair . public_key ,
'deadline' : timestamp . add_hours ( 2 ) . timestamp ,
'duration' : 0 ,
'divisibility' : 0 ,
'nonce' : MOSAIC_NONCE ,
'flags' : 'revokable'
})
modify_tx . fee = Amount ( fee_mult * modify_tx . size )
// 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 );
console . log (
`Mosaic ID: ${ mosaicId } (0x ${ mosaicId . toString ( 16 ) } )` );
const modifyTx = facade . transactionFactory . create ({
type : 'mosaic_definition_transaction_v1' ,
signerPublicKey : signerKeyPair . publicKey . toString (),
deadline : timestamp . addHours ( 2 ). timestamp ,
duration : 0n ,
divisibility : 0 ,
nonce : MOSAIC_NONCE ,
flags : 'revokable'
});
modifyTx . fee = new models . Amount ( feeMult * modifyTx . size );
MOSAIC_NONCE 環境変数は、変更するモザイクの ノンス を指定します。同じモザイクを対象とするために、モザイクの作成 時に使用したものと一致させる必要があります。
変更トランザクションは、最初の作成時と同じ mosaic_definition_transaction_v1 タイプを使用します。
大きな違いは、ノンスが新規作成ではなく既存のモザイクを指している 点です。
トランザクションを処理する際、各プロパティは以下のルールに従って現在の値と組み合わされます。
フラグ は、現在のフラグと 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 に設定すると現在の期間には何も加算されません。
変更の送信
# 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
print ( 'Announcing mosaic modification to /transactions' )
request = urllib . request . Request (
f ' { NODE_URL } /transactions' ,
data = json_payload . encode (),
headers = { 'Content-Type' : 'application/json' },
method = 'PUT'
)
with urllib . request . urlopen ( request ) as response :
print ( f ' Response: { response . read () . decode () } ' )
// 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
console . log (
'Announcing mosaic modification to /transactions' );
const announceResponse = await fetch (
` ${ NODE_URL } /transactions` , {
method : 'PUT' ,
headers : { 'Content-Type' : 'application/json' },
body : jsonPayload
});
console . log ( ' Response:' , await announceResponse . text ());
変更トランザクションは、転送トランザクションの作成 と同じプロセスに従って署名され、アナウンスされます。
コードはその後、ステータスが confirmed に変わるまで /transactionStatus/{hash} GET エンドポイントをポーリングして、トランザクションが承認されるのを待ちます。
モザイクの取得
変更が適用されたことを確認するために、コードは /mosaics/{mosaicId} GET エンドポイントを使用してネットワークからモザイクを取得し、更新されたプロパティを表示します。
レスポンスが成功すれば、モザイクが期待通りのフラグ値を持っていることが確認されます。
出力
以下に示す出力は、プログラムの典型的な実行結果に対応しています。
Using node https://reference.symboltest.net:3001
Signer address: TCHBDENCLKEBILBPWP3JPB2XNY64OE7PYHHE32I
Fetching current network time from /node/time
Network time: 103748303591 ms since nemesis
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
出力の主なポイント:
モザイクノンス (7行目): ノンス 1770998662 はモザイク作成時に使用されたものと一致しており、同じモザイクを変更対象としています。
モザイクID (8行目): モザイクID 0x5bdd3795f7a8b3af はノンスと署名者のアドレスから派生しており、正しいモザイクが変更されていることが確認できます。
トランザクションプロパティ (19、21-22行目): 変更により duration が 0(変更なし)、 flags が 8( revokable )、 divisibility が 0(変更なし)に設定されています。
更新されたプロパティ (36-38行目): モザイクの可分性は 2 のままです( 0 との XOR により不変)。
フラグは 14 になっており、これは transferable (2) + restrictable (4) + revokable (8) に対応します。
有効期間は 0(無期限、不変)のままです。
出力に印刷されたトランザクション ハッシュ を使用して、 Symbol Testnet Explorer でトランザクションを検索できます。
結論
このチュートリアルでは、以下の方法を説明しました。