# 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.

Wax extended using HAfAH REST API package - Full IntelliSense support
Wax extended using HAfAH REST API package - Full IntelliSense support

# 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:

View WAX Spec Generator package on npmjs 🡭
https://www.npmjs.com/package/@hiveio/wax-spec-generator

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.

View JSON-RPC API package on npmjs 🡭
https://npmjs.com/package/@hiveio/wax-api-jsonrpc

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:

View HAfAH API package on npmjs 🡭
https://npmjs.com/package/@hiveio/wax-api-hafah
View Block Explorer API package on npmjs 🡭
https://npmjs.com/package/@hiveio/wax-api-hafbe
View Reputation Tracker API package on npmjs 🡭
https://npmjs.com/package/@hiveio/wax-api-reputation-tracker
View Balance Tracker API package on npmjs 🡭
https://npmjs.com/package/@hiveio/wax-api-balance-tracker

Example usage:

import HAfAH from "@hiveio/wax-api-hafah";

const extendedChain = chain.extendRest(HAfAH);
// You can now call extendedChain.restApi...<methodNames>()
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