diff --git a/README.adoc b/README.adoc index 1745729..f9033e3 100644 --- a/README.adoc +++ b/README.adoc @@ -26,6 +26,7 @@ WARNING: UNDER DEVELOPMENT - `io.emeraldpay.polkaj:polkaj-api-base:{lib-version}` - RPC base classes - `io.emeraldpay.polkaj:polkaj-api-http:{lib-version}` - JSON RPC HTTP client - `io.emeraldpay.polkaj:polkaj-api-ws:{lib-version}` - JSON RPC WebSocket client +- `io.emeraldpay.polkaj:polkaj-tx:{lib-version-dev}` - Storage access and Extrinsics == Usage diff --git a/docs/05-balance.adoc b/docs/05-balance.adoc deleted file mode 100644 index b9b43dd..0000000 --- a/docs/05-balance.adoc +++ /dev/null @@ -1,182 +0,0 @@ -= Working with Account Balance - -== How it works - -With Polkadot you use Runtime to get value from storage or submit transactions (which are called _Extrinsics_). - -To read data from storage, you prepare a query encoded as a hex string, and the result is usually a SCALE encoded object. -The encoded requests sent to `state_getStorage` (or related), and the response is a hex + SCALE encoded object. -The actual request parameters and the response type are described in the current Runtime Metadata object. - -To send and execute an Extrinsic, is must be encoded as SCALE as well, signed, and submitted with `author_submitExtrinsic` (or related). - -Polkaj is designed to be Runtime agnostic, and any of such requests could be build using Polkaj provided modules: - - - `polkaj-scale`, `polkaj-scale-types` - - `polkaj-api-http` or `polkaj-api-ws` - - `polkaj-schnorrkel` - - `polkaj-tx`, with _Hashing_ and _Signer_ classes specifically - -But Polkaj also provides Java-friendly wrappers to make some of the common operations, such as balance transfer (with `AccountRequests`). - -== Account specific requests - -`AccountRequests` class provides common operations for address and balance. -There are two types of operations: _Storage Request_ and _Extrinsic Request_, with respective interfaces. - -.`StorageRequest` interface -- provides `execute(PolkadotApi)` method to fetch the data -- or `ByteData encodeRequest()` method, as the RPC request query if you want to fetch manually -- by itself it `extends Function` to convert RPC response to a Java object, you need it when you make a manual request - -.`ExtrinsicRequest` interface -- provides `ByteData encodeRequest()` which encodes the transaction to send to RPC - -=== Get total amount of issued coins - -`totalIssuance()` method of `AccountRequests` class provides a functionality to request and process total amount of currently issues coins. -For the query it encodes request to storage function `TotalIssuance` of the module `Balances` on the current Runtime. - -[source, java] ----- -try ( - PolkadotHttpApi client = PolkadotWsApi.newBuilder() - .connectTo("wss://cc3-5.kusama.network") - .build() -) { - DotAmount total = AccountRequests.totalIssuance() - // execute on RPC - .execute(client) - // get the value synchroniously - .get(); - - System.out.println( - "Total Issued: " + DotAmountFormatter.autoFormatter().format(total) - ); -} ----- - -=== Get balance - -`balanceOf(address)` method of `AccountRequests` class provides a functionality to request and process account info for the specified address. -For the query it encodes request to storage function `System` of the module `Account` on the current Runtime. - -[source, java] ----- -try ( - PolkadotHttpApi client = PolkadotWsApi.newBuilder() - .connectTo("wss://cc3-5.kusama.network") - .build() -) { - // request balance of Alice - Address alice = Address.from("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"); - - // execute on RPC - AccountInfo accountInfo = AccountRequests.balanceOf(alice) - .execute(client) - .get(); - - System.out.println( - "Current balance: " + - DotAmountFormatter.autoFormatter().format(accountInfo.getData().getFree()) - ); - System.out.println( - "Current nonce : " + - accountInfo.getNonce() - ); -} ----- - -=== Transfer balance - -Creating an _Extrinsic_, such as a balance transfer, is a bit more complex task, since it depends on the current Runtime state and other details of the current blockchain. -So first you need to fetch all of them and use as part of the encoded value and a signature. - -`AccountRequests.transfer()` provides a _builder_ to prepare the extrinsic. -But you also need to prepare `ExtrinsicContext` to sign the extrinsic. - -There are two ways to prepare such context: manual and automatic from current RPC. -To build it manually, use `ExtrinsicContext.newBuilder()` and set all values (genesis, runtime version, and nonce). -The other way is to use `ExtrinsicContext.newAutoBuilder()` and fetch all those values from RPC. - -.Manual context -[source, java] ----- -// Current runtime version -RuntimeVersionJson runtimeVersion = client.execute( - StandardCommands.getInstance().getRuntimeVersion() - ).get(); - -// Blockchain genesis block -Hash256 genesis = client.execute( - StandardCommands.getInstance().getBlockHash(0) - ).get(); - -// Sender address info -AccountInfo accountInfo = AccountRequests.balanceOf(alice) - .execute(client) - .get(); - -// Build a context for the execution -ExtrinsicContext context = ExtrinsicContext.newAutoBuilder(alice) - // genesis block - .genesis(genesis) - // runtime version - .runtime(runtimeVersion) - // current sender nonce - .nonce(accountInfo.getNonce()) - .build(); ----- - -.Automatic context -[source, java] ----- -ExtrinsicContext context = ExtrinsicContext.newAutoBuilder(alice, client) - // synchronious - .get() - .build(); ----- - -Automatic context builder is easier to use, but with the manual builder you can save few RPC requests when you make multiple transfers. - -.Full example -[source, java] ----- -try ( - PolkadotHttpApi client = PolkadotWsApi.newBuilder() - .connectTo("wss://cc3-5.kusama.network") - .build() -) { - // Build a context for the execution - ExtrinsicContext context = ExtrinsicContext.newAutoBuilder(alice, client) - .get() - .build(); - - // Current runtime meta - Metadata metadata = client.execute( - StandardCommands.getInstance().stateMetadata() - ) - .thenApply(ScaleExtract.fromBytesData(new MetadataReader())) - .get(); - - // And build an actual call to the runtime method - AccountRequests.Transfer transfer = AccountRequests.transfer() - // get standard details from metadata (module and method id, etc) - .runtime(metadata) - // sender - .from(alice) - // recipient - .to(bob) - // amount to transfer - .amount(amount) - // sign with the context - .sign(aliceKey, context) - .build(); - - // Finally, submit to the blockchain - Hash256 txid = client.execute( - StandardCommands.getInstance() - .authorSubmitExtrinsic(transfer.encodeRequest()) - ).get(); -} ----- \ No newline at end of file diff --git a/docs/05-extrinsics.adoc b/docs/05-extrinsics.adoc new file mode 100644 index 0000000..5e5216f --- /dev/null +++ b/docs/05-extrinsics.adoc @@ -0,0 +1,94 @@ += Extrinsics and Storage + +== How it works + +With Polkadot you use Runtime to get value from storage or submit transactions (which are called _Extrinsics_). + +To read data from storage, you prepare a query encoded as a hex string, and the result is usually a SCALE encoded object. +The encoded requests sent to `state_getStorage` (or related), and the response is a hex + SCALE encoded object. +The actual request parameters and the response type are described in the current Runtime Metadata object. + +To send and execute an Extrinsic, is must be encoded as SCALE as well, signed, and submitted with `author_submitExtrinsic` (or related). + +Polkaj is designed to be Runtime agnostic, and any of such requests could be build using Polkaj provided modules: + +- `polkaj-scale`, `polkaj-scale-types` +- `polkaj-api-http` or `polkaj-api-ws` +- `polkaj-schnorrkel` +- `polkaj-tx`, with _Hashing_ and _ExtrinsicSigner_ classes specifically + +== Storage Request + +.`StorageRequest` interface +- provides `execute(PolkadotApi)` method to fetch the data +- or `ByteData encodeRequest()` method, as the RPC request query if you want to fetch manually +- methods `boolean isKeyEqualTo(ByteData key)` allows to verify the storage response, when used together with `state_subscribeStorage` RPC Subscription +- it `extends Function` to convert RPC response to a Java object, you may need it if you make a manual request + +== Extrinsic Context + +_Extrinsic_ details depend on the current Runtime state and other details of the current blockchain, such as height and genesis. +To create an Extrinsic you need to have all of them and use as a part of the encoded value and a signature. +It's all container by `ExtrinsicContext` class, which you have to prepare to sign an extrinsic. + +There are two ways to prepare such context: manual and automatic from current RPC. + +To build it manually, use `ExtrinsicContext.newBuilder()` and set all values (genesis, runtime version, and nonce). +The other way is to use `ExtrinsicContext.newAutoBuilder()` and fetch all those values from RPC. + +.Manual context +[source, java] +---- +// Current runtime version +RuntimeVersionJson runtimeVersion = client.execute( + StandardCommands.getInstance().getRuntimeVersion() + ).get(); + +// Blockchain genesis block +Hash256 genesis = client.execute( + StandardCommands.getInstance().getBlockHash(0) + ).get(); + +// Sender address info +AccountInfo accountInfo = AccountRequests.balanceOf(alice) + .execute(client) + .get(); + +// Build a context for the execution +ExtrinsicContext context = ExtrinsicContext.newAutoBuilder(alice) + // genesis block + .genesis(genesis) + // runtime version + .runtime(runtimeVersion) + // current sender nonce + .nonce(accountInfo.getNonce()) + .build(); +---- + +.Automatic context +[source, java] +---- +ExtrinsicContext context = ExtrinsicContext.newAutoBuilder(alice, client) + // synchronious + .get() + .build(); +---- + +Automatic context builder is easier to use, but with the manual builder you can save few RPC requests when you make multiple transfers. + + +== Extrinsic Signer + +Class `ExtrinsicSigner` provides methdos for making a signature, or signature verification. +It's typed with a `Call` to sign, and supposed to be created with a SCALE writer for the call type it support. + +.There're two main methods: +- `Hash512 sign(ExtrinsicContext ctx, CALL call, Schnorrkel.KeyPair key)` to sign the call under provided call context +- and `boolean isValid(ExtrinsicContext ctx, CALL call, Hash512 signature, Address address)` to verify an existing signature + +== Extrinsic Request + +Extrinsic Request class combines _Call Data_ and _Signature_ and prepares a payload to broadcast to the blockchain. + +.`ExtrinsicRequest` interface +- provides `ByteData encodeRequest()` which encodes the transaction to send to RPC \ No newline at end of file diff --git a/docs/06-balance.adoc b/docs/06-balance.adoc new file mode 100644 index 0000000..51a5538 --- /dev/null +++ b/docs/06-balance.adoc @@ -0,0 +1,108 @@ += Working with Account Balance + +Polkaj provides Java-friendly wrappers to make some common operations. +`AccountRequests` class provides common operations for address and balance. + +== Get total amount of issued coins + +`totalIssuance()` method of `AccountRequests` class provides a functionality to request and process total amount of currently issues coins. +For the query it encodes request to storage function `TotalIssuance` of the module `Balances` on the current Runtime. + +[source, java] +---- +try ( + PolkadotHttpApi client = PolkadotWsApi.newBuilder() + .connectTo("wss://cc3-5.kusama.network") + .build() +) { + DotAmount total = AccountRequests.totalIssuance() + // execute on RPC + .execute(client) + // get the value synchroniously + .get(); + + System.out.println( + "Total Issued: " + DotAmountFormatter.autoFormatter().format(total) + ); +} +---- + +== Get balance + +`balanceOf(address)` method of `AccountRequests` class provides a functionality to request and process account info for the specified address. +For the query it encodes request to storage function `System` of the module `Account` on the current Runtime. + +[source, java] +---- +try ( + PolkadotHttpApi client = PolkadotWsApi.newBuilder() + .connectTo("wss://cc3-5.kusama.network") + .build() +) { + // request balance of Alice + Address alice = Address.from("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"); + + // execute on RPC + AccountInfo accountInfo = AccountRequests.balanceOf(alice) + .execute(client) + .get(); + + System.out.println( + "Current balance: " + + DotAmountFormatter.autoFormatter().format(accountInfo.getData().getFree()) + ); + System.out.println( + "Current nonce : " + + accountInfo.getNonce() + ); +} +---- + +== Transfer balance + +`AccountRequests.transfer()` provides a _builder_ to prepare the extrinsic. +Use it to set values, such as _from_, _to_, and _amount_ to transfer. + +To make a valid extrinsic, it also needs the current Runtime Metadata and Extrinsic Context with Key Pari to make a signature. + +.Example +[source, java] +---- +try ( + PolkadotHttpApi client = PolkadotWsApi.newBuilder() + .connectTo("wss://cc3-5.kusama.network") + .build() +) { + // Build a context for the execution + ExtrinsicContext context = ExtrinsicContext.newAutoBuilder(alice, client) + .get() + .build(); + + // Current runtime meta + Metadata metadata = client.execute( + StandardCommands.getInstance().stateMetadata() + ) + .thenApply(ScaleExtract.fromBytesData(new MetadataReader())) + .get(); + + // And build an actual call to the runtime method + AccountRequests.Transfer transfer = AccountRequests.transfer() + // get standard details from metadata (module and method id, etc) + .runtime(metadata) + // sender + .from(alice) + // recipient + .to(bob) + // amount to transfer + .amount(amount) + // sign with the context + .sign(aliceKey, context) + .build(); + + // Finally, submit to the blockchain + Hash256 txid = client.execute( + StandardCommands.getInstance() + .authorSubmitExtrinsic(transfer.encodeRequest()) + ).get(); +} +---- \ No newline at end of file diff --git a/docs/README.adoc b/docs/README.adoc index 5493cce..69c4a4e 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -6,7 +6,8 @@ . link:02-encoding.adoc[Encoding and Decoding (SCALE, SS58)] . link:03-rpc-client.adoc[RPC Client] . link:04-cryptography.adoc[Cryptography and Keys] -. link:05-balance.adoc[Working with Account Balance] +. link:05-extrinsics.adoc[Extrinsics and Storage] +. link:06-balance.adoc[Working with Account Balance] == Reference diff --git a/examples/README.adoc b/examples/README.adoc index 1929baa..ac3ae19 100644 --- a/examples/README.adoc +++ b/examples/README.adoc @@ -76,8 +76,12 @@ cd balance ./gradlew run ---- +NOTE: To run a development network use: `polkadot --dev` + .Transfer using real network (ex. Kusama) ---- cd balance ./gradlew run --args="wss://cc3-5.kusama.network SENDER_KEY_SEED RECIPIENT_ADDRESS" ----- \ No newline at end of file +---- + +WARNING: If you use on a real network please be aware that it executes the transfer without any additional convirmation from you \ No newline at end of file