Skip to the content.

LibSimba4J Blocks Platform Documentation

This page provides usage examples of the Java SIMBA Chain Blocks Platform client.

JavaDocs are available here

Some notes on the Blocks API (to be extended) are available here

Getting Started

To use LibSimba4J include it as a dependency. For Maven builds add the following dependency to your pom file:

<dependency>
    <groupId>com.simbachain</groupId>
    <artifactId>libsimba4j-platform</artifactId>
    <version>1.0.4</version>
</dependency>

or for Gradle:

repositories {
    mavenCentral()
}

dependencies {
    implementation 'com.simbachain:libsimba4j-platform:1.0.4'
}

To build from source, LibSimba4J builds with maven. You will need to have maven 3.* installed. Once you have maven, cd into the top level directory and type:

mvn install

Configuration File

Configuration is loaded consistent with other SIMBA client tools. The config file should be in dotenv format and should be called .simbachain.env or simbachain.env (i.e. a visible variant) or .env.

This can be placed on the classpath, or can be placed anywhere if the environment variable SIMBA_HOME is set. This variable should point to the directory containing the dotenv file. The SIMBA_HOME variable defaults to the user’s home directory, e.g. ~/

The search order for this file is:

The config setup supports in memory env vars taking precedence over values in the file. All environment variables for Libsimba4J are prefixed with SIMBA_.

Auth Configuration

SIMBA Blocks Platform uses OAuth 2. Two auth providers are currently supported: Blocks and Keycloak.

NOTE: The Blocks provider is currently the default and should be used in all cases. The Keycloak provider may be used in the future.

For Blocks the configuration will look something like below, i.e., the SIMBA_AUTH_BASE_URL and SIMBA_API_BASE_URL are the same:

SIMBA_AUTH_CLIENT_SECRET=...
SIMBA_AUTH_CLIENT_ID=...
SIMBA_AUTH_BASE_URL=https://my.blocks.server
SIMBA_API_BASE_URL=https://my.blocks.server

For keycloak, the configuration will look more like, below:

SIMBA_AUTH_CLIENT_SECRET=...
SIMBA_AUTH_CLIENT_ID=...
SIMBA_AUTH_REALM=simbachain
SIMBA_API_BASE_URL=https://my.blocks.server
SIMBA_AUTH_BASE_URL=https://my.keycloak.server

Additionally, a SIMBA_TOKEN_DIR file location can be set to save tokens to file. Combined with setting writeToFile on the auth config will cause the token to be written to file. This is useful if you are making many calls but are closing down the application and cannot therefore cache in memory.

These values can also be directly set an environment variables if you don’t use a dot env file.

Other values can be stored in the env file if required. Any values set directly as environment variables will override values in the file.

The config is loaded using the SimbaConfigFile class. For example:

import com.simbachain.SimbaConfigFile;

SimbaConfigFile config = new SimbaConfigFile();

String clientId = config.getAuthClientId();
String clientSecret = config.getAuthClientSecret();
String authHost = config.getAuthBAseUrl();
String host = config.getApiBaseUrl();

BlocksConfig authConfig = new BlocksConfig(clientId, clientSecret, authHost);
AuthenticatedUser user = new AuthenticatedUser(host, authConfig);

System.out.println("Authenticated user: " + user.whoami());

Logging

Libsimba4J uses SLF4J for logging. All logging is at Debug level.

Setting up a SIMBA Blocks Client

The first thing to do is configure authentication. See above for configuring auth.

Once you have an auth config established, you can create services. Let’s start with the OrganisationService. This provides management tasks to list resources, and deploy code and contracts. The following example shows how this is done.

First create an OrganisationConfig instance. This takes your auth config and the name of an organisation you are a member of.

Then create an OrganisationService passing in the org config and the root endpoint of the SEP service.

BlocksConfig authConfig = new BlocksConfig(clientId, clientSecret, authHost);
OrganisationConfig orgConfig = new OrganisationConfig("simbachain", config);
OrganisationService orgService = new OrganisationService(host, orgConfig);

The OrganisationService class provides access to general org-level APIs: List applications, contract design, artifacts and deployed contracts:

PagedResult<Blockchain> bcs = orgService.getBlockchains();
List<? extends Blockchain> bcsResults = bcs.getResults();
for (Blockchain result : bcsResults) {
    System.out.println(result.getName());
}

PagedResult<Storage> sts = orgService.getStorages();
List<? extends Storage> stsResults = sts.getResults();
for (Storage result : stsResults) {
    System.out.println(result.getName());
}
    
PagedResult<Application> apps = orgService.getApplications();
List<? extends Application> results = apps.getResults();
for (Application result : results) {
    System.out.println(result.getName());
}

PagedResult<ContractDesign> cds = orgService.getContractDesigns();
List<? extends ContractDesign> cdsResults = cds.getResults();
for(ContractDesign result:cdsResults){
    System.out.println(result.getName());
}

PagedResult<ContractArtifact> cas = orgService.getContractArtifacts();
List<? extends ContractArtifact> casResults = cas.getResults();
for(ContractArtifact result:casResults){
    System.out.println(result.getName());
}

PagedResult<DeployedContract> dcs = orgService.getDeployedContracts();
List<? extends DeployedContract> dcsResults = dcs.getResults();
for(DeployedContract result:dcsResults){
    System.out.println(result.getApiName());
}

You can also iterate over pages, e.g.:

List<? extends ContractDesign> cdsResults = cds.getResults();
for (ContractDesign result : cdsResults) {
    System.out.println(result.getName());
}
while (cds.getNext() != null) {
    cds = orgService.nextContractDesigns(cds);
    cdsResults = cds.getResults();
    for (ContractDesign result : cdsResults) {
        System.out.printf("next returned contract design: %s%n", result);
    }
}

Deploying Contracts

In the simple case, deploying a contract involves supplying solidity code and a contract name. The name is not required to be unique. You create a design and compile it using a CompilationSpec object. This takes a number of fields:

Along with the compilation spec, supply a String or an InputStream which is the code itself.

InputStream in = CompileDeployTest.class.getResourceAsStream("/supply.sol");

CompilationSpec compSpec = new CompilationSpec().withName(apiName);
ContractDesign design = orgService.compileContract(in, compSpec);
System.out.println(design);

Once a design is created, you can continue to modify it. Before deploying a contract, you should create a contract artifact. This is a frozen snapshot of a design and is used as the information to deploy.

To create a snapshot of the code as an artifact pass in the identifier of the contract design:

ContractArtifact artifact = orgService.createArtifact(design.getId());

To deploy the artifact, use a DeploymentSpec. This is used to spell out the configuration of the deployment including:

DeploymentSpec spec=new DeploymentSpec()
    .withApiName(apiName)
    .withBlockchain("Quorum")
    .withStorage("azure")
    .withAppName("neo-supplychain");


Future<DeployedContract> future = orgService.deployContract(artifact.getId(),spec);
DeployedContract contract = future.get();
System.out.println(contract);

Once you have a deployed contract, you can create a ContractService to interact directly with it.

The ContractService creation method of OrganisationService takes an Application name, and the API name you gave to the DeploedContract.

When creating the contract service, the auth config is passed to it.

ContractService contractService = orgService.newContractService("supplychain", apiName);

Calling and Querying Contract Methods

You can now call methods on the contract:

JsonData supplyData=JsonData.with("price", 120)
    .and("dateTime", System.currentTimeMillis())
    .and("supplier", JsonData.with("__Supplier", "Supplier3.32"))
    .and("purchaser", JsonData.with("__Supplier", "Supplier2.11"))
    .and("part", JsonData.with("__Part", "Part542"));

CallResponse ret=contractService.callMethod("supply", supplyData);
System.out.println("Got back response: " + ret);

To wait for a transaction to complete, retrieve a future object using the transaction id:

Future<Transaction> transactionFuture = contractService.waitForTransactionCompletion(ret.getRequestIdentitier());
Transaction result = transactionFuture.get();

This is invoking a contract method defined as:

struct Supplier {
    string __Supplier;
}

struct Part {
    string __Part;
}

function supply (
    Supplier memory supplier,
    Supplier memory purchaser,
    Part memory part,
    uint price,
    uint dateTime
) public {
}

For methods that are getters, i.e., do not create transactions and do return values, the callGetter function can be used. The json data here is converted to query paramters and the class defines the expected return type.

CallReturn<String> getterResponse = contractService.callGetter("getSupplier", String.class, JsonData.with("pk", "1234567890"));
System.out.println("getter response: " + getterResponse.getReturnValue());

For methods that do create transactions, you can query for them using filters and select a subset of fields to return:

List<String> fields = new ArrayList<>();
fields.add("method");
fields.add("inputs");

PagedResult<Transaction> results = simba.getTransactions("supply", Query.in("inputs.part.__Part", "Part542"), fields);
txns = results.getResults();
for (Transaction transaction : txns) {
    System.out.println(String.format("returned transaction: %s", transaction));
}
// Call next, if there are more results.
while (results.getNext() != null) {
    results = simba.next(results);
    txns = results.getResults();
    for (Transaction transaction : txns) {
        System.out.println(String.format("next returned transaction: %s", transaction));
    }
}

Methods with File Uploads

The special _bundleHash parameter name is used to determine whether a method has been defined to accept file uploads. This field should not be populated by the client side. Instead, if files are present in the current request and the field is defined on the method, then the files are written to off-chain storage with the JSON manifest being constructed and written out alongside the files. The manifest contains the content hashes of the files along with metadata and timestamp. The content hash of the JSON manifest is then set to be the value of the _bundleHash field. This hash value is then what ends up on the chain in the transaction.

To create a bundle, the function must have a _bundleHash parameter in the function.

Below is an example of submitting files and getting the bundle hash back. The UploadFileclass is used to define the data to upload.

JsonData nonConformanceData = JsonData.with("dateTime", System.currentTimeMillis())
    .and("reason", "Non conformant")
    .and("source", JsonData.with("__DataSource", "DataSource2.11"))
    .and("part", JsonData.with("__Part", "Part542"));

headers = new HashMap<>();

InputStream report = CompileDeployExample.class.getResourceAsStream("/ConformanceReport.pdf");
SimbaClient.UploadFile uploadFile = new SimbaClient.UploadFile("ConformanceReport", "application/pdf", report); 
CallResponse bundleCall = contractService.callMethod("nonConformance", nonConformanceData, headers, uploadFile);
Future<Transaction> ftxn = contractService.waitForTransactionCompletion(bundleCall.getRequestIdentitier());
Transaction txn = ftxn.get();
Map<String, Object> inputs = txn.getInputs();
String bundleHash = (String) inputs.get("_bundleHash");
System.out.println("Bundle Hash: " + bundleHash);

Using the bundle hash, you can query for the entire bundle which will be in gzip format, or files inside it, or the JSON manifest describing the bundle. When downloading files, specify the location to write locally to.

File file = new File(dataDir, "MyReport.pdf");
BufferedOutputStream bout = new BufferedOutputStream(new FileOutputStream(file)); 
contractService.getBundleFileForTransaction(bundleHash, "ConformanceReport.pdf", bout);

Manifest manifest = contractService.getBundleMetadataForTransaction(bundleHash);
System.out.println("Manifest: " + manifest);

Retrieving Contract Events

To query for events emitted by a contract, call the getTransactionEvents method of the contract service. This can include query parameters:

PagedResult<TransactionEvent> eventResults = contractService.getTransactionEvents("SupplyEvent", Query.icontains("inputs.supplier", "supplier3.32"));
List<? extends TransactionEvent> evts = eventResults.getResults();
for (TransactionEvent evt : evts) {
    System.out.println(String.format("returned transaction event: %s", evt));
}

Client Side Signing Transactions

Using the Wallet abstract class, you can create either FileWallet wallets from mnemonics or use the Account class initialised with a private key. Once you have a wallet instance, you can use the client side signing API. This involves sending the payload to Blocks and receiving the unsigned transaction back, signing it and then posting back the signed transaction for submission.

You can set wallets on an OrganisationService or a ContractService. Wallets are stored in a map, keyed to their address. If you set them on an org service, it will pass the wallets through to the contract services it creates.

To use a wallet for a transaction, add the txn-sender HTTP header with the address of the wallet. This is exposed as an Enum ContractService.Headers.HTTP_HEADER_SENDER:

Account acc = new Account("22aabb811efca4e6f4748bd18a46b502fa85549df9fa07da649c0a148d7d5530");
orgService.setWallet(acc);
Map<String, String> headers = new HashMap<>();
deployHeaders.put(ContractService.Headers.HTTP_HEADER_SENDER.getValue(), acc.getAddress());
Future<DeployedContract> ssFuture = orgService.deployContract(ssSpec, headers);

And calling a method:

JsonData ssSupplyData = JsonData.with("price", 120)
     .and("dateTime", System.currentTimeMillis())
     .and("supplier", JsonData.with("__Supplier", "Supplier3.33"))
     .and("purchaser", JsonData.with("__Supplier", "Supplier2.12"))
     .and("part", JsonData.with("__Part", "Part542"));

headers.put(ContractService.Headers.HTTP_HEADER_SENDER.getValue(), acc.getAddress());
CallResponse signedRet = ssContractService.callMethod("supply", ssSupplyData, headers);

Libsimba4J will map the stored wallets against the value in the header to select the chosen wallet.

Available HTTP Headers

As described above, the txn-sender header can be used to change the server behaviour to not attempt server side signing using a custodial wallet. The following headers are available for use with transactions. These are all defined in the ContractService.Headers enum.

Generated Classes

Using the ContractService you can also ask it to generate source code for you to simplify contract interactions:

String path = contractService.generateContractPackage("com.supplychain", "./");
System.out.println(path);

This will generate a Java class based on the contract.