#
Extending Chain
When writing an advanced Hive blockchain application, you may want to add more APIs to the standard set of Wax methods. There is a feature in Wax called extend or extendRest (for REST API) allowing you to extend Wax Chain with fully-typed requests with full typization.
#
Manually extending JSON-RPC API
import { createHiveChain, TWaxApiRequest } from '@hiveio/wax';
const chain = await createHiveChain();
interface IsKnownTransactionRequest {
id: string;
}
interface IsKnownTransactionResponse {
is_known: boolean;
}
// Create the proper API structure
type TExtendedApi = {
database_api: { // API
is_known_transaction: TWaxApiRequest<
IsKnownTransactionRequest,
IsKnownTransactionResponse
>; // Method
}
};
const extended = chain.extend<TExtendedApi>();
// Call the database_api API using our extended interface
const result = await extended.api.database_api.is_known_transaction({
id: "0000000000000000000000000000000000000000"
});
console.info(result);
{ "is_known": false }
As you can see in the example, there is a type called: TWaxApiRequest which as a first template argument takes a user input type (that the user will have to pass to the API request function). It may be an interface, but it can also be a standard type, like: boolean, number, Array and so on. The second argument should be the response type (type of result in the snippet above).
import asyncio
from msgspec import Struct
from beekeepy.handle.remote import AbstractAsyncApi
from wax import create_hive_chain
class BlockHeader(Struct):
previous: str
timestamp: str
witness: str
transaction_merkle_root: str
extensions: list
# Define a class-structure for the block header using msgspec's Struct
class BlockHeaderResponse(Struct):
header: BlockHeader # Define a structure for the block header using msgspec's Struct
# Define an API for interacting with block headers
class BlockApi(AbstractAsyncApi):
# This method will be implemented to fetch the block header for a given block number
@AbstractAsyncApi.endpoint_jsonrpc
async def get_block_header(self, *, block_num: int) -> BlockHeaderResponse: ...
# As the final step you need to create a new class that will contain all your custom APIs.
class NewApiCall:
def __init__(self):
self.block_api = BlockApi
async def main():
chain = create_hive_chain()
extended = chain.extends(NewApiCall)
print(await extended.api.block_api.get_block_header(block_num=123))
asyncio.run(main())
BlockHeaderResponse(header=BlockHeader(previous='0000007a514fd4034a39ff8bb4225760ccf61154', timestamp='2016-03-24T16:11:39', witness='initminer', transaction_merkle_root='0000000000000000000000000000000000000000', extensions=[]))
#
Manually extending REST API
import { createHiveChain } from '@hiveio/wax';
const chain = await createHiveChain();
interface BlockHeaderRequest {
blockNum: number;
}
interface BlockHeaderResponse {
witness: string;
previous: string;
timestamp: string;
extensions: object[];
transaction_merkle_root: string;
};
// Note: We have to first provide the type of our API with
// proper structure in the generics for IntelliSense
const extended = chain.extendRest<{
hafahApi: {
blocks: {
blockNum: {
header: {
params: BlockHeaderRequest;
result: BlockHeaderResponse;
}
}
}
}
}>
// Then here we provide the implementation details as
// a function argument for runtime evaluation.
// This helps to deduce template values in the URL
// (provided {} characters) and potentially change HTTP methods
({
hafahApi: {
urlPath: 'hafah-api',
blocks: {
blockNum: {
urlPath: '{blockNum}',
header: {
method: 'GET'
}
}
}
}
});
// Call the hafah API using our extended interface
const result = await extended.restApi.hafahApi.blocks.blockNum.header({
blockNum: 12345678
});
console.info(result);
{
previous: '00bc614d58b1745f3347e4f55f35fe68c82ad0d1',
timestamp: '2017-05-29T06:28:42',
witness: 'good-karma',
transaction_merkle_root: '3843fd6daebf3742ecc84fe5926df037131a66a6',
extensions: []
}
import asyncio
from beekeepy.handle.remote import AbstractAsyncApi
from wax import create_hive_chain
# Define a new API client that extends AbstractAsyncApi
class ReputationApi(AbstractAsyncApi):
def base_path(self) -> str:
return "/reputation-api"
# Define a REST endpoint for fetching account reputation
@AbstractAsyncApi.endpoint_rest().get("/accounts/{account-name}/reputation")
async def get_account_reputations(self, account_name: str) -> int: ...
# Create a class that contains all custom APIs
class NewApiCall:
def __init__(self):
# Add ReputationApi as part of the extended chain interface
self.reputation_api = ReputationApi
# Example: Extending the chain interface with a custom REST API.
async def main():
chain = create_hive_chain()
# Extend the chain with the custom API
extended = chain.extend_rest(NewApiCall)
# Call the custom API endpoint through the extended interface
print(f"Output: {await extended.api.reputation_api.get_account_reputations('gtg')}")
asyncio.run(main())
#
Automatically extending API
Thanks to the OpenAPI specifications, we can automatically generate API definitions for both JSON-RPC and REST API. This way you don't have to manually define each method, its parameters, and return types. If you have your own API with OpenAPI spec, you can also automatically generate the types and use them with Wax.
When dealing with TypeScript and JavaScript, you can use the following package to automatically generate types from OpenAPI spec:
TBA
#
Use JSON-RPC API packages
For basic wax usage, the default API methods, shipped with the package are usually sufficient. However, if you want to use more advanced API methods, you can extend the default API with additional methods. Defining a whole set of methods can be tedious, so Wax provides a way to automatically extend the API with additional methods, using the extend method and external packages with automatically generated spec from OpenAPI.
Example usage:
import JsonRPC from "@hiveio/wax-api-jsonrpc";
const extendedChain = chain.extend(JsonRPC);
// You can now call extendedChain.api[apiType][apiMethod](dataToSend)
TBA
#
Use REST API packages
As you can see it is a little complicated and REST API can potentially change frequently as it is not consensus-based, so we created multiple packages, automatically generating API definitions from OpenAPI definitions for each endpoint. You would only need to install the package and use the generated types:
Example usage:
import HAfAH from "@hiveio/wax-api-hafah";
const extendedChain = chain.extendRest(HAfAH);
// You can now call extendedChain.restApi...<methodNames>()
- HAfAH:
wax-api-hafah - Block Explorer:
wax-api-hafbe - Reputation Tracker:
wax-api-reputation-tracker - Balance Tracker:
wax-api-balance-tracker
import asyncio
from wax import create_hive_chain
from reputation_api.reputation_api_client import ReputationApi
# Class containing additional APIs
class ExtendedApi:
def __init__(self):
# Extend the chain interface with the predefined ReputationApi
self.reputation_api = ReputationApi
async def main():
chain = create_hive_chain()
# Create an extended chain with the new API
extended_chain = chain.extends(new_api=ExtendedApi)
# Calling methods from ReputationApi through the extended interface
print(f"Reputation: {await extended_chain.api.reputation_api.accounts_reputation('gtg')}")
print(f"Version: {await extended_chain.api.reputation_api.version()}")
print(f"Last synced block: {await extended_chain.api.reputation_api.last_synced_block()}")
asyncio.run(main())
Reputation: 76
Version: 8393e19323be1002a16df9826423c1fb442b4a12
Last synced block: 99305697