Restful Algorand API with Spring Boot
The Spring framework allows developers to easily stand-up common applications. By creating a simple spring boot starter package we can easily abstract away some of the boilerplate initialization into properties files. These files then allow for easier deployment across different environments.
In this tutorial we’ll show how to leverage this existing Algorand Spring Boot starter along with the Spring Web library to build a restful API to interact with the Algorand ecosystem. We will provide examples of creating and retrieving accounts, assets, and transactions by leveraging the Algorand Client and Indexer along with various parameters.
In this tutorial we will use the PureStake Developer API as our source of truth for Algorand data but you can implement any Algorand Blockchain you see fit.
Requirements
algorand-spring-boot-starter
If you chose to not repackage all dependencies from the project above, you will also need the algorand-sdk
Access to an Algorand test node as described here for purposes of this tutorial I used the Purestake Developer API
Optional: API testing tool such as Postman
Java IDE such as IntelliJ, Eclipse, VS Code (with additional packages)
Maven
Background
In this tutorial you will see API Keys and address mnemonics stored in plain text. In production code you would not do this but would look into encrypting (especially your account related strings) when at rest. There are many ways to do this such as with Jasypt. Also, if hosting an API like this in a container run in a shared environment, you would look into injecting your properties as secrets into the properties file.
Steps
- 1. Get necessary dependencies
- 2. Setup your implementation project
- 3. Add application.properties file
- 4. Create an API controller
- 5. Create a new Account from a Mnemonic
- 6. Get account data from Purestake
- 7. Get asset data
- 8. Get pending transactions
- 9. Using the Indexer
- 10. Searching for Transactions
- 11. Filtering for an Asset in the configured account
- 12. Completed Code
1. Get necessary dependencies
As of right now, the alogrand-spring-boot-starter isn’t hosted in a maven or gradle repository so you will have to pull the source code and build it yourself. That can easily be done by cloning the master branch into a new project in your IDE of choice.
Once you have cloned the branch, use maven to run ‘clean’ and ‘package’ goals. You will now have a jar (without dependencies) in your project’s target directory. (This will be the jar we use for the rest of the project. If you wish to contain all dependencies within this jar (thereby not needing to import the algo sdk separately) you can do that as well but you might have to tailor the instructions to match your goals)
Now install the jar to your local maven repo by running the command:
mvn install:install-file -Dfile=<PATH TO YOUR TARGET/JAR>\algorand-spring-boot-starter-1.0-SNAPSHOT.jar -DgroupId=org.algorand -DartifactId=algorand-spring-boot-starter -Dversion=1.0-SNAPSHOT -Dpackaging=jar -DgeneratePom=true
This will place the jar in your local maven repo. You can verify that it was installed by finding the .m2 folder (sometimes hidden in windoze) and browsing to org/algorand
2. Setup your implementation project
Now create a Spring boot project either by using your editor or the Spring Initializr. If using the Spring Initialzr then use maven. The Java version and Spring boot version are up to you. Make sure to add Spring Web as a dependency. You can also manually configure all this with a plain Maven project in your IDE or you can reference my demo project.
Long story short, once you have created your project, you should have a pom file that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.algorand.starter</groupId>
<artifactId>algorand-spring-starter-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>algorand-spring-starter-demo</name>
<description>Demo project for Algorand Spring integration</description>
<properties>
<java.version>16</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
That’s a plain Spring boot starter project. No Algorand yet (unless you cloned my demo).
Now let’s add two dependencies to the pom file as follows:
<dependency>
<groupId>org.algorand</groupId>
<artifactId>algorand-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.algorand</groupId>
<artifactId>algosdk</artifactId>
<version>1.5.1</version>
</dependency>
The first adds the spring boot starter you built and installed to your maven repo in Step #1 the second adds the algorand java sdk.
If you used the Spring Intializr you should also have a main class that runs a spring boot application by default, if you didn’t you’ll have to add one yourself.
3. Add application.properties file
One of the nice things about Spring boot is it allows us to easily configure boilerplate code by supplying values from an application.properties file (among other ways). The application.properties file allows us to specify values in a config file such as urls, tokens, etc. This gives us the ability to have multiple configuration files and specify which config to use based on environment.
Create an application.properties file under the /resources folder of your project. Spring will look here by default for a application.properties file. No need to add anything now we’ll add properties in future steps.
4. Create an API controller
Under your project package root, create a package called ‘controller’. This is where we’ll put the Rest API controller we’re going to build. As such, create a new Class in this package called ‘AlgorandController’.
@RestController
public class AlgorandController { }
This controller is where, for this demo, we’ll be writing all of our integration code. In a production setting you should follow a better pattern and make use of services and such. For purposes of the demo we’ll write everything in here for simplicity.
5. Create a new Account from a Mnemonic
We’ll leverage a mnemonic embedded in the application properties file to get an instance of an account. The mnemonic is a 25 word string and is a bit gregarious in the application properties file but can be abstracted out into it’s own file if needed using various mechanisms. For purposes of the demo we’ll put it directly in the application.properties file. So, add the following line to the application.properties file:
algorand.account.mnemonic=portion never forward pill lunch organ biology weird catch curve isolate plug innocent skin grunt bounce clown mercy hole eagle soul chunk type absorb trim
Now, back in your Algorand controller add a new account object as a private variable:
@Autowired
private Account account;
The @Autowired tag will tell Spring to look in the application properties file for the necessary configuration parameters and will automatically spin up an account object for your controller to use.
Now, let’s add a new method that will supply an endpoint for us to retrieve our account details:
@GetMapping(path="/account", produces = "application/json; charset=UTF-8")
public String account() throws Exception {
return "Account Address: " + account.getAddress();
}
Now, you can use maven to perform a spring boot run and your application will spin up a new host on port 8080. You can hit http://localhost:8080/account and you should see the message ‘Acocunt Address: <the address seed based on mnemonic>’ Nothing terribly fancy but it proves that everything is working.
6. Get account data from Purestake
In a similar manner to the last step we’re going to create another object (AlgodClient) to connect with the Purestake Developer API. First, we’re going to add the AlgodClient parameters to our application properties file as so:
algorand.algodclient.host=https://testnet-algorand.api.purestake.io/ps2
algorand.algodclient.port=443
algorand.algodclient.token=
In the case of integrating with Purestake, we do not initialize the Client with the API token. Firstly, because in this version of the SDK, the API token hasn’t been created as a parameter for the client yet and secondly, Purestake expects the API token to be included with the header request (probably because of the previous point). Feel free to use whatever token (not api token) suits your fancy in the config file above.
Now, back in our AlgodController we’re going to initialize a new AlgodClient private variable:
@Autowired
private AlgodClient client;
Now we need to add specific headers for Purestake to our class:
String[] headers = {"X-API-Key"};
String[] values = {"<your-api-key-here>"};
Then change the return line of the account() method to this:
return client.AccountInformation(account.getAddress()).execute(headers, values).body().toString();
The above return will use the AlgodClient to retrieve the account information from the blockchain for the address we specified in the properties file. Do note, that similar functionality could have been done for a lookup method by passing the account mnemonic as a parameter to the method and creating an account to pass along on the fly.
Navigating to http://localhost:8080/account will now give you all of the account information recorded (on the testnet) for the address you specified.
7. Get asset data
Riffing on the previous step, we can also use the AlgodClient to obtain information on an individual asset. For this step we don’t need to add anything other than another controller method which will create a new endpoint for us. Let’s add the following to our controller:
@GetMapping(path="/asset", produces = "application/json; charset=UTF-8")
public String asset(@RequestParam Long assetId) throws Exception {
return client.GetAssetByID(assetId).execute(headers, values).body().toString();
}
This method will take an assetId as a parameter and use it to lookup the asset on the chain. It will then return the values for the asset. You can test it out by adding it and relaunching the app and browsing to http://localhost:8080/asset?assetId=17691
. You can use any assetID you see in say your previous account request and you should see details similar to the following:
8. Get pending transactions
Another easy implementation is searching for pending transactions against an account. We’ll use the account we instantiated with our properties file but you can easily mod this method to take an address parameter and allow a user to pass in account info.
@GetMapping(path="/pendingTransactions", produces = "application/json; charset=UTF-8")
public String pendingTransactions() throws Exception {
return client.GetPendingTransactionsByAddress(account.getAddress()).execute(headers, values).toString();
}
9. Using the Indexer
Now we’ll do something a little different. We’ll use the IndexerClient to search the blockchain for assets. In a similar manner to above, we’re first going to add our configuration properties for the Indexer to the application.properties file, as so (note the host is different from above):
algorand.algodindexer.host=https://testnet-algorand.api.purestake.io/idx2
algorand.algodindexer.port=443
algorand.algodindexer.token=
Now we’ll return to the controller class and add a new private variable for the IndexerClient:
@Autowired
private IndexerClient indexer;
Now, we’ll add a new controller method to search for assets on the chain:
@GetMapping(path="/assetSearch", produces = "application/json; charset=UTF-8")
public String assetSearch(@RequestParam String assetName) throws Exception {
return indexer.searchForAssets().name(assetName).limit(1L).execute(headers, values).toString();
}
The above method takes in a string representing an asset name and searches the block chain for said asset. We use the .limit() method to specify that we want one result but we can specify a different limit or even allow the user to specify this as a parameter. Do keep performance considerations in mind. We’ll get output similar to this:
10. Searching for Transactions
Again we’ll leverage the Indexer but this time we’ll generically search for transactions. In the controller add a new method:
@GetMapping(path = "/accountTransactionsBefore", produces = "application/json; charset=UTF-8")
public String accountTransactionsBeforeDate(@RequestParam Date date) throws Exception {
return indexer.lookupAccountTransactions(account.getAddress()).beforeTime(date).limit(1L).execute(headers, values).toString();
}
This method will take a date specified by a user an use it to search the block chain for one transaction before the specified date. There are about a dozen modifiers to this search at the time of this writing so it would be trivial for someone to write a more robust method that allows a user to specify more search parameters. One could build a complex query builder and pair it with a UI to allow a user to easily explore the blockchain by parameter specification. When the above is executed, one should see somthing similar to:
11. Filtering for an Asset in the configured account
In this instance we’ll pass a filter name in order to locate an asset at our given account (specified in the properties file). In the controller add a new method as described below:
@GetMapping(path="/accountAssetSearch", produces = "application/json; charset=UTF-8")
public String account(@RequestParam String assetName) throws Exception {
Asset returnAsset = new Asset();
List<Asset> createdAssets =
client.AccountInformation(account.getAddress()).execute(headers, values).body().createdAssets;
for (Asset asset : createdAssets) {
if(asset.params.name.equals(assetName)){
returnAsset = asset;
}
}
return returnAsset.toString();
}
This method will take a name as a filter specified by the user and use that value to loop through the assets contained in the specified account (specified by the properties file). Do note that there are other ways to do this logic using the indexer but this allows the user to do the lifting via Java versus having to engage the API. Once executed on the account and locating an asset with a valid name as specified the user should see something like this:
12. Completed Code
The functionality described can easily be extended. Also, one could easily write API endpoints to sign and send transactions and there are several good tutorials here on signing and sending transactions.
The completed code can be found on Github .