Using Java Algorand SDK with Spring Boot
Spring Boot is very popular with Java developers. Using the dependency injection and other features of the Spring Framework, the application can be made more readable and robust. This tutorial is to showcase how to use the Algorand Java SDK and the Spring Framework together.
Requirements
Background
I have selected a simple example to showcase how to interact with the Algorand network. There will be 2 accounts: Bob and Alice. Alice will transfer 1 ALGO to Bob and we verify that the transfer was successful using a Junit test. The example is similar to this tutorial but I use in a Spring idiomatic way. Note that this tutorial is intended for experienced Spring developers, therefore, I do not explain in detail about the Spring Framework.
Steps
1. Install Sandbox
Follow the instruction here to setup a sandbox environment on your computer. Start the sandbox in a private network mode:
./sandbox up
2. Checkout code from GitHub
Checkout the following Spring Boot application from GitHub:
git clone https://github.com/urajbhandari/boot-algorand
Load the code in a Java IDE such as Intellij.
Note that in the Maven’s pom.xml the Java version is specified as 15:
<properties>
<java.version>15</java.version>
</properties>
You can change this version to match the Java version that you have on your computer as long as it is 8 or higher.
3. Create two Algorand accounts
Run com.demo.boot.algorand.util.AccountUtil‘s main method. It will output text similar to the following:
app:
accounts:
alice:
address: 634XGEIC3YHBMEO5P4N7XDUQ3VC7U6MQGG3C5MYEQO5O7WFRBY757PHLKQ
mnemonic: spare despair spice pottery fuel pledge canyon body urge bridge usual admit father remind ordinary gaze tilt vessel wine split include advance few ability beef
bob:
address: H3WZQIYC3MYU7PQRGTGZKGLTK4BWDFTMW5BBYGRW3SZKLTNSWYQWXOHE5U
mnemonic: moment dog buyer auction myth much police marble slim report runway atom destroy ramp hunt grass divert afford daring melody mouse plug fit abstract road
Paste this content into application.yaml. Note that application.yaml already has accounts setup. The existing values will also work because I pasted the values and checked into git.
4. Transfer ALGOs to Alice
For Alice to transfer ALGOs to Bob, she must have some ALGOs first. In the example above, Alice’s address is 634XGEIC3YHBMEO5P4N7XDUQ3VC7U6MQGG3C5MYEQO5O7WFRBY757PHLKQ (you may have different address). To transfer 100 ALGOs to Alice run the following command line command from the sandbox directory:
./sandbox goal clerk send -a 10000000 -f KWW3LGTW4AVPWEUUERQ4IN2OMCA2WZPEZSACB2HV774U5C5HV6XBLTTXEM -t 634XGEIC3YHBMEO5P4N7XDUQ3VC7U6MQGG3C5MYEQO5O7WFRBY757PHLKQ
When the sandbox private network starts, it comes with a few accounts populated with ALGOs. The from (-f) account above already has ALGOs. To see the list of accounts and the balance you can use the following command:
./sandbox goal account list
5. Run Junit Test
There is one test in com.demo.boot.algorand.service.AlgoTransferServiceTest with the method name testTransferAlgoFromAliceToBob(). Run that test. The test should pass.
This is the test method:
@Test
void testTransferAlgoFromAliceToBob() throws Exception {
Account aliceAccount = accountFactory.getAccountByAlias("alice");
Long aliceAccountInitialBalance = accountService.getAccountBalance(aliceAccount);
log.info("Alic account initial balance: " + aliceAccountInitialBalance);
//if account balance is less than 0.001 (which is equivalent to 1000microAlgo) Algo, fail the test
if (aliceAccountInitialBalance < 1000) {
Assertions.fail("Alice account does not have enough balance: " + aliceAccountInitialBalance);
}
Account bobAccount = accountFactory.getAccountByAlias("bob");
Long bobAccountInitialBalance = accountService.getAccountBalance(bobAccount);
log.info("Bob account initial balance: " + bobAccountInitialBalance);
//transfer the amount
int transferAmountMicroAlgos = 1000000;
String note = "Alice to Bob micro algo transfer: " + transferAmountMicroAlgos;
PendingTransactionResponse transactionResponse = transferService.transferAlgo(aliceAccount, bobAccount.getAddress(),
transferAmountMicroAlgos, note);
//verify the account balances are correct after the transfer
Long aliceAccountBalanceAfterTransfer = accountService.getAccountBalance(aliceAccount);
Long bobAccountBalanceAfterTransfer = accountService.getAccountBalance(bobAccount);
assertThat(aliceAccountBalanceAfterTransfer).isEqualTo(aliceAccountInitialBalance - transferAmountMicroAlgos - 1000);
assertThat(bobAccountBalanceAfterTransfer).isEqualTo(bobAccountInitialBalance + transferAmountMicroAlgos);
assertThat(getNote(transactionResponse)).isEqualTo(note);
}
Initially Alice would have 100 ALGOs and Bob would have 0. The transfer amount in the above code is 1 ALGO (or 1000000 micro ALGOs). After the transfer, we verify that both Alice’s and Bob’s accounts have expected amount of ALGOs.
6. Understanding the code
You can use the Junit test as the starting point to explore the logic implemented. Here are some of the main points:
6.1 AppConfig.java
AlgodClient is specified a Spring bean. AlgodClient enables us to interact with the Algorand network. Because of @EnableConfigurationProperties(AppProperties.class), the values of application.yaml are read into AppProperties.class Spring Bean.
@Configuration
@EnableConfigurationProperties(AppProperties.class)
public class AppConfig {
@Bean
public AlgodClient algodClient() {
final String ALGOD_API_ADDR = "localhost";
final int ALGOD_PORT = 4001;
final String ALGOD_API_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
return new AlgodClient(ALGOD_API_ADDR, ALGOD_PORT, ALGOD_API_TOKEN);
}
}
6.2 AlgoTransferService.java
This is the main logic for transferring ALGOs from fromAccount to toAddress:
public PendingTransactionResponse transferAlgo(Account fromAccount, Address toAddress, long amountMicroAlgos, String note) throws Exception {
Transaction txn = createTransaction(fromAccount, toAddress, amountMicroAlgos, note);
SignedTransaction signedTxn = signTransaction(fromAccount, txn);
String id = submitTransactionToNetwork(signedTxn);
PendingTransactionResponse pTrx = waitForTxnConfirmation(id, 4);
log.info("Transaction " + id + " confirmed in round " + pTrx.confirmedRound);
return pTrx;
}
First we create a transaction. Then we sign the transaction. Then we submit the transaction. Lastly, we wait for the transaction to be confirmed until next 4 rounds.