importjsonimportosimporttimeimporturllib.requestfromsymbolchain.CryptoTypesimportPrivateKeyfromsymbolchain.facade.NemFacadeimportNemFacadefromsymbolchain.ncimportAmountfromsymbolchain.nem.FeeCalculatorimportcalculate_transaction_feeNODE_URL=os.getenv('NODE_URL','http://libertalia.nemtest.net:7890')print(f'Using node {NODE_URL}')facade=NemFacade('testnet')KEY_TEMPLATE='0'*63+'{}'# Set up the keys for the multisig account and its two cosignatoriesMULTISIG_PRIVATE_KEY=os.getenv('MULTISIG_PRIVATE_KEY',KEY_TEMPLATE.format(1))multisig_key_pair=NemFacade.KeyPair(PrivateKey(MULTISIG_PRIVATE_KEY))multisig_address=facade.network.public_key_to_address(multisig_key_pair.public_key)print(f'Multisig address: {multisig_address} 'f'(public key {multisig_key_pair.public_key})')cosignatory_key_pairs=[]foriinrange(2):COSIGNATORY_PRIVATE_KEY=os.getenv(f'COSIGNATORY{i}_PRIVATE_KEY',KEY_TEMPLATE.format(i+2))key_pair=NemFacade.KeyPair(PrivateKey(COSIGNATORY_PRIVATE_KEY))cosignatory_key_pairs.append(key_pair)addr=facade.network.public_key_to_address(key_pair.public_key)print(f'Cosignatory {i} address: 'f'{addr} (public key {key_pair.public_key})')# 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.')# Returns the cosignatory addresses of the provided multisig# account, or an empty list if the account is not multisigdefget_multisig_cosignatories(address):account_path=f'/account/get?address={address}'print(f'Getting cosignatories from {account_path}')url=f'{NODE_URL}{account_path}'withurllib.request.urlopen(url)asaccount_response:account_info=json.loads(account_response.read().decode())found_cosignatories=[cosignatory['address']forcosignatoryinaccount_info['meta']['cosignatories']]ifnotfound_cosignatories:print(' Response: No cosignatories')return[]print(f' Response: {found_cosignatories}')returnfound_cosignatories# Returns a transaction that turns a regular account into a multisigdefmultisig_enable_transaction(approval_delta):# Create a multisig account modification transaction# that adds the cosignatoriesmodifications=[{'modification':{'modification_type':'add_cosignatory','cosignatory_public_key':key_pair.public_key}}forkey_pairincosignatory_key_pairs]transaction=facade.create_transaction_from_descriptor({'type':'multisig_account_modification_transaction_v2',# Change of the number of cosignatures# required to approve transactions'min_approval_delta':approval_delta,'modifications':modifications},# This is the account that will be turned into a multisigmultisig_key_pair.public_key,0,2*60*60)# Calculate and attach the transaction feefee=calculate_transaction_fee(transaction)transaction.fee=Amount(fee)print(f' Transaction fee: {fee/1_000_000} XEM')print('Enabling the multisig with the modification transaction:')print(json.dumps(transaction.to_json(),indent=2))# Sign the transaction with the multisig's keysignature=facade.sign_transaction(multisig_key_pair,transaction)facade.transaction_factory.attach_signature(transaction,signature)returntransaction# Returns a transaction that removes one cosignatory from the multisigdefmultisig_removal_transaction(removed_key_pair,approval_delta):# Create a multisig account modification transaction# that removes a single cosignatoryinner_transaction=facade.create_transaction_from_descriptor({'type':'multisig_account_modification_transaction_v2',# Change of the number of cosignatures# required to approve transactions'min_approval_delta':approval_delta,'modifications':[{'modification':{'modification_type':'delete_cosignatory','cosignatory_public_key':removed_key_pair.public_key}}]},# This is the multisig account that will be modifiedmultisig_key_pair.public_key,0,2*60*60)# Wrap the modification in a multisig transactioninner_fee=calculate_transaction_fee(inner_transaction)inner_transaction.fee=Amount(inner_fee)transaction=facade.create_transaction_from_descriptor({'type':'multisig_transaction_v1','inner_transaction':facade.transaction_factory.to_non_verifiable_transaction(inner_transaction)},# This is the cosignatory that initiates the removalcosignatory_key_pairs[0].public_key,0,2*60*60)# Calculate and attach the transaction feefee=calculate_transaction_fee(transaction)transaction.fee=Amount(fee)print(f' Transaction fee: {(inner_fee+fee)/1_000_000} XEM')print('Disabling the multisig with the multisig transaction:')print(json.dumps(transaction.to_json(),indent=2))# Sign the transaction with the cosignatory's keysignature=facade.sign_transaction(cosignatory_key_pairs[0],transaction)facade.transaction_factory.attach_signature(transaction,signature)returntransactiontry:# Get current state of the multisig account and decide which# operation to performcosignatories=get_multisig_cosignatories(multisig_address)iflen(cosignatories)==0:# Enable the multisigtransactions=[multisig_enable_transaction(1)]else:# Disable the multisigtransactions=[multisig_removal_transaction(cosignatory_key_pairs[1],0),multisig_removal_transaction(cosignatory_key_pairs[0],-1)]# Announce each transaction and wait for confirmationforsigned_transactionintransactions:transaction_hash=facade.hash_transaction(signed_transaction)print(f'Built transaction with hash: {transaction_hash}')json_payload=facade.transaction_factory.to_json(signed_transaction)announce_result=announce_transaction(json_payload,'transaction')if'SUCCESS'!=announce_result:print('Transaction rejected')breakwait_for_confirmation(transaction_hash,'transaction')exceptExceptionaserror:print(error)
import{PrivateKey}from'symbol-sdk';import{NemFacade,calculateTransactionFee,descriptors,models}from'symbol-sdk/nem';constNODE_URL=process.env.NODE_URL||'http://libertalia.nemtest.net:7890';console.log('Using node',NODE_URL);constfacade=newNemFacade('testnet');constKEY_PREFIX='0'.repeat(63);// Set up the keys for the multisig account and its two cosignatoriesconstMULTISIG_PRIVATE_KEY=process.env.MULTISIG_PRIVATE_KEY||(`${KEY_PREFIX}1`);constmultisigKeyPair=newNemFacade.KeyPair(newPrivateKey(MULTISIG_PRIVATE_KEY));constmultisigAddress=facade.network.publicKeyToAddress(multisigKeyPair.publicKey);console.log(`Multisig address: ${multisigAddress}`,`(public key ${multisigKeyPair.publicKey})`);constcosignatoryKeyPairs=[];for(leti=0;2>i;i++){constCOSIGNATORY_PRIVATE_KEY=process.env[`COSIGNATORY${i}_PRIVATE_KEY`]||(KEY_PREFIX+String(i+2));constkeyPair=newNemFacade.KeyPair(newPrivateKey(COSIGNATORY_PRIVATE_KEY));cosignatoryKeyPairs.push(keyPair);constaddr=facade.network.publicKeyToAddress(keyPair.publicKey);console.log(`Cosignatory ${i} address: ${addr}`,`(public key ${keyPair.publicKey})`);}// Helper function to announce a transactionasyncfunctionannounceTransaction(payload,label){constannouncePath='/transaction/announce';console.log(`Announcing ${label} to ${announcePath}`);constresponse=awaitfetch(`${NODE_URL}${announcePath}`,{method:'POST',headers:{'Content-Type':'application/json'},body:payload});if(!response.ok)thrownewError(`HTTP ${response.status}`);constresult=awaitresponse.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){console.log(' Transaction status: pending');awaitnewPromise(resolve=>{setTimeout(resolve,1000);});}else{constconfirmed=awaitresponse.json();console.log(`${label} confirmed in block`,confirmed.meta.height);isConfirmed=true;break;}}if(!isConfirmed)console.warn(`${label} confirmation took too long.`);}// Returns the cosignatory addresses of the provided multisig// account, or an empty list if the account is not multisigasyncfunctiongetMultisigCosignatories(address){constaccountPath=`/account/get?address=${address}`;console.log(`Getting cosignatories from ${accountPath}`);constresponse=awaitfetch(`${NODE_URL}${accountPath}`);constaccountInfo=awaitresponse.json();constfoundCosignatories=accountInfo.meta.cosignatories.map(cosignatory=>cosignatory.address);if(0===foundCosignatories.length){console.log(' Response: No cosignatories');return[];}console.log(' Response:',JSON.stringify(foundCosignatories));returnfoundCosignatories;}// Returns a transaction that turns a regular account into a multisigfunctionmultisigEnableTransaction(approvalDelta){// Create a multisig account modification transaction// that adds the cosignatoriesconstmodifications=cosignatoryKeyPairs.map(keyPair=>newdescriptors.SizePrefixedMultisigAccountModificationDescriptor(newdescriptors.MultisigAccountModificationDescriptor(models.MultisigAccountModificationType.ADD_COSIGNATORY,keyPair.publicKey)));consttransaction=facade.createTransactionFromTypedDescriptor(newdescriptors.MultisigAccountModificationTransactionV2Descriptor(// Change of the number of cosignatures// required to approve transactionsapprovalDelta,modifications),// This is the account that will be turned into a multisigmultisigKeyPair.publicKey,0n,2*60*60);// Calculate and attach the transaction feeconstfee=calculateTransactionFee(transaction);transaction.fee=newmodels.Amount(fee);console.log(` Transaction fee: ${Number(fee)/1_000_000} XEM`);console.log('Enabling the multisig with the modification transaction:');console.log(JSON.stringify(transaction.toJson(),null,2));// Sign the transaction with the multisig's keyconstsignature=facade.signTransaction(multisigKeyPair,transaction);facade.transactionFactory.static.attachSignature(transaction,signature);returntransaction;}// Returns a transaction that removes one cosignatory from the multisigfunctionmultisigRemovalTransaction(removedKeyPair,approvalDelta){// Create a multisig account modification transaction// that removes a single cosignatoryconstinnerTransaction=facade.createTransactionFromTypedDescriptor(newdescriptors.MultisigAccountModificationTransactionV2Descriptor(// Change of the number of cosignatures// required to approve transactionsapprovalDelta,[newdescriptors.SizePrefixedMultisigAccountModificationDescriptor(newdescriptors.MultisigAccountModificationDescriptor(models.MultisigAccountModificationType.DELETE_COSIGNATORY,removedKeyPair.publicKey))]),// This is the multisig account that will be modifiedmultisigKeyPair.publicKey,0n,2*60*60);// Wrap the modification in a multisig transactionconstinnerFee=calculateTransactionFee(innerTransaction);innerTransaction.fee=newmodels.Amount(innerFee);consttransaction=facade.createTransactionFromTypedDescriptor(newdescriptors.MultisigTransactionV1Descriptor(facade.transactionFactory.static.toNonVerifiableTransaction(innerTransaction)),// This is the cosignatory that initiates the removalcosignatoryKeyPairs[0].publicKey,0n,2*60*60);// Calculate and attach the transaction feeconstfee=calculateTransactionFee(transaction);transaction.fee=newmodels.Amount(fee);console.log(' Transaction fee:',`${Number(innerFee+fee)/1_000_000} XEM`);console.log('Disabling the multisig with the multisig transaction:');console.log(JSON.stringify(transaction.toJson(),null,2));// Sign the transaction with the cosignatory's keyconstsignature=facade.signTransaction(cosignatoryKeyPairs[0],transaction);facade.transactionFactory.static.attachSignature(transaction,signature);returntransaction;}try{// Get current state of the multisig account and decide// which operation to performconstcosignatories=awaitgetMultisigCosignatories(multisigAddress);lettransactions;if(0===cosignatories.length){// Enable the multisigtransactions=[multisigEnableTransaction(1)];}else{// Disable the multisigtransactions=[multisigRemovalTransaction(cosignatoryKeyPairs[1],0),multisigRemovalTransaction(cosignatoryKeyPairs[0],-1)];}// Announce each transaction and wait for confirmationfor(constsignedTransactionoftransactions){consttransactionHash=facade.hashTransaction(signedTransaction).toString();console.log('Built transaction with hash:',transactionHash);constjsonPayload=facade.transactionFactory.static.toJson(signedTransaction);constresult=awaitannounceTransaction(jsonPayload,'transaction');if('SUCCESS'!==result){console.log('Transaction rejected');break;}awaitwaitForConfirmation(transactionHash,'transaction');}}catch(e){console.error(e.message,'| Cause:',e.cause?.code??'unknown');}
//JAVA 21+//DEPS org.symbol:symbol-sdk:3.3.3importjava.io.IOException;importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.net.http.HttpResponse.BodyHandlers;importjava.util.ArrayList;importjava.util.List;importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;importorg.symbol.sdk.CryptoTypes;importorg.symbol.sdk.facade.NemFacade;importorg.symbol.sdk.nem.Address;importorg.symbol.sdk.nem.FeeCalculator;importorg.symbol.sdk.nem.KeyPair;importorg.symbol.sdk.nem.NemTransactionFactory;importorg.symbol.sdk.nem.descriptors.*;importorg.symbol.sdk.nem.models.Amount;importorg.symbol.sdk.nem.models.MultisigAccountModificationType;importorg.symbol.sdk.nem.models.Transaction;publicfinalclassConfigureMultisig{privatestaticfinalObjectMapperJSON_MAPPER=newObjectMapper();privatestaticfinalHttpClientHTTP_CLIENT=HttpClient.newHttpClient();privatefinalStringnodeUrl=System.getenv().getOrDefault("NODE_URL","http://libertalia.nemtest.net:7890");privatefinalNemFacadefacade=newNemFacade("testnet");// Helper method to announce a transactionprivateStringannounceTransaction(finalStringpayload,finalStringlabel)throwsIOException,InterruptedException{finalStringannouncePath="/transaction/announce";System.out.printf("Announcing %s to %s%n",label,announcePath);finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(nodeUrl+announcePath)).header("Content-Type","application/json").POST(HttpRequest.BodyPublishers.ofString(payload)).build();finalHttpResponse<String>response=HTTP_CLIENT.send(request,BodyHandlers.ofString());if(2!=response.statusCode()/100)thrownewIOException("HTTP "+response.statusCode());finalStringresult=JSON_MAPPER.readTree(response.body()).get("message").asText();System.out.printf(" Result: %s%n",result);returnresult;}// Helper method to wait for transaction confirmationprivatevoidwaitForConfirmation(finalStringtransactionHash,finalStringlabel)throwsIOException,InterruptedException{finalStringstatusPath=String.format("/transaction/get?hash=%s",transactionHash);System.out.printf("Waiting for %s confirmation from %s%n",label,statusPath);booleanisConfirmed=false;for(intattempt=1;120>=attempt;++attempt){finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(nodeUrl+statusPath)).GET().build();finalHttpResponse<String>response=HTTP_CLIENT.send(request,BodyHandlers.ofString());if(2!=response.statusCode()/100){System.out.println(" Transaction status: pending");Thread.sleep(1000);}else{finalJsonNodeconfirmed=JSON_MAPPER.readTree(response.body());System.out.printf("%s confirmed in block %s%n",label,confirmed.get("meta").get("height").asText());isConfirmed=true;break;}}if(!isConfirmed)System.out.printf("%s confirmation took too long.%n",label);}publicstaticvoidmain(finalString[]args){try{newConfigureMultisig().run();}catch(finalExceptionex){System.out.println(null==ex.getMessage()?ex.toString():ex.getMessage());}}privatevoidrun()throwsIOException,InterruptedException{System.out.printf("Using node %s%n",nodeUrl);finalStringkeyPrefix="0".repeat(63);finalStringmultisigPrivateKey=System.getenv().getOrDefault("MULTISIG_PRIVATE_KEY",keyPrefix+"1");finalKeyPairmultisigKeyPair=newKeyPair(newCryptoTypes.PrivateKey(multisigPrivateKey));finalAddressmultisigAddress=facade.network.publicKeyToAddress(multisigKeyPair.getPublicKey());System.out.printf("Multisig address: %s (public key %s)%n",multisigAddress,multisigKeyPair.getPublicKey());finalList<KeyPair>cosignatoryKeyPairs=newArrayList<>();for(inti=0;2>i;++i){finalStringprivateKey=System.getenv().getOrDefault(String.format("COSIGNATORY%d_PRIVATE_KEY",i),keyPrefix+(i+2));finalKeyPairkeyPair=newKeyPair(newCryptoTypes.PrivateKey(privateKey));cosignatoryKeyPairs.add(keyPair);finalAddressaddress=facade.network.publicKeyToAddress(keyPair.getPublicKey());System.out.printf("Cosignatory %d address: %s (public key %s)%n",i,address,keyPair.getPublicKey());}// Get the current state of the multisig accountfinalList<String>cosignatories=getMultisigCosignatories(multisigAddress);// Decide which operation to performfinalList<Transaction>transactions;if(cosignatories.isEmpty()){transactions=List.of(multisigEnableTransaction(multisigKeyPair,cosignatoryKeyPairs,1));}else{transactions=List.of(multisigRemovalTransaction(multisigKeyPair,cosignatoryKeyPairs,cosignatoryKeyPairs.get(1),0),multisigRemovalTransaction(multisigKeyPair,cosignatoryKeyPairs,cosignatoryKeyPairs.get(0),-1));}// Announce each transaction and wait for confirmationfor(finalTransactiontransaction:transactions){finalStringhash=facade.hashTransaction(transaction).toString();System.out.printf("Built transaction with hash: %s%n",hash);finalStringresult=announceTransaction(NemTransactionFactory.toJson(transaction),"transaction");if(!"SUCCESS".equals(result)){System.out.println("Transaction rejected");break;}waitForConfirmation(hash,"transaction");}}// Returns the cosignatory addresses of the provided multisig// account, or an empty list if the account is not multisigprivateList<String>getMultisigCosignatories(finalAddressaddress)throwsIOException,InterruptedException{finalStringpath="/account/get?address="+address;System.out.printf("Getting cosignatories from %s%n",path);finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(nodeUrl+path)).GET().build();finalJsonNodefound=JSON_MAPPER.readTree(HTTP_CLIENT.send(request,BodyHandlers.ofString()).body()).get("meta").get("cosignatories");finalList<String>result=newArrayList<>();for(finalJsonNodecosignatory:found)result.add(cosignatory.get("address").asText());if(result.isEmpty())System.out.println(" Response: No cosignatories");elseSystem.out.printf(" Response: %s%n",result);returnresult;}// Returns a transaction that turns a regular account into a multisigprivateTransactionmultisigEnableTransaction(finalKeyPairmultisigKeyPair,finalList<KeyPair>cosignatoryKeyPairs,finalintapprovalDelta)throwsIOException{finalList<SizePrefixedMultisigAccountModificationDescriptor>modifications=newArrayList<>();for(finalKeyPairkeyPair:cosignatoryKeyPairs)modifications.add(newSizePrefixedMultisigAccountModificationDescriptor(newMultisigAccountModificationDescriptor(MultisigAccountModificationType.ADD_COSIGNATORY,keyPair.getPublicKey())));finalTransactiontransaction=facade.createTransactionFromTypedDescriptor(newMultisigAccountModificationTransactionV2Descriptor(approvalDelta,modifications),multisigKeyPair.getPublicKey(),0,2*60*60);// Calculate and attach the transaction feefinallongfee=FeeCalculator.calculateTransactionFee(transaction);transaction.setFee(newAmount(fee));System.out.printf(" Transaction fee: %s XEM%n",(double)fee/1_000_000);System.out.println("Enabling the multisig with the modification transaction:");System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(transaction.toJson()));// Sign the transaction with the multisig's keyNemTransactionFactory.attachSignature(transaction,facade.signTransaction(multisigKeyPair,transaction));returntransaction;}// Returns a transaction that removes one cosignatory from the multisigprivateTransactionmultisigRemovalTransaction(finalKeyPairmultisigKeyPair,finalList<KeyPair>cosignatoryKeyPairs,finalKeyPairremovedKeyPair,finalintapprovalDelta)throwsIOException{finalSizePrefixedMultisigAccountModificationDescriptorremoval=newSizePrefixedMultisigAccountModificationDescriptor(newMultisigAccountModificationDescriptor(MultisigAccountModificationType.DELETE_COSIGNATORY,removedKeyPair.getPublicKey()));finalTransactioninnerTransaction=facade.createTransactionFromTypedDescriptor(newMultisigAccountModificationTransactionV2Descriptor(approvalDelta,List.of(removal)),multisigKeyPair.getPublicKey(),0,2*60*60);// Wrap the modification in a multisig transactionfinallonginnerFee=FeeCalculator.calculateTransactionFee(innerTransaction);innerTransaction.setFee(newAmount(innerFee));finalTransactiontransaction=facade.createTransactionFromTypedDescriptor(newMultisigTransactionV1Descriptor(NemTransactionFactory.toNonVerifiableTransaction(innerTransaction),List.of()),cosignatoryKeyPairs.get(0).getPublicKey(),0,2*60*60);// Calculate and attach the transaction feefinallongfee=FeeCalculator.calculateTransactionFee(transaction);transaction.setFee(newAmount(fee));System.out.printf(" Transaction fee: %s XEM%n",(double)(innerFee+fee)/1_000_000);System.out.println("Disabling the multisig with the multisig transaction:");System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(transaction.toJson()));// Sign the transaction with the cosignatory's keyNemTransactionFactory.attachSignature(transaction,facade.signTransaction(cosignatoryKeyPairs.get(0),transaction));returntransaction;}}
KEY_TEMPLATE='0'*63+'{}'# Set up the keys for the multisig account and its two cosignatoriesMULTISIG_PRIVATE_KEY=os.getenv('MULTISIG_PRIVATE_KEY',KEY_TEMPLATE.format(1))multisig_key_pair=NemFacade.KeyPair(PrivateKey(MULTISIG_PRIVATE_KEY))multisig_address=facade.network.public_key_to_address(multisig_key_pair.public_key)print(f'Multisig address: {multisig_address} 'f'(public key {multisig_key_pair.public_key})')cosignatory_key_pairs=[]foriinrange(2):COSIGNATORY_PRIVATE_KEY=os.getenv(f'COSIGNATORY{i}_PRIVATE_KEY',KEY_TEMPLATE.format(i+2))key_pair=NemFacade.KeyPair(PrivateKey(COSIGNATORY_PRIVATE_KEY))cosignatory_key_pairs.append(key_pair)addr=facade.network.public_key_to_address(key_pair.public_key)print(f'Cosignatory {i} address: 'f'{addr} (public key {key_pair.public_key})')
constKEY_PREFIX='0'.repeat(63);// Set up the keys for the multisig account and its two cosignatoriesconstMULTISIG_PRIVATE_KEY=process.env.MULTISIG_PRIVATE_KEY||(`${KEY_PREFIX}1`);constmultisigKeyPair=newNemFacade.KeyPair(newPrivateKey(MULTISIG_PRIVATE_KEY));constmultisigAddress=facade.network.publicKeyToAddress(multisigKeyPair.publicKey);console.log(`Multisig address: ${multisigAddress}`,`(public key ${multisigKeyPair.publicKey})`);constcosignatoryKeyPairs=[];for(leti=0;2>i;i++){constCOSIGNATORY_PRIVATE_KEY=process.env[`COSIGNATORY${i}_PRIVATE_KEY`]||(KEY_PREFIX+String(i+2));constkeyPair=newNemFacade.KeyPair(newPrivateKey(COSIGNATORY_PRIVATE_KEY));cosignatoryKeyPairs.push(keyPair);constaddr=facade.network.publicKeyToAddress(keyPair.publicKey);console.log(`Cosignatory ${i} address: ${addr}`,`(public key ${keyPair.publicKey})`);}
# Returns the cosignatory addresses of the provided multisig# account, or an empty list if the account is not multisigdefget_multisig_cosignatories(address):account_path=f'/account/get?address={address}'print(f'Getting cosignatories from {account_path}')url=f'{NODE_URL}{account_path}'withurllib.request.urlopen(url)asaccount_response:account_info=json.loads(account_response.read().decode())found_cosignatories=[cosignatory['address']forcosignatoryinaccount_info['meta']['cosignatories']]ifnotfound_cosignatories:print(' Response: No cosignatories')return[]print(f' Response: {found_cosignatories}')returnfound_cosignatories
// Returns the cosignatory addresses of the provided multisig// account, or an empty list if the account is not multisigasyncfunctiongetMultisigCosignatories(address){constaccountPath=`/account/get?address=${address}`;console.log(`Getting cosignatories from ${accountPath}`);constresponse=awaitfetch(`${NODE_URL}${accountPath}`);constaccountInfo=awaitresponse.json();constfoundCosignatories=accountInfo.meta.cosignatories.map(cosignatory=>cosignatory.address);if(0===foundCosignatories.length){console.log(' Response: No cosignatories');return[];}console.log(' Response:',JSON.stringify(foundCosignatories));returnfoundCosignatories;}
// Returns the cosignatory addresses of the provided multisig// account, or an empty list if the account is not multisigprivateList<String>getMultisigCosignatories(finalAddressaddress)throwsIOException,InterruptedException{finalStringpath="/account/get?address="+address;System.out.printf("Getting cosignatories from %s%n",path);finalHttpRequestrequest=HttpRequest.newBuilder(URI.create(nodeUrl+path)).GET().build();finalJsonNodefound=JSON_MAPPER.readTree(HTTP_CLIENT.send(request,BodyHandlers.ofString()).body()).get("meta").get("cosignatories");finalList<String>result=newArrayList<>();for(finalJsonNodecosignatory:found)result.add(cosignatory.get("address").asText());if(result.isEmpty())System.out.println(" Response: No cosignatories");elseSystem.out.printf(" Response: %s%n",result);returnresult;}
# Get current state of the multisig account and decide which# operation to performcosignatories=get_multisig_cosignatories(multisig_address)iflen(cosignatories)==0:# Enable the multisigtransactions=[multisig_enable_transaction(1)]else:# Disable the multisigtransactions=[multisig_removal_transaction(cosignatory_key_pairs[1],0),multisig_removal_transaction(cosignatory_key_pairs[0],-1)]
// Get current state of the multisig account and decide// which operation to performconstcosignatories=awaitgetMultisigCosignatories(multisigAddress);lettransactions;if(0===cosignatories.length){// Enable the multisigtransactions=[multisigEnableTransaction(1)];}else{// Disable the multisigtransactions=[multisigRemovalTransaction(cosignatoryKeyPairs[1],0),multisigRemovalTransaction(cosignatoryKeyPairs[0],-1)];}
// Decide which operation to performfinalList<Transaction>transactions;if(cosignatories.isEmpty()){transactions=List.of(multisigEnableTransaction(multisigKeyPair,cosignatoryKeyPairs,1));}else{transactions=List.of(multisigRemovalTransaction(multisigKeyPair,cosignatoryKeyPairs,cosignatoryKeyPairs.get(1),0),multisigRemovalTransaction(multisigKeyPair,cosignatoryKeyPairs,cosignatoryKeyPairs.get(0),-1));}
# Returns a transaction that turns a regular account into a multisigdefmultisig_enable_transaction(approval_delta):# Create a multisig account modification transaction# that adds the cosignatoriesmodifications=[{'modification':{'modification_type':'add_cosignatory','cosignatory_public_key':key_pair.public_key}}forkey_pairincosignatory_key_pairs]transaction=facade.create_transaction_from_descriptor({'type':'multisig_account_modification_transaction_v2',# Change of the number of cosignatures# required to approve transactions'min_approval_delta':approval_delta,'modifications':modifications},# This is the account that will be turned into a multisigmultisig_key_pair.public_key,0,2*60*60)
// Returns a transaction that turns a regular account into a multisigfunctionmultisigEnableTransaction(approvalDelta){// Create a multisig account modification transaction// that adds the cosignatoriesconstmodifications=cosignatoryKeyPairs.map(keyPair=>newdescriptors.SizePrefixedMultisigAccountModificationDescriptor(newdescriptors.MultisigAccountModificationDescriptor(models.MultisigAccountModificationType.ADD_COSIGNATORY,keyPair.publicKey)));consttransaction=facade.createTransactionFromTypedDescriptor(newdescriptors.MultisigAccountModificationTransactionV2Descriptor(// Change of the number of cosignatures// required to approve transactionsapprovalDelta,modifications),// This is the account that will be turned into a multisigmultisigKeyPair.publicKey,0n,2*60*60);
// Returns a transaction that turns a regular account into a multisigprivateTransactionmultisigEnableTransaction(finalKeyPairmultisigKeyPair,finalList<KeyPair>cosignatoryKeyPairs,finalintapprovalDelta)throwsIOException{finalList<SizePrefixedMultisigAccountModificationDescriptor>modifications=newArrayList<>();for(finalKeyPairkeyPair:cosignatoryKeyPairs)modifications.add(newSizePrefixedMultisigAccountModificationDescriptor(newMultisigAccountModificationDescriptor(MultisigAccountModificationType.ADD_COSIGNATORY,keyPair.getPublicKey())));finalTransactiontransaction=facade.createTransactionFromTypedDescriptor(newMultisigAccountModificationTransactionV2Descriptor(approvalDelta,modifications),multisigKeyPair.getPublicKey(),0,2*60*60);
# Calculate and attach the transaction feefee=calculate_transaction_fee(transaction)transaction.fee=Amount(fee)print(f' Transaction fee: {fee/1_000_000} XEM')print('Enabling the multisig with the modification transaction:')print(json.dumps(transaction.to_json(),indent=2))
// Calculate and attach the transaction feeconstfee=calculateTransactionFee(transaction);transaction.fee=newmodels.Amount(fee);console.log(` Transaction fee: ${Number(fee)/1_000_000} XEM`);console.log('Enabling the multisig with the modification transaction:');console.log(JSON.stringify(transaction.toJson(),null,2));
// Calculate and attach the transaction feefinallongfee=FeeCalculator.calculateTransactionFee(transaction);transaction.setFee(newAmount(fee));System.out.printf(" Transaction fee: %s XEM%n",(double)fee/1_000_000);System.out.println("Enabling the multisig with the modification transaction:");System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(transaction.toJson()));
# Sign the transaction with the multisig's keysignature=facade.sign_transaction(multisig_key_pair,transaction)facade.transaction_factory.attach_signature(transaction,signature)returntransaction
// Sign the transaction with the multisig's keyconstsignature=facade.signTransaction(multisigKeyPair,transaction);facade.transactionFactory.static.attachSignature(transaction,signature);returntransaction;
// Sign the transaction with the multisig's keyNemTransactionFactory.attachSignature(transaction,facade.signTransaction(multisigKeyPair,transaction));returntransaction;
# Returns a transaction that removes one cosignatory from the multisigdefmultisig_removal_transaction(removed_key_pair,approval_delta):# Create a multisig account modification transaction# that removes a single cosignatoryinner_transaction=facade.create_transaction_from_descriptor({'type':'multisig_account_modification_transaction_v2',# Change of the number of cosignatures# required to approve transactions'min_approval_delta':approval_delta,'modifications':[{'modification':{'modification_type':'delete_cosignatory','cosignatory_public_key':removed_key_pair.public_key}}]},# This is the multisig account that will be modifiedmultisig_key_pair.public_key,0,2*60*60)
// Returns a transaction that removes one cosignatory from the multisigfunctionmultisigRemovalTransaction(removedKeyPair,approvalDelta){// Create a multisig account modification transaction// that removes a single cosignatoryconstinnerTransaction=facade.createTransactionFromTypedDescriptor(newdescriptors.MultisigAccountModificationTransactionV2Descriptor(// Change of the number of cosignatures// required to approve transactionsapprovalDelta,[newdescriptors.SizePrefixedMultisigAccountModificationDescriptor(newdescriptors.MultisigAccountModificationDescriptor(models.MultisigAccountModificationType.DELETE_COSIGNATORY,removedKeyPair.publicKey))]),// This is the multisig account that will be modifiedmultisigKeyPair.publicKey,0n,2*60*60);
// Returns a transaction that removes one cosignatory from the multisigprivateTransactionmultisigRemovalTransaction(finalKeyPairmultisigKeyPair,finalList<KeyPair>cosignatoryKeyPairs,finalKeyPairremovedKeyPair,finalintapprovalDelta)throwsIOException{finalSizePrefixedMultisigAccountModificationDescriptorremoval=newSizePrefixedMultisigAccountModificationDescriptor(newMultisigAccountModificationDescriptor(MultisigAccountModificationType.DELETE_COSIGNATORY,removedKeyPair.getPublicKey()));finalTransactioninnerTransaction=facade.createTransactionFromTypedDescriptor(newMultisigAccountModificationTransactionV2Descriptor(approvalDelta,List.of(removal)),multisigKeyPair.getPublicKey(),0,2*60*60);
# Wrap the modification in a multisig transactioninner_fee=calculate_transaction_fee(inner_transaction)inner_transaction.fee=Amount(inner_fee)transaction=facade.create_transaction_from_descriptor({'type':'multisig_transaction_v1','inner_transaction':facade.transaction_factory.to_non_verifiable_transaction(inner_transaction)},# This is the cosignatory that initiates the removalcosignatory_key_pairs[0].public_key,0,2*60*60)
// Wrap the modification in a multisig transactionconstinnerFee=calculateTransactionFee(innerTransaction);innerTransaction.fee=newmodels.Amount(innerFee);consttransaction=facade.createTransactionFromTypedDescriptor(newdescriptors.MultisigTransactionV1Descriptor(facade.transactionFactory.static.toNonVerifiableTransaction(innerTransaction)),// This is the cosignatory that initiates the removalcosignatoryKeyPairs[0].publicKey,0n,2*60*60);
// Wrap the modification in a multisig transactionfinallonginnerFee=FeeCalculator.calculateTransactionFee(innerTransaction);innerTransaction.setFee(newAmount(innerFee));finalTransactiontransaction=facade.createTransactionFromTypedDescriptor(newMultisigTransactionV1Descriptor(NemTransactionFactory.toNonVerifiableTransaction(innerTransaction),List.of()),cosignatoryKeyPairs.get(0).getPublicKey(),0,2*60*60);
# Calculate and attach the transaction feefee=calculate_transaction_fee(transaction)transaction.fee=Amount(fee)print(f' Transaction fee: {(inner_fee+fee)/1_000_000} XEM')print('Disabling the multisig with the multisig transaction:')print(json.dumps(transaction.to_json(),indent=2))
// Calculate and attach the transaction feeconstfee=calculateTransactionFee(transaction);transaction.fee=newmodels.Amount(fee);console.log(' Transaction fee:',`${Number(innerFee+fee)/1_000_000} XEM`);console.log('Disabling the multisig with the multisig transaction:');console.log(JSON.stringify(transaction.toJson(),null,2));
// Calculate and attach the transaction feefinallongfee=FeeCalculator.calculateTransactionFee(transaction);transaction.setFee(newAmount(fee));System.out.printf(" Transaction fee: %s XEM%n",(double)(innerFee+fee)/1_000_000);System.out.println("Disabling the multisig with the multisig transaction:");System.out.println(JSON_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(transaction.toJson()));
# Sign the transaction with the cosignatory's keysignature=facade.sign_transaction(cosignatory_key_pairs[0],transaction)facade.transaction_factory.attach_signature(transaction,signature)returntransaction
// Sign the transaction with the cosignatory's keyconstsignature=facade.signTransaction(cosignatoryKeyPairs[0],transaction);facade.transactionFactory.static.attachSignature(transaction,signature);returntransaction;
// Sign the transaction with the cosignatory's keyNemTransactionFactory.attachSignature(transaction,facade.signTransaction(cosignatoryKeyPairs.get(0),transaction));returntransaction;
# Announce each transaction and wait for confirmationforsigned_transactionintransactions:transaction_hash=facade.hash_transaction(signed_transaction)print(f'Built transaction with hash: {transaction_hash}')json_payload=facade.transaction_factory.to_json(signed_transaction)announce_result=announce_transaction(json_payload,'transaction')if'SUCCESS'!=announce_result:print('Transaction rejected')breakwait_for_confirmation(transaction_hash,'transaction')
// Announce each transaction and wait for confirmationfor(constsignedTransactionoftransactions){consttransactionHash=facade.hashTransaction(signedTransaction).toString();console.log('Built transaction with hash:',transactionHash);constjsonPayload=facade.transactionFactory.static.toJson(signedTransaction);constresult=awaitannounceTransaction(jsonPayload,'transaction');if('SUCCESS'!==result){console.log('Transaction rejected');break;}awaitwaitForConfirmation(transactionHash,'transaction');}
// Announce each transaction and wait for confirmationfor(finalTransactiontransaction:transactions){finalStringhash=facade.hashTransaction(transaction).toString();System.out.printf("Built transaction with hash: %s%n",hash);finalStringresult=announceTransaction(NemTransactionFactory.toJson(transaction),"transaction");if(!"SUCCESS".equals(result)){System.out.println("Transaction rejected");break;}waitForConfirmation(hash,"transaction");}