Tutorials
No Results
Tutorial

Intermediate · 30 minutes

How to use Multisignature Transactions in Java

Tutorial Overview

This tutorial will cover how a multi-signature transaction should be created in a real-world application. We will cover the following:

  • Create Multisignature Address from defined threshold and existing Algorand Accounts
  • Create transaction, append multiple signatures
  • Save signed transaction to file
  • Read signed transaction string from file
  • Submit Multisignature transaction to the network

Requirements

Perequisites

  • A familiarity with the Java Programming language.
  • Basic Understanding of some blockchain terminologies.
  • A familiarity with Google’s GSON library.
  • Knowledge of single-signature transactions.
  • File Handling

Background

What are Multisignature Accounts

The Multisignature Account type in Algorand allow a group of existing accounts to jointly calculate a new multisig account address with specific authorization properties. The individual accounts coordinate to define a threshold of valid signatures m provided from their set of public addresses n for authorization of transactions from their generated address. Often referred to as an m-of-n signature scheme where n is the total number of defined public addresses and m is the threshold of signatures required for transaction authorization. For example, a 2-of-4 multisignature account is one where 4 public keys are listed as potential signers and at least 2 of them must be used to create signatures for a valid transaction to spend the funds or interact with an application.

Steps

1. Java-algorand-sdk Setup and Configuration

Maven Configuration

Make sure to add the following within the project tag in your pom.xml file.

 <dependencies>
        <dependency>
            <groupId>com.algorand</groupId>
            <artifactId>algosdk</artifactId>
            <version>1.5.1</version>
        </dependency>
         <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.3.1</version>
        </dependency>
    </dependencies>

Gradle Configuration

In your project level build.gradle file, make sure mavenCentral() is added in the allprojects repositories section like it is done below.

allprojects {
    repositories {
        google()
        jcenter()
         mavenCentral()

    }
}

Finally, add the following dependencies in the app-level build.gradle file.

  implementation 'com.algorand:algosdk:1.5.1'
  implementation 'com.google.code.gson:gson:2.3.1'

So that’s all for both maven and Gradle setup, we just added the dependency for the Google GSON library and that for the Algorand SDK.

2 Utility Methods Field Members And Imports

The following are utility methods that we will be using:

import com.algorand.algosdk.crypto.Ed25519PublicKey;
import com.algorand.algosdk.crypto.MultisigAddress;
import com.algorand.algosdk.transaction.SignedTransaction;
import com.algorand.algosdk.util.Encoder;
import com.algorand.algosdk.v2.client.common.AlgodClient;
import com.algorand.algosdk.v2.client.common.Response;
import com.algorand.algosdk.v2.client.model.*;
import com.algorand.algosdk.account.Account;
import com.algorand.algosdk.crypto.Address;
import com.algorand.algosdk.transaction.Transaction;
import com.google.gson.Gson;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class Main{


static   AlgodClient algodClient;
static String HACKATHON_API_ADDRESS="http://hackathon.algodev.network";
static String HACKATHON_API_TOKEN="ef920e2e7e002953f4b29a8af720efe8e4ecc75ff102b165e0472834b25832c1";
static Integer HACKATHON_API_PORT=9100;


    //Method that creates an Account when given a mnemonic string
 public static  Account createAccountWithMnemonic(String mnemonic){
        Account myAccount1= null;
        try {
            myAccount1 = new Account(mnemonic);
            System.out.println(" algod account address: " + myAccount1.getAddress());
            System.out.println(" algod account MNEMONIC: " + myAccount1.toMnemonic());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            System.out.println(" Eror while creating new account "+e);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        return  myAccount1;
    }


    //method that returns an Account balance
     public static   Long getAccountBalance(Address address) {
        com.algorand.algosdk.v2.client.model.Account accountInfo = null;
        try {
            accountInfo = algodClient.AccountInformation(address).execute().body();
            System.out.println("Account Balance: "+ accountInfo.amount+" microAlgos");
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }

        return  accountInfo.amount;
    }

   //method that creates a transaction  
    public static Transaction createTransaction(Account senderAccount,String receiverAdddress,String note,int valueToSend,String senderAddress){
        TransactionParametersResponse transactionParametersResponse;
        Transaction transaction=null;
        PendingTransactionResponse pendingTransactionResponse;
        try {
            transactionParametersResponse = algodClient.TransactionParams().execute().body();
            transaction= Transaction.PaymentTransactionBuilder().sender(senderAddress)
                    .amount(valueToSend).receiver(new Address(receiverAdddress)).note(note.getBytes()).suggestedParams(transactionParametersResponse).build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return transaction;
    }

    //method that tells us when our transaction has been confirmed
     public static void waitForConfirmation(String txID) throws Exception {
        Long lastRound = algodClient.GetStatus().execute().body().lastRound;
        while (true) {
            try {
                // Check the pending tranactions
                Response<PendingTransactionResponse> pendingInfo = algodClient.PendingTransactionInformation(txID).execute();
                if (pendingInfo.body().confirmedRound != null && pendingInfo.body().confirmedRound > 0) {
                    // Got the completed Transaction
                    System.out.println("Transaction " + txID + " confirmed in round " + pendingInfo.body().confirmedRound);
                    break;
                }
                lastRound++;
                algodClient.WaitForBlock(lastRound).execute();
            } catch (Exception e) {
                throw (e);
            }
        }
    }

    // method to create an AlgodClient
     public static AlgodClient createClientFromHackathonInstance(String HACKATHON_API_ADDRESS,int HACKATHON_API_PORT,String HACKATHON_API_TOKEN){
        algodClient = (AlgodClient) new AlgodClient(HACKATHON_API_ADDRESS,
                HACKATHON_API_PORT, HACKATHON_API_TOKEN);
        try {
            String[] headers = {"X-API-Key"};
            String[] values = {HACKATHON_API_TOKEN};
            NodeStatusResponse status = algodClient.GetStatus().execute(headers, values).body();
            System.out.println("algod last round: " + status.lastRound);
        } catch (Exception e) {
            System.err.print("Failed to get algod status: " + e.getMessage());
        }
        return algodClient;
    }
}

3 Create Multisignature Address

When creating a Multisignature address, we need information about the potential signers unencrypted public key since any of the potential signers ought to be able to construct the multisignature address without having access to other potential signers private key.
So lets go ahead and create a method that does that.

  public static MultisigAddress createMultiSigAddress(int version,int threshold,List<Ed25519PublicKey> ed25519PublicKeys){

        MultisigAddress msig = new MultisigAddress(version, threshold, ed25519PublicKeys);
        try {
            System.out.println(msig.toAddress());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return  msig;
    }

The createMultiSigAddress method receives a version, a threshold which is the number of signers that must sign the transaction for it to be valid, and a list of Ed25519PublicKey. Ed25519PublicKey is a digital signature scheme that supports very fast signature verification, fast signing and fast Key Generation. The method further returns a new Multisignature instance by passing in its parameters to the multi-signature in that order.

Create Ed25519PublicKey From Address

It is rarely the case that another potential signer or random individual will have access to the exact Ed25519PublicKey of another potential signer so it makes sense that we create a method that allows us to retrieve an Ed25519PublicKey from an Address object.

   public static Ed25519PublicKey createEd25519PublicKeys(Address address){
        byte[] b = new byte[0];
        byte[] raw = new byte[32];
        try {
            b = address.toVerifyKey().getEncoded();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        if (b.length != 44) {
            throw new RuntimeException("Generated public key and X.509 prefix is the wrong size");
        } else {
            System.arraycopy(b, 12, raw, 0, 32);
        }
        return  new Ed25519PublicKey(raw);
    }

The createEd25519PublicKeys method simply takes in an Address object, gets the unencoded Public Key and returns a new instance of the Ed25519PublicKey by passing in the unencoded Public Key to the Ed25519PublicKey constructor.

4 Create and Sign Multisignature Transaction

Now lets proceed by creating a method that will create and sign our multisignature transaction.

 public static SignedTransaction createAMultiSigTransaction(Account account, Transaction transaction,MultisigAddress msig){
        SignedTransaction signedTransaction=null;
        PendingTransactionResponse pendingTransactionResponse;
        try {
            signedTransaction = account.signMultisigTransaction(msig, transaction);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  signedTransaction;
    }

This method receives an Account object, a transaction object and a MultisigAddress object and then calls the signMultisigTransaction method on the Account object passing in the MultisigAddress object and the transaction object and then returning the created signedTransaction.

5 Save Signed Transaction to File

In a real-life situation, you won’t always have access to the signed transaction object to pass it to another potential signer to approve or also sign the transaction, so I’ll show you how to save it to a file in this tutorial, you might decide to send it to your server or a database in a more practical application. Lets quickly create a method that does this.

public static void writeSignedTransactionToFile(String nameOfFile,SignedTransaction signedTransaction) throws IOException {
        Gson gson=new Gson();
        String jsonString=gson.toJson(signedTransaction);
        FileWriter fileWriter=null;
        File file=new File(nameOfFile);
        if(file.createNewFile()){
            fileWriter=new FileWriter(nameOfFile);
            fileWriter.write(jsonString);
            fileWriter.close();
        }
    }

The method writeSignedTransactionToFile takes as arguments a nameOfFile which specifies what the name of the file will be and a SignedTransaction object which is what we intend to convert to a string, it further uses the Gson library whose dependency we added earlier to create a JSON string of the SignedTransaction object and creates a new instance of the File Object, passing in the nameOfFile to the File constructor, then it uses the createNewFile method on the file object to create a new file and lastly uses the FileWriter object to write our jsonString to the file.

6 Read signed transaction string from file

Let’s now create the method that will read our SignedTransaction object from the file.

public static  SignedTransaction  readSignedTransactionFromFile(String nameOfFile) throws FileNotFoundException {
        File file=new File(nameOfFile);
        Scanner scanner=new Scanner(file);
        String data="";
        while (scanner.hasNextLine()){
            data=scanner.nextLine();
         }
        Gson gson=new Gson();
        SignedTransaction signedTransaction=gson.fromJson(data,SignedTransaction.class);
        return signedTransaction;
    }

The method above receives the name of a file, uses the Scanner class to read the string in the file and then uses the GSON library to construct a SignedTransaction object from the string that was read.

7 Sign Transaction by Appending Signatures

So far, we have created our method to create the MultisigAddress, create the Multisignature Transaction, but we still need to create a method that appends a signature to the transaction. Let’s do that now.

 public static SignedTransaction approveMultisigTransaction(Account account, SignedTransaction transaction,MultisigAddress msig) {
        SignedTransaction signedTransaction=null;
        PendingTransactionResponse pendingTransactionResponse;
        try {
            signedTransaction = account.appendMultisigTransaction(msig, transaction);
        }  catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NullPointerException e){
            e.printStackTrace();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return signedTransaction;
    }

This method above just like our createAMultiSigTransaction method receives an Account object and a MultisigAddress object, but instead of receiving a Transaction object, it receives a SignedTransaction object, it then approves the signed Transaction by calling the appendMultisigTransaction method on the account and then passing in the MultisignatureAddress object and the SignedTransaction object and finally returns the created SignedTransaction.

8 Submit Multisignature Transaction

Finally, let’s create a function that submits our Multisignature Transaction to the Algorand Network.

public  static  void  submitTransactionToNetwork(SignedTransaction signedTransaction) throws Exception {
        PendingTransactionResponse pendingTransactionResponse=null;
        byte[] encodedTxBytes = new byte[0];
        encodedTxBytes = Encoder.encodeToMsgPack(signedTransaction);
        String id=  algodClient.RawTransaction().rawtxn(encodedTxBytes).execute().body().txId;
        waitForConfirmation(id);
        pendingTransactionResponse = algodClient.PendingTransactionInformation(id).execute().body();
        System.out.println("Transaction information (with notes): " + pendingTransactionResponse.toString());
    }

The method above receives a SignedTransaction object, encodes it to a byte form that can be sent to the network, sends it to the network, then calls our utility method waitForConfirmation which tells us when our there is at least one confirmation block for our transaction.

9 Tying It All Up

Its now time for us to bring all the code we’ve written so far together and see the result, execute the following code in your main function

  public static void main(String[] args) throws Exception {
    //create AlgodClient using the Hackathon Instance
    algodClient=createClientFromHackathonInstance(HACKATHON_API_ADDRESS,HACKATHON_API_PORT,HACKATHON_API_TOKEN);
    //Create the two random test accounts that will be used to create the multisig Address
    Account account=createAccountWithMnemonic("soup someone render seven flip woman olive great random color scene physical put tilt say route coin clutch repair goddess rack cousin decide abandon cream");
    Account account1=createAccountWithMnemonic("box wear empty voyage scout cheap arrive father wagon correct thought sand planet comfort also patient vast patient tide rather young cinnamon plastic abandon model");

    // create Ed25519PublicKey of both accounts and add them to a list
    Ed25519PublicKey ed25519PublicKey1=createEd25519PublicKeys(account.getAddress());
    Ed25519PublicKey ed25519PublicKey2=createEd25519PublicKeys(account1.getAddress());
    List<Ed25519PublicKey> ed25519PublicKeys=new ArrayList<>();
    ed25519PublicKeys.add(ed25519PublicKey1);
    ed25519PublicKeys.add(ed25519PublicKey2);
    //Create a multisig address using the Ed25519PublicKey list created earlier whose version is 1 and threshold is 2
    MultisigAddress multisigAddress=createMultiSigAddress(1,2,ed25519PublicKeys);

    //Create a Transaction
    String receiverAddress="FMBQKMGDE7LYNDHCSUPJBXNMMT3HC2TXMIFAJKGBYJQDZN4R3M554N4QTY";
    Transaction transaction=createTransaction(account,receiverAddress,"Note",1266666,multisigAddress.toAddress().toString());

    //print out the balance of the multisig address and the receiver address before the transaction
    Long multisigBalanceBeforeTransaction=getAccountBalance(multisigAddress.toAddress(), algodClient);
    long receiverBalanceBeforeTransaction=getAccountBalance(new Address(receiverAddress),algodClient);
    System.out.println("multisigBalanceBeforeTransaction: "+multisigBalanceBeforeTransaction);
    System.out.println("receiverBalanceBeforeTransaction: "+receiverBalanceBeforeTransaction);

    //Create SignedTransaction
    SignedTransaction signedTransaction=createAMultiSigTransaction(account,transaction,multisigAddress);

    //Save it to a file
    String nameOfFile=System.currentTimeMillis()+".txt";
    writeSignedTransactionToFile(nameOfFile,signedTransaction);

    //read the signed transaction saved to file earlier and submit it to the network
    signedTransaction=readSignedTransactionFromFile(nameOfFile);
    SignedTransaction completeTransaction=approveMultisigTransaction(account1,signedTransaction,multisigAddress);
    submitTransactionToNetwork(completeTransaction);

    //print out multisig Address balance and receiver address balance after transaction
    Long multisigBalanceAfterTransaction=getAccountBalance(multisigAddress.toAddress(), algodClient);
    long receiverBalanceAfterTransaction=getAccountBalance(new Address(receiverAddress),algodClient);
    System.out.println("multisigBalanceAfterTransaction: "+multisigBalanceAfterTransaction);
    System.out.println("receiverBalanceAfterTransaction: "+receiverBalanceAfterTransaction);
    }

The result of the program can be found below

algod last round: 11124997
 algod account address: LL2ZGXSHW7FJGOOVSV76RRZ6IGU5ZF4DPCHQ23G7ZLIWCB4WEMIATDBTLY
 algod account MNEMONIC: soup someone render seven flip woman olive great random color scene physical put tilt say route coin clutch repair goddess rack cousin decide abandon cream
 algod account address: FMBQKMGDE7LYNDHCSUPJBXNMMT3HC2TXMIFAJKGBYJQDZN4R3M554N4QTY
 algod account MNEMONIC: box wear empty voyage scout cheap arrive father wagon correct thought sand planet comfort also patient vast patient tide rather young cinnamon plastic abandon model
VJQG6EJPZDAWFYLFF5XE3OMRQEK6RFFYSBVJOGXBH63ZQZ3QRRIUVIB7MY
Account Balance: 99879000 microAlgos
Account Balance: 410000 microAlgos
multisigBalanceBeforeTransaction: 99879000
receiverBalanceBeforeTransaction: 410000
Transaction TPB5YAJR5BXSQ76QWWXCZTOABQDAKDTBKNCMCKYUIB7K2HGN7WFA confirmed in round 11125000
Transaction information (with notes): {"confirmed-round":11125000,"global-state-delta":[],"local-state-delta":[],"pool-error":"","txn":{"msig":{"subsig":[{"pk":"WvWTXke3ypM51ZV/6Mc+Qancl4N4jw1s38rRYQeWIxA=","s":"DhAuO4bTkWB7u1nwVrg/G9N8D2wROnT4B6Baizjz6Vvccvgy8GSJfuO+Fvf0A+Vfi/ZMJ3F1B6mDrp8qW/LxDA=="},{"pk":"KwMFMMMn14aM4pUekN2sZPZxandiCgSowcJgPLeR2zs=","s":"j1asbisPM8Qb03XDRIoI0powJ6xeiV6QDpd07+OCU57PwrDz3OZYv3t6rocVO/O3Pwil1hNJuncmj4exzTcUCQ=="}],"thr":2,"v":1},"txn":{"amt":1266666,"fee":1000,"fv":11124998,"gen":"testnet-v1.0","gh":"SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=","lv":11125998,"note":"Tm90ZQ==","rcv":"KwMFMMMn14aM4pUekN2sZPZxandiCgSowcJgPLeR2zs=","snd":"qmBvES/IwWLhZS9uTbmRgRXolLiQapca4T+3mGdwjFE=","type":"pay"}}}
Account Balance: 98611334 microAlgos
Account Balance: 1676666 microAlgos
multisigBalanceAfterTransaction: 98611334
receiverBalanceAfterTransaction: 1676666

Process finished with exit code 0

As you can see, the transaction executed successfully.

10 Full Main.java code

import com.algorand.algosdk.crypto.Ed25519PublicKey;
import com.algorand.algosdk.crypto.MultisigAddress;
import com.algorand.algosdk.transaction.SignedTransaction;
import com.algorand.algosdk.util.Encoder;
import com.algorand.algosdk.v2.client.common.AlgodClient;
import com.algorand.algosdk.v2.client.common.Response;
import com.algorand.algosdk.v2.client.model.*;
import com.algorand.algosdk.account.Account;
import com.algorand.algosdk.crypto.Address;
import com.algorand.algosdk.transaction.Transaction;
import com.google.gson.Gson;
import java.io.*;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;


public class MultisigTransClass {
static String PURESTAKE_API_KEY="ADRySlL0NK5trzqZGAE3q1xxIqlQdSfk1nbHxTNe";
static   AlgodClient algodClient;
static String HACKATHON_API_ADDRESS="http://hackathon.algodev.network";
static String HACKATHON_API_TOKEN="ef920e2e7e002953f4b29a8af720efe8e4ecc75ff102b165e0472834b25832c1";
static Integer HACKATHON_API_PORT=9100;
static final Integer SANDBOX_ALGOD_PORT = 4001;
static  final String SANDBOX_ALGOD_API_TOKEN ="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
    public static void main(String[] args) throws Exception {
    //create AlgodClient using the Hackathon Instance
    algodClient=createClientFromHackathonInstance(HACKATHON_API_ADDRESS,HACKATHON_API_PORT,HACKATHON_API_TOKEN);
    //Create the two random test accounts that will be used to create the multisig Address
    Account account=createAccountWithMnemonic("soup someone render seven flip woman olive great random color scene physical put tilt say route coin clutch repair goddess rack cousin decide abandon cream");
    Account account1=createAccountWithMnemonic("box wear empty voyage scout cheap arrive father wagon correct thought sand planet comfort also patient vast patient tide rather young cinnamon plastic abandon model");

    // create Ed25519PublicKey of both accounts and add them to a list
    Ed25519PublicKey ed25519PublicKey1=createEd25519PublicKeys(account.getAddress());
    Ed25519PublicKey ed25519PublicKey2=createEd25519PublicKeys(account1.getAddress());
    List<Ed25519PublicKey> ed25519PublicKeys=new ArrayList<>();
    ed25519PublicKeys.add(ed25519PublicKey1);
    ed25519PublicKeys.add(ed25519PublicKey2);
    //Create a multisig address using the Ed25519PublicKey list created earlier whose version is 1 and threshold is 2
    MultisigAddress multisigAddress=createMultiSigAddress(1,2,ed25519PublicKeys);

    //Create a Transaction
    String receiverAddress="FMBQKMGDE7LYNDHCSUPJBXNMMT3HC2TXMIFAJKGBYJQDZN4R3M554N4QTY";
    Transaction transaction=createTransaction(account,receiverAddress,"Note",1266666,multisigAddress.toAddress().toString());

    //print out the balance of the multisig address and the receiver address before the transaction
    Long multisigBalanceBeforeTransaction=getAccountBalance(multisigAddress.toAddress(), algodClient);
    long receiverBalanceBeforeTransaction=getAccountBalance(new Address(receiverAddress),algodClient);
    System.out.println("multisigBalanceBeforeTransaction: "+multisigBalanceBeforeTransaction);
    System.out.println("receiverBalanceBeforeTransaction: "+receiverBalanceBeforeTransaction);

    //Create SignedTransaction
    SignedTransaction signedTransaction=createAMultiSigTransaction(account,transaction,multisigAddress);

    //Save it to a file
    String nameOfFile=System.currentTimeMillis()+".txt";
    writeSignedTransactionToFile(nameOfFile,signedTransaction);

    //read the signed transaction saved to file earlier and submit it to the network
    signedTransaction=readSignedTransactionFromFile(nameOfFile);
    SignedTransaction completeTransaction=approveMultisigTransaction(account1,signedTransaction,multisigAddress);
    submitTransactionToNetwork(completeTransaction);

    //print out multisig Address balance and receiver address balance after transaction
    Long multisigBalanceAfterTransaction=getAccountBalance(multisigAddress.toAddress(), algodClient);
    long receiverBalanceAfterTransaction=getAccountBalance(new Address(receiverAddress),algodClient);
    System.out.println("multisigBalanceAfterTransaction: "+multisigBalanceAfterTransaction);
    System.out.println("receiverBalanceAfterTransaction: "+receiverBalanceAfterTransaction);
    }

    public static  Account createAccountWithMnemonic(String mnemonic){
        Account myAccount1= null;
        try {
            myAccount1 = new Account(mnemonic);
            System.out.println(" algod account address: " + myAccount1.getAddress());
            System.out.println(" algod account MNEMONIC: " + myAccount1.toMnemonic());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            System.out.println(" Eror while creating new account "+e);
        } catch (GeneralSecurityException e) {
            e.printStackTrace();
        }
        return  myAccount1;
    }

    public static   Long getAccountBalance(Address address,AlgodClient algodClient) {
        String[] headers = {"X-API-Key"};
        String[] values = {PURESTAKE_API_KEY};
        com.algorand.algosdk.v2.client.model.Account accountInfo = null;
        try {
            accountInfo = algodClient.AccountInformation(address).execute(headers,values).body();
            System.out.println("Account Balance: "+ accountInfo.amount+" microAlgos");
        } catch (Exception e) {
            System.out.println(e.getMessage());
            e.printStackTrace();
        }

        return  accountInfo.amount;
    }

    public static AlgodClient createClientFromHackathonInstance(String HACKATHON_API_ADDRESS,int HACKATHON_API_PORT,String HACKATHON_API_TOKEN){
        algodClient = (AlgodClient) new AlgodClient(HACKATHON_API_ADDRESS,
                HACKATHON_API_PORT, HACKATHON_API_TOKEN);
        try {
            String[] headers = {"X-API-Key"};
            String[] values = {HACKATHON_API_TOKEN};
            NodeStatusResponse status = algodClient.GetStatus().execute(headers, values).body();
            System.out.println("algod last round: " + status.lastRound);
        } catch (Exception e) {
            System.err.print("Failed to get algod status: " + e.getMessage());
        }
        return algodClient;
    }


    public static Ed25519PublicKey createEd25519PublicKeys(Address address){
        byte[] b = new byte[0];
        byte[] raw = new byte[32];
        try {
            b = address.toVerifyKey().getEncoded();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        if (b.length != 44) {
            throw new RuntimeException("Generated public key and X.509 prefix is the wrong size");
        } else {
            System.arraycopy(b, 12, raw, 0, 32);
        }
        return  new Ed25519PublicKey(raw);
    }

    public static MultisigAddress createMultiSigAddress(int version,int threshold,List<Ed25519PublicKey> ed25519PublicKeys){

        MultisigAddress msig = new MultisigAddress(version, threshold, ed25519PublicKeys);
        try {
            System.out.println(msig.toAddress());
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return  msig;
    }

    public static SignedTransaction createAMultiSigTransaction(Account account, Transaction transaction,MultisigAddress msig){
        SignedTransaction signedTransaction=null;
        PendingTransactionResponse pendingTransactionResponse;
        try {
            signedTransaction = account.signMultisigTransaction(msig, transaction);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return  signedTransaction;
    }
    public static Transaction createTransaction(Account senderAccount,String receiverAdddress,String note,int valueToSend,String senderAddress){
        TransactionParametersResponse transactionParametersResponse;
        Transaction transaction=null;
        PendingTransactionResponse pendingTransactionResponse;
        try {
            transactionParametersResponse = algodClient.TransactionParams().execute().body();
            transaction= Transaction.PaymentTransactionBuilder().sender(senderAddress)
                    .amount(valueToSend).receiver(new Address(receiverAdddress)).note(note.getBytes()).suggestedParams(transactionParametersResponse).build();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return transaction;
    }
    public static SignedTransaction approveMultisigTransaction(Account account, SignedTransaction transaction,MultisigAddress msig) {
        SignedTransaction signedTransaction=null;
        PendingTransactionResponse pendingTransactionResponse;
        try {
            signedTransaction = account.appendMultisigTransaction(msig, transaction);
        }  catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NullPointerException e){
            e.printStackTrace();
        }catch (Exception e) {
            e.printStackTrace();
        }
        return signedTransaction;
    }

    public static void waitForConfirmation(String txID) throws Exception {
        Long lastRound = algodClient.GetStatus().execute().body().lastRound;
        while (true) {
            try {
                // Check the pending tranactions
                Response<PendingTransactionResponse> pendingInfo = algodClient.PendingTransactionInformation(txID).execute();
                if (pendingInfo.body().confirmedRound != null && pendingInfo.body().confirmedRound > 0) {
                    // Got the completed Transaction
                    System.out.println("Transaction " + txID + " confirmed in round " + pendingInfo.body().confirmedRound);
                    break;
                }
                lastRound++;
                algodClient.WaitForBlock(lastRound).execute();
            } catch (Exception e) {
                throw (e);
            }
        }


    }

    public  static  void  submitTransactionToNetwork(SignedTransaction signedTransaction) throws Exception {
        PendingTransactionResponse pendingTransactionResponse=null;
        byte[] encodedTxBytes = new byte[0];
        encodedTxBytes = Encoder.encodeToMsgPack(signedTransaction);
        String id=  algodClient.RawTransaction().rawtxn(encodedTxBytes).execute().body().txId;
        waitForConfirmation(id);
        pendingTransactionResponse = algodClient.PendingTransactionInformation(id).execute().body();
        System.out.println("Transaction information (with notes): " + pendingTransactionResponse.toString());
    }

    public static void writeSignedTransactionToFile(String nameOfFile,SignedTransaction signedTransaction) throws IOException {
        Gson gson=new Gson();
        String jsonString=gson.toJson(signedTransaction);
        FileWriter fileWriter=null;
        File file=new File(nameOfFile);
        if(file.createNewFile()){
            fileWriter=new FileWriter(nameOfFile);
            fileWriter.write(jsonString);
            fileWriter.close();
        }
    }

    public static  SignedTransaction  readSignedTransactionFromFile(String nameOfFile) throws FileNotFoundException {
        File file=new File(nameOfFile);
        Scanner scanner=new Scanner(file);
        String data="";
        while (scanner.hasNextLine()){
            data=scanner.nextLine();
         }
        Gson gson=new Gson();
        SignedTransaction signedTransaction=gson.fromJson(data,SignedTransaction.class);
        return signedTransaction;
    }
}

Things to Note

  • The accounts used here should only be used for testing and nothing more.
  • If you plan to change the accounts by using other mnemonic seed words, make sure to recharge such accounts from the Algorand Dispenser
  • If you plan to do this for the android environment, make sure to check out this tutorial for the necessary setup for that environment
  • Slides can be found here

Video Recap

logicsignature

transactions

signatures

java

multisignature

January 06, 2021