Skip to content

HowTo Examples

Ishinka edited this page Aug 10, 2022 · 7 revisions

Here you can find helpful tutorials and examples how to use the substrate-client-java library.

Setup

This tutorial provides information about the initial steps you have to take in order to configure you application to use our substrate-client-java library.

1. Add dependencies

In order to use the substrate-client-java library you need to add it to you application.

First, setup the necessary repositories:

repositories {
    mavenCentral()
    maven { url 'https://jitpack.io' }
    maven {
        name = "GitHubPackages"
        url = "https://maven.pkg.github.com/strategyobject/substrate-client-java"
        credentials {
            username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_USERNAME")
            password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN")
        }
    }
}

Second, add substrate-client-java library's dependencies:

def substrateClientVersion = '0.2.0'

dependencies {
        implementation "com.strategyobject.substrateclient:common:${substrateClientVersion}"
        implementation "com.strategyobject.substrateclient:crypto:${substrateClientVersion}"
        implementation "com.strategyobject.substrateclient:scale:${substrateClientVersion}"
        implementation "com.strategyobject.substrateclient:transport:${substrateClientVersion}"
        implementation "com.strategyobject.substrateclient:rpc:${substrateClientVersion}"
        implementation "com.strategyobject.substrateclient:rpc-api:${substrateClientVersion}"
        implementation "com.strategyobject.substrateclient:pallet:${substrateClientVersion}"
        implementation "com.strategyobject.substrateclient:api:${substrateClientVersion}"

        annotationProcessor "com.strategyobject.substrateclient:scale-codegen:${substrateClientVersion}"
        annotationProcessor "com.strategyobject.substrateclient:rpc-codegen:${substrateClientVersion}"
        annotationProcessor "com.strategyobject.substrateclient:pallet-codegen:${substrateClientVersion}"
}

2. Initialize API

In order to communicate with a blockchain node, we need to instantiate a component responsible for the transport layer.

Here we use the same concept of providers used in polkadot{.js}.

Currently the WebSocket provider is the only type that is implemented by our Java API. You can use it like shown in this example:

Supplier<ProviderInterface> wsProvider = WsProvider.builder().setEndpoint("ws://127.0.0.1:9944");

Use the Api.with method to create the API, as shown below:

Api api = Api.with(wsProvider).build().join();

3. Register custom readers, writers, event, etc.

The client library have the following registries:

  • ScaleReaderRegistry
  • ScaleWriterRegistry
  • RpcDecoderRegistry
  • RpcEncoderRegistry
  • EventRegistry

All readers, writers, encoders, decoders and events found in the library are registered automatically at startup. Custom objects have to be registered during the api configuration with ApiBuilder.configure() method.

For example, to register all events in a package "com.example":

try (Api api = Api.with(wsProvider)
        .configure(defaultModule ->
                defaultModule.configureEventRegistry(registry ->
                        registry.registerAnnotatedFrom("com.example")))
        .build()
        .join()) {
// ...
}

Now we have the API ready for action.

Make RPC call

This tutorial explains how to make an RPC call. First, you need to define a corresponding RPC interface. Here is an example code of a "system" interface with a single "accountNextIndex" call:

@RpcInterface("system")
public interface System {

    @RpcCall("accountNextIndex")
    CompletableFuture<Index> accountNextIndex(AccountId accountId);
}

Second, you need to instantiate an RPC interface with api.rpc() method:

final System system = api.rpc(System.class);

And then execute a call:

final Index index = system.accountNextIndex(aliceAccountId).join();

Access Storage Items

This tutorial provides an example of how to read storage structures from a pallet.

Our API uses a declarative approach and code generation. In order to access a substrate pallet, you have to describe it with a Java interface.

You can find more information about describing and reading a pallet's storage here.

The code below shows an example in Rust defining a BlockHash storage item in the System pallet.

/// Map of block numbers to block hashes.
#[pallet::storage]
#[pallet::getter(fn block_hash)]
pub type BlockHash<T: Config> =
    StorageMap<_, Twox64Concat, T::BlockNumber, T::Hash, ValueQuery>;

The above definition written in Java looks like this:

@Pallet("System")
public interface SystemPallet {
    @Storage(
            value = "BlockHash",
            keys = {
                    @StorageKey(
                            type = @Scale(BlockNumber.class),
                            hasher = StorageHasher.TWOX_64_CONCAT
                    )
            })
    StorageNMap<BlockHash> blockHash();
}

As you can see it is quite similar to the original definition in Rust. The implementation of the interface will be generated automatically at compilation time.

Next, we need to instantiate the pallet with our Java API so we can start executing queries. The code for creating the pallet is:

System systemPallet = api.pallet(System.class);

After we have initialized the pallet, we can now access its storage items. Example query for accessing the BlockHash storage:

BlockHash blockHash = systemPallet
        .blockHash()
        .get(BlockNumber.GENESIS)
        .join();

Listen to events

The Events storage is defined in System pallet, so in order to listen to the events you need to subscribe to that storage. More information on Storage API can be found on Storage page.

Supplier<CompletableFuture<Boolean>> unsubscribe = api.pallet(System.class)
    .events()
    .subscribe((exception, block, eventRecords, keys) -> {
        eventRecords.forEach(x -> out.printf("%s::(phase={\"ApplyExtrinsic\":%s})%n",
            x.getEvent().getEvent().getClass().getSimpleName(),
            x.getPhase().getApplyExtrinsicIndex()));
    }, Arg.EMPTY)
    .join();

The getEvent() method of EventRecord contains a reference to EventDescriptor, an intermediate class containing the name of the pallet that the event corresponds to, its index within the pallet, and a reference to the deserialized event itself.

Don't forget to unsubscribe:

unsubscribe.get().join();
Clone this wiki locally