Hello ❗
This article is addressed to everyone interested in a simplified way of operating with Hive blockchain (what should allow you to drop the beekeeping suit from the picture above). 😊
Here is a wax, the library which allows you to perform API calls, build transactions, then use another tool beekeeper (responsible for holding your keys) to sign them and finally do broadcast.
The library contains several parts:
- Core C++ layer which wraps
hive::protocol
code (directly shared to Hived repo). Here you can find a specific code to transaction/operation serialization (JSON and binary), transaction static validation (a step performed also by Hived node before transaction is evaluated) as well as finally a code able to calculate sig-digest, TAPOS and transaction signatures by executing exactly the same code C++ as Hive Protocol does.
The code for HP APR calculation is also put here, receiving accounts manabar specific values and standard asset representation - all very internal Hived aspects often used by frontends/client libraries and frequently causing problems too.
All the above C++ code (core-wasm and beekeeper parts) is published to TypeScript/JavaScript environment by using a WebAssembly technology. The Python part is addressed by built dedicated Cython extension. - Cross language representation of Hive operations written by using Google ProtoBuf which enabled us to generate a similar code (containing the comments describing operation details) for Python and Typescript environments. All such operations are defined close to Hive operation main definitions inside the hive::protocol library
https://gitlab.syncad.com/hive/hive/-/tree/develop/libraries/protocol/proto?ref_type=heads - Dedicated Typescript Object Oriented wrapper simplifies Wax library usage and makes it much more Intellisense compliant.
- Python layer that is mostly used by another Hive project: clive - this part of Wax library is to be improved soon (hopefully). It also makes it possible to build intuitive and natural programming interface as it has already been done for Typescript.
So let's focus on Typescript version.
We have decided to create two layers in the Typescript implementation of Wax:
- the first one which can be used offline and provides the basic set of features like converting operations to textual form, building assets, transactions, processing transaction signatures (recalculating public keys used to sign them): you can find all the provided functionality in the IWaxBaseInterface. The instance of this interface can be acquired by calling a
createWaxFoundation
function. - the second one supporting online features like automatic filling the transaction with TAPOS (while using provided TransactionBuilder) and it allows performing API calls together with enabled request and response verification. All its functionality can be found in this interface definition: IHiveChainInterface which also extends a
IWaxBaseInterface
to provide its methods too. One of the advanced usage scenarios is extending a set of APIs being supported by Hive Chain which can provide you an IDE support while calling their methods. It also allows to verify request and response.
You can find some examples presenting the usage at some interesting scenarios here: Wax-NPM.
Wax library supports both: direct webbrowser as well as NodeJS environments.
An Important note: before playing with this library, you probably need to define/change your local .npmrc
file to resolve package registry address correctly .
echo @hive:registry=https://gitlab.syncad.com/api/v4/packages/npm/ >> .npmrc
I'd like to discuss one scenario in deeper details: where we're creating a transaction: https://gitlab.syncad.com/hive/wax/-/blob/develop/npm.ts.md#create-a-signed-transaction
Beekeeper
In the very beginning. we're performing the setup of another tool: beekeeper, which initially has imported a private key 🔑 (next to be used to sign your transaction). This part is actually out of Wax library scope, but we decided to put it here to show a complete operation flow needed to build, sign and broadcast transaction.
Another thing that is worth mentioning is the fact that this step can be made separately since beekeeper finally stores imported private keys (inside a defined wallet) and next it only allows to use them (i.e. to generate a signature). It is impossible to read private keys back (using beekeeper API) from its encrypted storage. Before accessing the wallet, you have to unlock it (and provide initialy configured password) 🔓.
import beekeeperFactory from '@hive/beekeeper';
/// Build the beekeeper instance
const beekeeper = await beekeeperFactory();
/// First thing to start operating with beekeeper is establishing a session, which is additionally secured by specified salt.
const session = beekeeper.createSession("salt");
/// Once we got session, it's a time to create a wallet. Wallets are persisted to the WebBrower storage (IndexDB) or in NodeJS case to local filesystem.
const { wallet } = await session.createWallet("w0");
const myWifPrivateKey: string = '5JkFnXrLM2ap9t3AmAxBJvQHF7xSKtnTrCTginQCkhzU5S7ecPT'
/// Last action - we need to import private key
const publicKey = await wallet.importKey(myWifPrivateKey);
🔗 Wax HiveChain interface
Once beekeeper is ready to operate, we can start to play with Wax interfaces itself. Let's use a mentioned above IHiveChainInterface
:
The purpose of this interface is to join static parts together (provided by IWaxBaseInterface
) and online Hive features (mostly available by Hive APIs).
import { createHiveChain } from '@hive/wax';
/// Here important thing to note: there are (default) parameters passed to `createHiveChain` function: pointing default Hive API node instance used for communication (api.hive.blog)
const chain = await createHiveChain();
🛠 Transaction builder tool
To make transaction processing simpler, the Wax library provides a TransactionBuilder tool which can build a transaction for you and provide its several intresting properties like id
, sigDigest
as well.
Accessing its instance from the chain interface level, allows you to have automatically filled several important transaction properties as described below:
/** Now let's create a transaction builder. Important things:
* - getTransactionBuilder takes optional `expirationTime parameter`
* - getTransactionBuilder implicitly reads current Hive head block and correctly configures TAPOS for transaction you want to build
*/
const txBuilder = await chain.getTransactionBuilder();
/// Now let's add some operation to internally held transaction and at the end validate it:
txBuilder.push({
vote: {
voter: "otom",
author: "c0ff33a",
permlink: "ewxhnjbj",
weight: 2200
}
}).validate();
It is worth noting that TransactionBuilder
offers much more operation building features (than specifying them as Typescipt object). To encapsulate blockchain complexity and allow seamless use of this interface the Wax library offers several complex operation builders whose description is probably a good opportunity for another post 📜.
🖆 Signature generation
// Build and sign the transaction object. Here is passed a **public key** to identify previously imported private one.
const signedTx = txBuilder.build(wallet, publicKey);
Behind the scenes, several important things happen - very specific to blockchain internals:
- the created transaction is finally formed
- it is passed to the Wax library internals, which then try to deserialize it directly into
hive::protocol::signed_transaction
object - once it succeeds, regular Hive Protocol functions are used to binary serialize transaction, calculate transaction
id
(hash), itssigDigest
(basing also on passedchain-id
) - the last step is producing a signature from internally passed
sigDigest
, by calling IBeekeeperUnlockedWaller.signDigest - then produced signature is appended to the signature list in the transaction held by builder instance
📣 Broadcast
Once transaction has been signed, you can broadcast it by (of course) using a chain object:
/** First let's create a broadcast request: it can be built in 2 ways:
* - by passing a transaction builder object already holding your signed transaction (like in this example)
* - by passing a signed transaction object directly
*/
const request = new BroadcastTransactionRequest(txBuilder);
// Transmit
await chain.api.network_broadcast_api.broadcast_transaction(request);
🏁 Thats all ❗ Thanks for reading 🏅. We are waiting for your comments ;-)