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
- 2 Utility Methods Field Members And Imports
- 3 Create Multisignature Address
- 4 Create and Sign Multisignature Transaction
- 5 Save Signed Transaction to File
- 6 Read signed transaction string from file
- 7 Sign Transaction by Appending Signatures
- 8 Submit Multisignature Transaction
- 9 Tying It All Up
- 10 Full Main.java code
- Things to Note
- Video Recap
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