importjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportPrivateKey,PublicKeyfromsymbolchain.facade.NemFacadeimportNemFacadefromsymbolchain.ncimportAmountfromsymbolchain.nem.FeeCalculatorimportcalculate_transaction_feefromsymbolchain.nem.NetworkimportNetworkTimestampNODE_URL=os.getenv('NODE_URL','http://libertalia.nemtest.net:7890')print(f'Using node {NODE_URL}')# Helper function to announce a transactiondefannounce_transaction(payload,label):announce_path='/transaction/announce'print(f'Announcing {label} to {announce_path}')request=urllib.request.Request(f'{NODE_URL}{announce_path}',data=payload.encode(),headers={'Content-Type':'application/json'},method='POST')withurllib.request.urlopen(request)asannounce_response:result=json.loads(announce_response.read().decode())print(f' Result: {result["message"]}')returnresult['message']# Helper function to wait for transaction confirmationdefwait_for_confirmation(tx_hash,label):status_path=f'/transaction/get?hash={tx_hash}'print(f'Waiting for {label} confirmation from {status_path}')is_confirmed=Falsefor_inrange(120):try:withurllib.request.urlopen(f'{NODE_URL}{status_path}')asstatus_response:confirmed=json.loads(status_response.read().decode())height=confirmed['meta']['height']print(f'{label} confirmed in block {height}')is_confirmed=Truebreakexcepturllib.error.HTTPError:print(' Transaction status: pending')time.sleep(1)ifnotis_confirmed:print(f'{label} confirmation took too long.')facade=NemFacade('testnet')MULTISIG_PUBLIC_KEY=os.getenv('MULTISIG_PUBLIC_KEY','D656155B48D4E71E4C59EC6FAEB5EB4F214DE8BC3C65D5BF6A3D9931B4E5ACF2')multisig_public_key=PublicKey(MULTISIG_PUBLIC_KEY)multisig_address=facade.network.public_key_to_address(multisig_public_key)print(f'Multisig public key: {multisig_public_key}')COSIGNATORY0_PRIVATE_KEY=os.getenv('COSIGNATORY0_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000002')cosignatory0_key_pair=NemFacade.KeyPair(PrivateKey(COSIGNATORY0_PRIVATE_KEY))print(f'Cosignatory 0 public key: {cosignatory0_key_pair.public_key}')COSIGNATORY1_PRIVATE_KEY=os.getenv('COSIGNATORY1_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000003')cosignatory1_key_pair=NemFacade.KeyPair(PrivateKey(COSIGNATORY1_PRIVATE_KEY))print(f'Cosignatory 1 public key: {cosignatory1_key_pair.public_key}')try:# Fetch current network timetime_path='/time-sync/network-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())network_time=response_json['receiveTimeStamp']//1000print(f' Network time: {network_time} s since the nemesis block')# Derived fields from network timetimestamp=NetworkTimestamp(network_time)deadline=timestamp.add_hours(2)# Build the inner transfer transactiontransfer_transaction=facade.transaction_factory.create({'type':'transfer_transaction_v2','signer_public_key':multisig_public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'recipient_address':multisig_address,'amount':1_000_000# 1 XEM})transfer_transaction.fee=Amount(calculate_transaction_fee(transfer_transaction))# Build the wrapper multisig transactiontransaction=facade.transaction_factory.create({'type':'multisig_transaction_v1',# This is the cosignatory that initiates the transfer'signer_public_key':cosignatory0_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'inner_transaction':facade.transaction_factory.to_non_verifiable_transaction(transfer_transaction)})transaction.fee=Amount(calculate_transaction_fee(transaction))# Sign and announce the multisig transactionsignature=facade.sign_transaction(cosignatory0_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)print('Built multisig transaction:')print(json.dumps(transaction.to_json(),indent=2))announce_result=announce_transaction(json_payload,'multisig transaction')# The transaction is now waiting for the second signature# Retrieve the pending transaction from the networkif'SUCCESS'==announce_result:cosignatory1_address=facade.network.public_key_to_address(cosignatory1_key_pair.public_key)unconfirmed_path=('/account/unconfirmedTransactions'f'?address={cosignatory1_address}')print(f'Fetching pending transactions from {unconfirmed_path}')withurllib.request.urlopen(f'{NODE_URL}{unconfirmed_path}')asresponse:pending=json.loads(response.read().decode())['data']# Select the pending transaction issued by the multisig accountinner_transaction_hash=next(entry['meta']['data']forentryinpendingifentry['transaction'].get('otherTrans',{}).get('signer','').upper()==str(multisig_public_key))print(f' Inner transaction hash: {inner_transaction_hash}')# Build the cosignaturecosignature=facade.transaction_factory.create({'type':'cosignature_v1',# This is the cosignatory providing the second signature'signer_public_key':cosignatory1_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,# Hash of the inner transfer transaction'other_transaction_hash':inner_transaction_hash,# Address of the multisig account'multisig_account_address':multisig_address})cosignature.fee=Amount(calculate_transaction_fee(cosignature))# Sign and announce the cosignaturecosignature_signature=facade.sign_transaction(cosignatory1_key_pair,cosignature)cosignature_payload=facade.transaction_factory.attach_signature(cosignature,cosignature_signature)print('Built cosignature:')print(json.dumps(cosignature.to_json(),indent=2))cosignature_result=announce_transaction(cosignature_payload,'cosignature')# Wait for the multisig transaction to be confirmedif'SUCCESS'==cosignature_result:wait_for_confirmation(facade.hash_transaction(transaction),'multisig transaction')else:print(f'Transaction rejected: {cosignature_result}')else:print(f'Transaction rejected: {announce_result}')excepturllib.error.URLErrorase:print(e.reason)
import{PrivateKey,PublicKey}from'symbol-sdk';import{NemFacade,NetworkTimestamp,calculateTransactionFee,models}from'symbol-sdk/nem';constNODE_URL=process.env.NODE_URL||'http://libertalia.nemtest.net:7890';console.log('Using node',NODE_URL);// Helper function to announce a transactionasyncfunctionannounceTransaction(payload,label){constannouncePath='/transaction/announce';console.log(`Announcing ${label} to ${announcePath}`);constannounceResponse=awaitfetch(`${NODE_URL}${announcePath}`,{method:'POST',headers:{'Content-Type':'application/json'},body:payload});constresult=awaitannounceResponse.json();console.log(' Result:',result.message);returnresult.message;}// Helper function to wait for transaction confirmationasyncfunctionwaitForConfirmation(transactionHash,label){conststatusPath=`/transaction/get?hash=${transactionHash}`;console.log(`Waiting for ${label} confirmation from`,statusPath);letisConfirmed=false;for(letattempt=1;120>=attempt;++attempt){constresponse=awaitfetch(`${NODE_URL}${statusPath}`);if(response.ok){constconfirmed=awaitresponse.json();console.log(`${label} confirmed in block`,confirmed.meta.height);isConfirmed=true;break;}console.log(' Transaction status: pending');awaitnewPromise(resolve=>{setTimeout(resolve,1000);});}if(!isConfirmed)console.warn(`${label} confirmation took too long.`);}constfacade=newNemFacade('testnet');constMULTISIG_PUBLIC_KEY=process.env.MULTISIG_PUBLIC_KEY||('D656155B48D4E71E4C59EC6FAEB5EB4F214DE8BC3C65D5BF6A3D9931B4E5ACF2');constmultisigPublicKey=newPublicKey(MULTISIG_PUBLIC_KEY);constmultisigAddress=facade.network.publicKeyToAddress(multisigPublicKey);console.log(`Multisig public key: ${multisigPublicKey}`);constCOSIGNATORY0_PRIVATE_KEY=process.env.COSIGNATORY0_PRIVATE_KEY||('0000000000000000000000000000000000000000000000000000000000000002');constcosignatory0KeyPair=newNemFacade.KeyPair(newPrivateKey(COSIGNATORY0_PRIVATE_KEY));console.log(`Cosignatory 0 public key: ${cosignatory0KeyPair.publicKey}`);constCOSIGNATORY1_PRIVATE_KEY=process.env.COSIGNATORY1_PRIVATE_KEY||('0000000000000000000000000000000000000000000000000000000000000003');constcosignatory1KeyPair=newNemFacade.KeyPair(newPrivateKey(COSIGNATORY1_PRIVATE_KEY));console.log(`Cosignatory 1 public key: ${cosignatory1KeyPair.publicKey}`);try{// Fetch current network timeconsttimePath='/time-sync/network-time';console.log('Fetching current network time from',timePath);consttimeResponse=awaitfetch(`${NODE_URL}${timePath}`);consttimeJSON=awaittimeResponse.json();constnetworkTime=Math.floor(timeJSON.receiveTimeStamp/1000);console.log(' Network time:',networkTime,'s since the nemesis block');// Derived fields from network timeconsttimestamp=newNetworkTimestamp(networkTime);constdeadline=timestamp.addHours(2);// Build the inner transfer transactionconsttransferTransaction=facade.transactionFactory.create({type:'transfer_transaction_v2',signerPublicKey:multisigPublicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,recipientAddress:multisigAddress.toString(),amount:1_000_000n// 1 XEM});transferTransaction.fee=newmodels.Amount(calculateTransactionFee(transferTransaction));// Build the wrapper multisig transactionconsttransaction=facade.transactionFactory.create({type:'multisig_transaction_v1',// This is the cosignatory that initiates the transfersignerPublicKey:cosignatory0KeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,innerTransaction:facade.transactionFactory.static.toNonVerifiableTransaction(transferTransaction)});transaction.fee=newmodels.Amount(calculateTransactionFee(transaction));// Sign and announce the multisig transactionconstsignature=facade.signTransaction(cosignatory0KeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);console.log('Built multisig transaction:');console.log(JSON.stringify(transaction.toJson(),null,2));constannounceResult=awaitannounceTransaction(jsonPayload,'multisig transaction');// The transaction is now waiting for the second signature// Retrieve the pending transaction from the networkif('SUCCESS'===announceResult){constcosignatory1Address=facade.network.publicKeyToAddress(cosignatory1KeyPair.publicKey);constunconfirmedPath='/account/unconfirmedTransactions'+`?address=${cosignatory1Address}`;console.log('Fetching pending transactions from',unconfirmedPath);constunconfirmedResponse=awaitfetch(`${NODE_URL}${unconfirmedPath}`);constpending=(awaitunconfirmedResponse.json()).data;// Select the pending transaction issued by the multisig accountconstpendingEntry=pending.find(entry=>multisigPublicKey.toString()===(entry.transaction.otherTrans?.signer??'').toUpperCase());constinnerTransactionHash=pendingEntry.meta.data;console.log(' Inner transaction hash:',innerTransactionHash);// Build the cosignatureconstcosignature=facade.transactionFactory.create({type:'cosignature_v1',// This is the cosignatory providing the second signaturesignerPublicKey:cosignatory1KeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,// Hash of the inner transfer transactionotherTransactionHash:innerTransactionHash,// Address of the multisig accountmultisigAccountAddress:multisigAddress.toString()});cosignature.fee=newmodels.Amount(calculateTransactionFee(cosignature));// Sign and announce the cosignatureconstcosignatureSignature=facade.signTransaction(cosignatory1KeyPair,cosignature);constcosignaturePayload=facade.transactionFactory.static.attachSignature(cosignature,cosignatureSignature);console.log('Built cosignature:');console.log(JSON.stringify(cosignature.toJson(),null,2));constcosignatureResult=awaitannounceTransaction(cosignaturePayload,'cosignature');// Wait for the multisig transaction to be confirmedif('SUCCESS'===cosignatureResult){awaitwaitForConfirmation(facade.hashTransaction(transaction).toString(),'multisig transaction');}else{console.log('Transaction rejected:',cosignatureResult);}}else{console.log('Transaction rejected:',announceResult);}}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
MULTISIG_PUBLIC_KEY=os.getenv('MULTISIG_PUBLIC_KEY','D656155B48D4E71E4C59EC6FAEB5EB4F214DE8BC3C65D5BF6A3D9931B4E5ACF2')multisig_public_key=PublicKey(MULTISIG_PUBLIC_KEY)multisig_address=facade.network.public_key_to_address(multisig_public_key)print(f'Multisig public key: {multisig_public_key}')COSIGNATORY0_PRIVATE_KEY=os.getenv('COSIGNATORY0_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000002')cosignatory0_key_pair=NemFacade.KeyPair(PrivateKey(COSIGNATORY0_PRIVATE_KEY))print(f'Cosignatory 0 public key: {cosignatory0_key_pair.public_key}')COSIGNATORY1_PRIVATE_KEY=os.getenv('COSIGNATORY1_PRIVATE_KEY','0000000000000000000000000000000000000000000000000000000000000003')cosignatory1_key_pair=NemFacade.KeyPair(PrivateKey(COSIGNATORY1_PRIVATE_KEY))print(f'Cosignatory 1 public key: {cosignatory1_key_pair.public_key}')
constMULTISIG_PUBLIC_KEY=process.env.MULTISIG_PUBLIC_KEY||('D656155B48D4E71E4C59EC6FAEB5EB4F214DE8BC3C65D5BF6A3D9931B4E5ACF2');constmultisigPublicKey=newPublicKey(MULTISIG_PUBLIC_KEY);constmultisigAddress=facade.network.publicKeyToAddress(multisigPublicKey);console.log(`Multisig public key: ${multisigPublicKey}`);constCOSIGNATORY0_PRIVATE_KEY=process.env.COSIGNATORY0_PRIVATE_KEY||('0000000000000000000000000000000000000000000000000000000000000002');constcosignatory0KeyPair=newNemFacade.KeyPair(newPrivateKey(COSIGNATORY0_PRIVATE_KEY));console.log(`Cosignatory 0 public key: ${cosignatory0KeyPair.publicKey}`);constCOSIGNATORY1_PRIVATE_KEY=process.env.COSIGNATORY1_PRIVATE_KEY||('0000000000000000000000000000000000000000000000000000000000000003');constcosignatory1KeyPair=newNemFacade.KeyPair(newPrivateKey(COSIGNATORY1_PRIVATE_KEY));console.log(`Cosignatory 1 public key: ${cosignatory1KeyPair.publicKey}`);
# Fetch current network timetime_path='/time-sync/network-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())network_time=response_json['receiveTimeStamp']//1000print(f' Network time: {network_time} s since the nemesis block')# Derived fields from network timetimestamp=NetworkTimestamp(network_time)deadline=timestamp.add_hours(2)
// Fetch current network timeconsttimePath='/time-sync/network-time';console.log('Fetching current network time from',timePath);consttimeResponse=awaitfetch(`${NODE_URL}${timePath}`);consttimeJSON=awaittimeResponse.json();constnetworkTime=Math.floor(timeJSON.receiveTimeStamp/1000);console.log(' Network time:',networkTime,'s since the nemesis block');// Derived fields from network timeconsttimestamp=newNetworkTimestamp(networkTime);constdeadline=timestamp.addHours(2);
# Build the inner transfer transactiontransfer_transaction=facade.transaction_factory.create({'type':'transfer_transaction_v2','signer_public_key':multisig_public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'recipient_address':multisig_address,'amount':1_000_000# 1 XEM})transfer_transaction.fee=Amount(calculate_transaction_fee(transfer_transaction))
// Build the inner transfer transactionconsttransferTransaction=facade.transactionFactory.create({type:'transfer_transaction_v2',signerPublicKey:multisigPublicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,recipientAddress:multisigAddress.toString(),amount:1_000_000n// 1 XEM});transferTransaction.fee=newmodels.Amount(calculateTransactionFee(transferTransaction));
# Build the wrapper multisig transactiontransaction=facade.transaction_factory.create({'type':'multisig_transaction_v1',# This is the cosignatory that initiates the transfer'signer_public_key':cosignatory0_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,'inner_transaction':facade.transaction_factory.to_non_verifiable_transaction(transfer_transaction)})transaction.fee=Amount(calculate_transaction_fee(transaction))
// Build the wrapper multisig transactionconsttransaction=facade.transactionFactory.create({type:'multisig_transaction_v1',// This is the cosignatory that initiates the transfersignerPublicKey:cosignatory0KeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,innerTransaction:facade.transactionFactory.static.toNonVerifiableTransaction(transferTransaction)});transaction.fee=newmodels.Amount(calculateTransactionFee(transaction));
# Sign and announce the multisig transactionsignature=facade.sign_transaction(cosignatory0_key_pair,transaction)json_payload=facade.transaction_factory.attach_signature(transaction,signature)print('Built multisig transaction:')print(json.dumps(transaction.to_json(),indent=2))announce_result=announce_transaction(json_payload,'multisig transaction')# The transaction is now waiting for the second signature
// Sign and announce the multisig transactionconstsignature=facade.signTransaction(cosignatory0KeyPair,transaction);constjsonPayload=facade.transactionFactory.static.attachSignature(transaction,signature);console.log('Built multisig transaction:');console.log(JSON.stringify(transaction.toJson(),null,2));constannounceResult=awaitannounceTransaction(jsonPayload,'multisig transaction');// The transaction is now waiting for the second signature
# Retrieve the pending transaction from the networkif'SUCCESS'==announce_result:cosignatory1_address=facade.network.public_key_to_address(cosignatory1_key_pair.public_key)unconfirmed_path=('/account/unconfirmedTransactions'f'?address={cosignatory1_address}')print(f'Fetching pending transactions from {unconfirmed_path}')withurllib.request.urlopen(f'{NODE_URL}{unconfirmed_path}')asresponse:pending=json.loads(response.read().decode())['data']# Select the pending transaction issued by the multisig accountinner_transaction_hash=next(entry['meta']['data']forentryinpendingifentry['transaction'].get('otherTrans',{}).get('signer','').upper()==str(multisig_public_key))print(f' Inner transaction hash: {inner_transaction_hash}')
// Retrieve the pending transaction from the networkif('SUCCESS'===announceResult){constcosignatory1Address=facade.network.publicKeyToAddress(cosignatory1KeyPair.publicKey);constunconfirmedPath='/account/unconfirmedTransactions'+`?address=${cosignatory1Address}`;console.log('Fetching pending transactions from',unconfirmedPath);constunconfirmedResponse=awaitfetch(`${NODE_URL}${unconfirmedPath}`);constpending=(awaitunconfirmedResponse.json()).data;// Select the pending transaction issued by the multisig accountconstpendingEntry=pending.find(entry=>multisigPublicKey.toString()===(entry.transaction.otherTrans?.signer??'').toUpperCase());constinnerTransactionHash=pendingEntry.meta.data;console.log(' Inner transaction hash:',innerTransactionHash);
# Build the cosignaturecosignature=facade.transaction_factory.create({'type':'cosignature_v1',# This is the cosignatory providing the second signature'signer_public_key':cosignatory1_key_pair.public_key,'timestamp':timestamp.timestamp,'deadline':deadline.timestamp,# Hash of the inner transfer transaction'other_transaction_hash':inner_transaction_hash,# Address of the multisig account'multisig_account_address':multisig_address})cosignature.fee=Amount(calculate_transaction_fee(cosignature))
// Build the cosignatureconstcosignature=facade.transactionFactory.create({type:'cosignature_v1',// This is the cosignatory providing the second signaturesignerPublicKey:cosignatory1KeyPair.publicKey.toString(),timestamp:timestamp.timestamp,deadline:deadline.timestamp,// Hash of the inner transfer transactionotherTransactionHash:innerTransactionHash,// Address of the multisig accountmultisigAccountAddress:multisigAddress.toString()});cosignature.fee=newmodels.Amount(calculateTransactionFee(cosignature));
# Sign and announce the cosignaturecosignature_signature=facade.sign_transaction(cosignatory1_key_pair,cosignature)cosignature_payload=facade.transaction_factory.attach_signature(cosignature,cosignature_signature)print('Built cosignature:')print(json.dumps(cosignature.to_json(),indent=2))cosignature_result=announce_transaction(cosignature_payload,'cosignature')
// Sign and announce the cosignatureconstcosignatureSignature=facade.signTransaction(cosignatory1KeyPair,cosignature);constcosignaturePayload=facade.transactionFactory.static.attachSignature(cosignature,cosignatureSignature);console.log('Built cosignature:');console.log(JSON.stringify(cosignature.toJson(),null,2));constcosignatureResult=awaitannounceTransaction(cosignaturePayload,'cosignature');
# Wait for the multisig transaction to be confirmedif'SUCCESS'==cosignature_result:wait_for_confirmation(facade.hash_transaction(transaction),'multisig transaction')else:print(f'Transaction rejected: {cosignature_result}')
// Wait for the multisig transaction to be confirmedif('SUCCESS'===cosignatureResult){awaitwaitForConfirmation(facade.hashTransaction(transaction).toString(),'multisig transaction');}else{console.log('Transaction rejected:',cosignatureResult);}