importasyncioimportjsonimportosimportrandomimporturllib.requestimportuuidimportstomperfromsymbolchain.CryptoTypesimportPrivateKeyfromsymbolchain.facade.NemFacadeimportNemFacadefromsymbolchain.ncimportAmountfromsymbolchain.nem.FeeCalculatorimportcalculate_transaction_feefromsymbolchain.nem.NetworkimportNetworkTimestampfromwebsocketsimportconnectNODE_URL=os.getenv('NODE_URL','http://libertalia.nemtest.net:7890')WS_URL=NODE_URL.replace(':7890',':7778')print(f'Using node {NODE_URL}')# SockJS has no Python client library.# These helpers wrap the raw WebSocket transport to mirror a STOMP client.defsockjs_url(endpoint_url):# SockJS raw WebSocket transport adds a random server and session idserver=random.randint(100,999)session=uuid.uuid4().hexws_base=endpoint_url.replace('http','ws',1)returnf'{ws_base}/{server}/{session}/websocket'asyncdefsend_frame(websocket,frame):# SockJS wraps each client payload as a JSON array of frame stringsawaitwebsocket.send(json.dumps([frame]))asyncdefstomp_connect(websocket):awaitwebsocket.recv()# consume the SockJS open frameawaitsend_frame(websocket,stomper.connect('','',NODE_URL,heartbeats=(0,0)))asyncdefstomp_subscribe(websocket,destination,sub_id):awaitsend_frame(websocket,stomper.subscribe(destination,sub_id))asyncdefstomp_send(websocket,destination,body):awaitsend_frame(websocket,stomper.send(destination,body))asyncdefstomp_unsubscribe(websocket,sub_id):awaitsend_frame(websocket,stomper.unsubscribe(sub_id))asyncdefstomp_disconnect(websocket):awaitsend_frame(websocket,stomper.disconnect())defstomp_messages(raw_frame):# Yield each STOMP MESSAGE frame in a SockJS data frameif'a'!=raw_frame[0]:# skip 'o' open, 'h' heartbeat, 'c' closereturnforpayloadinjson.loads(raw_frame[1:]):frame=stomper.unpack_frame(payload)if'MESSAGE'==frame['cmd']:yieldframeasyncdefstomp_frames(websocket):# Yield each STOMP MESSAGE frame as it arrivesasyncforraw_frameinwebsocket:forframeinstomp_messages(raw_frame):yieldframe# Set up the monitored address and signerMONITOR_ADDRESS=os.getenv('MONITOR_ADDRESS','TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4')print(f'Monitoring address: {MONITOR_ADDRESS}')SIGNER_PRIVATE_KEY=os.getenv('SIGNER_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000000')facade=NemFacade('testnet')signer_key_pair=NemFacade.KeyPair(PrivateKey(SIGNER_PRIVATE_KEY))asyncdefmain():# Build and sign a transfer to the monitored addresswithurllib.request.urlopen(f'{NODE_URL}/time-sync/network-time')asresp:network_time=json.loads(resp.read().decode())['receiveTimeStamp']//1000timestamp=NetworkTimestamp(network_time)deadline=timestamp.add_hours(2)transaction=facade.transaction_factory.create({'type':'transfer_transaction_v2','signer_public_key':signer_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'recipient_address':MONITOR_ADDRESS,'amount':0,})transaction.fee=Amount(calculate_transaction_fee(transaction))signature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)transaction_hash=str(facade.hash_transaction(transaction)).upper()# Connect to the WebSocketendpoint=f'{WS_URL}/w/messages'asyncwithconnect(sockjs_url(endpoint))aswebsocket:awaitstomp_connect(websocket)print(f'Connected to {WS_URL}')frames=stomp_frames(websocket)# Subscribe to the account and transaction channelsaccount_channel=f'/account/{MONITOR_ADDRESS}'channels={account_channel:'id-0',f'/unconfirmed/{MONITOR_ADDRESS}':'id-1',f'/transactions/{MONITOR_ADDRESS}':'id-2',}forchannel,sub_idinchannels.items():awaitstomp_subscribe(websocket,channel,sub_id)print(f'Subscribed to {channel} channel')# Register the account and confirm it is activeawaitstomp_send(websocket,'/w/api/account/get',json.dumps({'account':MONITOR_ADDRESS}))asyncforframeinframes:ifaccount_channel==frame['headers']['destination']:balance=json.loads(frame['body'])['account']['balance']print(f'Account update: balance={balance}')breakprint('Account registered')# Announce the transactionprint(f'Announcing transaction {transaction_hash[:16]}...')announce_request=urllib.request.Request(f'{NODE_URL}/transaction/announce',data=json_payload.encode(),headers={'Content-Type':'application/json'},method='POST')withurllib.request.urlopen(announce_request)asresp:result=json.loads(resp.read().decode())# Wait for the transaction to confirmif'SUCCESS'==result['message']:confirmed=Falseasyncforframeinframes:destination=frame['headers']['destination']body=json.loads(frame['body'])ifaccount_channel==destination:balance=body['account']['balance']print(f'Account update: balance={balance}')ifconfirmed:breakelif'/transactions/'indestination:message_hash=body['meta']['hash']['data']print(f'confirmed: hash={message_hash[:16]}...')ifmessage_hash.upper()==transaction_hash:short_hash=transaction_hash[:16]print(f'Transaction {short_hash}... confirmed')confirmed=Trueelse:message_hash=body['meta']['hash']['data']ifmessage_hash.upper()==transaction_hash:print(f'unconfirmed: hash={message_hash[:16]}...')else:print(f'Transaction rejected: {result["message"]}')# Unsubscribe before closingforsub_idinchannels.values():awaitstomp_unsubscribe(websocket,sub_id)print('Unsubscribed from all channels')awaitstomp_disconnect(websocket)try:asyncio.run(main())exceptExceptionaserror:print(error)
import{Client}from'@stomp/stompjs';importSockJSfrom'sockjs-client';import{PrivateKey}from'symbol-sdk';import{NemFacade,NetworkTimestamp,calculateTransactionFee,models}from'symbol-sdk/nem';constNODE_URL=process.env.NODE_URL||'http://libertalia.nemtest.net:7890';constWS_URL=NODE_URL.replace(':7890',':7778');console.log(`Using node ${NODE_URL}`);// Set up the monitored address and signerconstMONITOR_ADDRESS=process.env.MONITOR_ADDRESS||'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4';console.log(`Monitoring address: ${MONITOR_ADDRESS}`);constSIGNER_PRIVATE_KEY=process.env.SIGNER_PRIVATE_KEY||'0000000000000000000000000000000000000000000000000000000000000000';constfacade=newNemFacade('testnet');constsignerKeyPair=newNemFacade.KeyPair(newPrivateKey(SIGNER_PRIVATE_KEY));try{// Build and sign a transfer to the monitored addressconsttimeResponse=awaitfetch(`${NODE_URL}/time-sync/network-time`);constnetworkTime=Math.floor((awaittimeResponse.json()).receiveTimeStamp/1000);consttimestamp=newNetworkTimestamp(networkTime);constdeadline=timestamp.addHours(2);consttransaction=facade.transactionFactory.create({type:'transfer_transaction_v2',signerPublicKey:signerKeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,recipientAddress:MONITOR_ADDRESS,amount:0n});transaction.fee=newmodels.Amount(calculateTransactionFee(transaction));constsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);consttransactionHash=facade.hashTransaction(transaction).toString().toUpperCase();constshortHash=transactionHash.substring(0,16);// Connect to the WebSocketconstclient=newClient({webSocketFactory:()=>newSockJS(`${WS_URL}/w/messages`)});awaitnewPromise(resolve=>{client.onConnect=resolve;client.activate();});console.log(`Connected to ${WS_URL}`);// Wait for the transaction to confirmletconfirmed=false;letresolveRegistered;letresolveDone;constregistered=newPromise(resolve=>{resolveRegistered=resolve;});constdone=newPromise(resolve=>{resolveDone=resolve;});constonUnconfirmed=message=>{constmessageHash=JSON.parse(message.body).meta.hash.data;if(messageHash.toUpperCase()===transactionHash){console.log(`unconfirmed: hash=${messageHash.substring(0,16)}...`);}};constonConfirmed=message=>{constmessageHash=JSON.parse(message.body).meta.hash.data;console.log(`confirmed: hash=${messageHash.substring(0,16)}...`);if(messageHash.toUpperCase()===transactionHash){console.log(`Transaction ${shortHash}... confirmed`);confirmed=true;}};constonAccountUpdate=message=>{const{balance}=JSON.parse(message.body).account;console.log(`Account update: balance=${balance}`);resolveRegistered();if(confirmed)resolveDone();};// Subscribe to the account and transaction channelsconstaccountChannel=`/account/${MONITOR_ADDRESS}`;constsubscriptions=[{channel:accountChannel,handler:onAccountUpdate,id:'id-0'},{channel:`/unconfirmed/${MONITOR_ADDRESS}`,handler:onUnconfirmed,id:'id-1'},{channel:`/transactions/${MONITOR_ADDRESS}`,handler:onConfirmed,id:'id-2'}];for(const{channel,handler,id}ofsubscriptions){client.subscribe(channel,handler,{id});console.log(`Subscribed to ${channel} channel`);}// Register the account and confirm it is activeclient.publish({destination:'/w/api/account/get',body:JSON.stringify({account:MONITOR_ADDRESS})});awaitregistered;console.log('Account registered');// Announce the transactionconsole.log(`Announcing transaction ${shortHash}...`);constresponse=awaitfetch(`${NODE_URL}/transaction/announce`,{method:'POST',headers:{'Content-Type':'application/json'},body:jsonPayload});constannounceResult=awaitresponse.json();// Wait for the transaction to confirmif('SUCCESS'===announceResult.message)awaitdone;elseconsole.log(`Transaction rejected: ${announceResult.message}`);// Unsubscribe before closingfor(const{id}ofsubscriptions)client.unsubscribe(id);console.log('Unsubscribed from all channels');client.deactivate();}catch(error){console.error(error);}
# Set up the monitored address and signerMONITOR_ADDRESS=os.getenv('MONITOR_ADDRESS','TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4')print(f'Monitoring address: {MONITOR_ADDRESS}')SIGNER_PRIVATE_KEY=os.getenv('SIGNER_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000000')facade=NemFacade('testnet')signer_key_pair=NemFacade.KeyPair(PrivateKey(SIGNER_PRIVATE_KEY))
// Set up the monitored address and signerconstMONITOR_ADDRESS=process.env.MONITOR_ADDRESS||'TBULEAUG2CZQISUR442HWA6UAKGWIXHDABJVIPS4';console.log(`Monitoring address: ${MONITOR_ADDRESS}`);constSIGNER_PRIVATE_KEY=process.env.SIGNER_PRIVATE_KEY||'0000000000000000000000000000000000000000000000000000000000000000';constfacade=newNemFacade('testnet');constsignerKeyPair=newNemFacade.KeyPair(newPrivateKey(SIGNER_PRIVATE_KEY));
この手順では、監視するアドレスと、そこへ送金するアカウントをセットアップします。
MONITOR_ADDRESS は監視するアドレスです。
このチュートリアルがサブスクライブするチャネルはこのアドレスに対応付けられ、送金の送信者や受取人など、トランザクションに関係するたびに通知します。
WebSocket API では、アドレスを大文字かつハイフンなしで指定します。
# Build and sign a transfer to the monitored addresswithurllib.request.urlopen(f'{NODE_URL}/time-sync/network-time')asresp:network_time=json.loads(resp.read().decode())['receiveTimeStamp']//1000timestamp=NetworkTimestamp(network_time)deadline=timestamp.add_hours(2)transaction=facade.transaction_factory.create({'type':'transfer_transaction_v2','signer_public_key':signer_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'recipient_address':MONITOR_ADDRESS,'amount':0,})transaction.fee=Amount(calculate_transaction_fee(transaction))signature=facade.sign_transaction(signer_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)transaction_hash=str(facade.hash_transaction(transaction)).upper()
// Build and sign a transfer to the monitored addressconsttimeResponse=awaitfetch(`${NODE_URL}/time-sync/network-time`);constnetworkTime=Math.floor((awaittimeResponse.json()).receiveTimeStamp/1000);consttimestamp=newNetworkTimestamp(networkTime);constdeadline=timestamp.addHours(2);consttransaction=facade.transactionFactory.create({type:'transfer_transaction_v2',signerPublicKey:signerKeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,recipientAddress:MONITOR_ADDRESS,amount:0n});transaction.fee=newmodels.Amount(calculateTransactionFee(transaction));constsignature=facade.signTransaction(signerKeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);consttransactionHash=facade.hashTransaction(transaction).toString().toUpperCase();constshortHash=transactionHash.substring(0,16);
# Connect to the WebSocketendpoint=f'{WS_URL}/w/messages'asyncwithconnect(sockjs_url(endpoint))aswebsocket:awaitstomp_connect(websocket)print(f'Connected to {WS_URL}')frames=stomp_frames(websocket)
// Connect to the WebSocketconstclient=newClient({webSocketFactory:()=>newSockJS(`${WS_URL}/w/messages`)});awaitnewPromise(resolve=>{client.onConnect=resolve;client.activate();});console.log(`Connected to ${WS_URL}`);
# Subscribe to the account and transaction channelsaccount_channel=f'/account/{MONITOR_ADDRESS}'channels={account_channel:'id-0',f'/unconfirmed/{MONITOR_ADDRESS}':'id-1',f'/transactions/{MONITOR_ADDRESS}':'id-2',}forchannel,sub_idinchannels.items():awaitstomp_subscribe(websocket,channel,sub_id)print(f'Subscribed to {channel} channel')
// Subscribe to the account and transaction channelsconstaccountChannel=`/account/${MONITOR_ADDRESS}`;constsubscriptions=[{channel:accountChannel,handler:onAccountUpdate,id:'id-0'},{channel:`/unconfirmed/${MONITOR_ADDRESS}`,handler:onUnconfirmed,id:'id-1'},{channel:`/transactions/${MONITOR_ADDRESS}`,handler:onConfirmed,id:'id-2'}];for(const{channel,handler,id}ofsubscriptions){client.subscribe(channel,handler,{id});console.log(`Subscribed to ${channel} channel`);}
# Register the account and confirm it is activeawaitstomp_send(websocket,'/w/api/account/get',json.dumps({'account':MONITOR_ADDRESS}))asyncforframeinframes:ifaccount_channel==frame['headers']['destination']:balance=json.loads(frame['body'])['account']['balance']print(f'Account update: balance={balance}')breakprint('Account registered')
// Register the account and confirm it is activeclient.publish({destination:'/w/api/account/get',body:JSON.stringify({account:MONITOR_ADDRESS})});awaitregistered;console.log('Account registered');
# Wait for the transaction to confirmif'SUCCESS'==result['message']:confirmed=Falseasyncforframeinframes:destination=frame['headers']['destination']body=json.loads(frame['body'])ifaccount_channel==destination:balance=body['account']['balance']print(f'Account update: balance={balance}')ifconfirmed:breakelif'/transactions/'indestination:message_hash=body['meta']['hash']['data']print(f'confirmed: hash={message_hash[:16]}...')ifmessage_hash.upper()==transaction_hash:short_hash=transaction_hash[:16]print(f'Transaction {short_hash}... confirmed')confirmed=Trueelse:message_hash=body['meta']['hash']['data']ifmessage_hash.upper()==transaction_hash:print(f'unconfirmed: hash={message_hash[:16]}...')else:print(f'Transaction rejected: {result["message"]}')
// Wait for the transaction to confirmletconfirmed=false;letresolveRegistered;letresolveDone;constregistered=newPromise(resolve=>{resolveRegistered=resolve;});constdone=newPromise(resolve=>{resolveDone=resolve;});constonUnconfirmed=message=>{constmessageHash=JSON.parse(message.body).meta.hash.data;if(messageHash.toUpperCase()===transactionHash){console.log(`unconfirmed: hash=${messageHash.substring(0,16)}...`);}};constonConfirmed=message=>{constmessageHash=JSON.parse(message.body).meta.hash.data;console.log(`confirmed: hash=${messageHash.substring(0,16)}...`);if(messageHash.toUpperCase()===transactionHash){console.log(`Transaction ${shortHash}... confirmed`);confirmed=true;}};constonAccountUpdate=message=>{const{balance}=JSON.parse(message.body).account;console.log(`Account update: balance=${balance}`);resolveRegistered();if(confirmed)resolveDone();};
# Unsubscribe before closingforsub_idinchannels.values():awaitstomp_unsubscribe(websocket,sub_id)print('Unsubscribed from all channels')awaitstomp_disconnect(websocket)