importjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportPrivateKeyfromsymbolchain.facade.SymbolFacadeimportSymbolFacadefromsymbolchain.scimportAmountfromsymbolchain.symbol.IdGeneratorimportgenerate_mosaic_alias_idfromsymbolchain.symbol.NetworkimportNetworkTimestampNODE_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')try:# Fetch current network timetime_path='/node/time'print(f'Fetching current network time from {time_path}')withurllib.request.urlopen(f'{NODE_URL}{time_path}')asresponse:response_json=json.loads(response.read().decode())timestamp=NetworkTimestamp(int(response_json['communicationTimestamps']['receiveTimestamp']))print(f' Network time: {timestamp.timestamp} ms since nemesis')# Fetch recommended feesfee_path='/network/fees/transaction'print(f'Fetching recommended fees from {fee_path}')withurllib.request.urlopen(f'{NODE_URL}{fee_path}')asresponse: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 transactiontransaction=facade.transaction_factory.create({'type':'transfer_transaction_v1','signer_public_key':signer_key_pair.public_key,'deadline':timestamp.add_hours(2).timestamp,'recipient_address':facade.network.public_key_to_address(signer_key_pair.public_key),'mosaics':[{'mosaic_id':generate_mosaic_alias_id('symbol.xym'),'amount':1_000_000# 1 XYM}]})transaction.fee=Amount(fee_multiplier*transaction.size)# Sign transaction and generate final payloadsignature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)print('Built transaction:')print(json.dumps(transaction.to_json(),indent=2))# Announce the transactionannounce_path='/transactions'print(f'Announcing transaction to {announce_path}')announce_request=urllib.request.Request(f'{NODE_URL}{announce_path}',data=json_payload.encode(),headers={'Content-Type':'application/json'},method='PUT')withurllib.request.urlopen(announce_request)asresponse:print(f' Response: {response.read().decode()}')# Wait for confirmationstatus_path=(f'/transactionStatus/{facade.hash_transaction(transaction)}')print(f'Waiting for confirmation from {status_path}')forattemptinrange(60):try:withurllib.request.urlopen(f'{NODE_URL}{status_path}')asresponse:status=json.loads(response.read().decode())print(f' Transaction status: {status['group']}')ifstatus['group']=='confirmed':print(f'Transaction confirmed in {attempt} seconds')breakifstatus['group']=='failed':print(f'Transaction failed: {status['code']}')breakexcepturllib.error.HTTPErrorase:print(f' Transaction status: unknown | Cause: ({e.msg})')time.sleep(1)else:print('Confirmation took too long.')excepturllib.error.URLErrorase:print(e.reason)
import{PrivateKey}from'symbol-sdk';import{NetworkTimestamp,SymbolFacade,generateMosaicAliasId,models}from'symbol-sdk/symbol';constNODE_URL=process.env.NODE_URL||'https://reference.symboltest.net:3001';console.log('Using node',NODE_URL);constSIGNER_PRIVATE_KEY=process.env.SIGNER_PRIVATE_KEY||'0000000000000000000000000000000000000000000000000000000000000000';constsignerKeyPair=newSymbolFacade.KeyPair(newPrivateKey(SIGNER_PRIVATE_KEY));constfacade=newSymbolFacade('testnet');try{// Fetch current network timeconsttimePath='/node/time';console.log('Fetching current network time from',timePath);consttimeResponse=awaitfetch(`${NODE_URL}${timePath}`);consttimeJSON=awaittimeResponse.json();consttimestamp=newNetworkTimestamp(timeJSON.communicationTimestamps.receiveTimestamp);console.log(' Network time:',timestamp.timestamp,'ms since nemesis');// Fetch recommended feesconstfeePath='/network/fees/transaction';console.log('Fetching recommended fees from',feePath);constfeeResponse=awaitfetch(`${NODE_URL}${feePath}`);constfeeJSON=awaitfeeResponse.json();constmedianMultiplier=feeJSON.medianFeeMultiplier;constminimumMultiplier=feeJSON.minFeeMultiplier;constfeeMultiplier=Math.max(medianMultiplier,minimumMultiplier);console.log(' Fee multiplier:',feeMultiplier);// Build the transactionconsttransaction=facade.transactionFactory.create({type:'transfer_transaction_v1',signerPublicKey:signerKeyPair.publicKey.toString(),deadline:timestamp.addHours(2).timestamp,recipientAddress:facade.network.publicKeyToAddress(signerKeyPair.publicKey).toString(),mosaics:[{mosaicId:generateMosaicAliasId('symbol.xym'),amount:1_000_000n// 1 XYM}]});transaction.fee=newmodels.Amount(feeMultiplier*transaction.size);// Sign transaction and generate final payloadconstsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);console.log('Built transaction:');console.dir(transaction.toJson(),{colors:true});// Announce the transactionconstannouncePath='/transactions';console.log('Announcing transaction to',announcePath);constannounceResponse=awaitfetch(`${NODE_URL}${announcePath}`,{method:'PUT',headers:{'Content-Type':'application/json'},body:jsonPayload});console.log(' Response:',awaitannounceResponse.text());// Wait for confirmationconsttransactionHash=facade.hashTransaction(transaction).toString();conststatusPath=`/transactionStatus/${transactionHash}`;console.log('Waiting for confirmation from',statusPath);for(letattempt=1;60>=attempt;++attempt){constresponse=awaitfetch(`${NODE_URL}${statusPath}`);if(response.ok){conststatus=awaitresponse.json();console.log(' Transaction status:',status.group);if('confirmed'===status.group){console.log('Transaction confirmed in',attempt,'seconds');break;}if('failed'===status.group){console.log('Transaction failed:',status.code);break;}}else{console.log(' Transaction status: unknown | Cause:',response.status);}awaitnewPromise(resolve=>{setTimeout(resolve,1000);});if(60===attempt)console.warn('Confirmation took too long.');}}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
# Fetch current network timetime_path='/node/time'print(f'Fetching current network time from {time_path}')withurllib.request.urlopen(f'{NODE_URL}{time_path}')asresponse:response_json=json.loads(response.read().decode())timestamp=NetworkTimestamp(int(response_json['communicationTimestamps']['receiveTimestamp']))print(f' Network time: {timestamp.timestamp} ms since nemesis')
// Fetch current network timeconsttimePath='/node/time';console.log('Fetching current network time from',timePath);consttimeResponse=awaitfetch(`${NODE_URL}${timePath}`);consttimeJSON=awaittimeResponse.json();consttimestamp=newNetworkTimestamp(timeJSON.communicationTimestamps.receiveTimestamp);console.log(' Network time:',timestamp.timestamp,'ms since nemesis');
Symbol の トランザクション には、有効期限(deadline)を含める必要があります。これは、ネットワークがトランザクションを破棄する前に承認を試みる期間を定義します。
有効期限は絶対的なネットワーク時間で表されるため、最初のステップは ノード から現在のネットワーク時間を取得することです。
ネットワーク時間とは何ですか?
Symbol では、時間を最初のブロックである ネメシスブロック(他のブロックチェーンではジェネシスブロックとして知られています)の作成から経過した秒数として定義しています。
すべてのトランザクションの有効期限とタイムスタンプは、この起点を基準に計算されます。
UTC などのより人間が理解しやすい形式でタイムスタンプを表示したい場合は、ネットワークプロパティから取得できるネメシスブロックのタイムスタンプを加算する必要があります。
# Sign transaction and generate final payloadsignature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)print('Built transaction:')print(json.dumps(transaction.to_json(),indent=2))
// Sign transaction and generate final payloadconstsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);console.log('Built transaction:');console.dir(transaction.toJson(),{colors:true});
# Announce the transactionannounce_path='/transactions'print(f'Announcing transaction to {announce_path}')announce_request=urllib.request.Request(f'{NODE_URL}{announce_path}',data=json_payload.encode(),headers={'Content-Type':'application/json'},method='PUT')withurllib.request.urlopen(announce_request)asresponse:print(f' Response: {response.read().decode()}')
# Wait for confirmationstatus_path=(f'/transactionStatus/{facade.hash_transaction(transaction)}')print(f'Waiting for confirmation from {status_path}')forattemptinrange(60):try:withurllib.request.urlopen(f'{NODE_URL}{status_path}')asresponse:status=json.loads(response.read().decode())print(f' Transaction status: {status['group']}')ifstatus['group']=='confirmed':print(f'Transaction confirmed in {attempt} seconds')breakifstatus['group']=='failed':print(f'Transaction failed: {status['code']}')breakexcepturllib.error.HTTPErrorase:print(f' Transaction status: unknown | Cause: ({e.msg})')time.sleep(1)else:print('Confirmation took too long.')
// Wait for confirmationconsttransactionHash=facade.hashTransaction(transaction).toString();conststatusPath=`/transactionStatus/${transactionHash}`;console.log('Waiting for confirmation from',statusPath);for(letattempt=1;60>=attempt;++attempt){constresponse=awaitfetch(`${NODE_URL}${statusPath}`);if(response.ok){conststatus=awaitresponse.json();console.log(' Transaction status:',status.group);if('confirmed'===status.group){console.log('Transaction confirmed in',attempt,'seconds');break;}if('failed'===status.group){console.log('Transaction failed:',status.code);break;}}else{console.log(' Transaction status: unknown | Cause:',response.status);}awaitnewPromise(resolve=>{setTimeout(resolve,1000);});if(60===attempt)console.warn('Confirmation took too long.');}
ネットワークの観点からトランザクションを確認するには、 Symbol Testnet Explorer にアクセスし、トランザクションハッシュを検索します。
ハッシュは Waiting for confirmation from /transactionStatus/... と書かれた行に出力されています。
トランザクションが承認プロセスを進む様子をリアルタイムで確認できるはずです。