Plugin SDK
Quick overview
To add indexer support for (part of) a blockchain you’d best use the Plugin SDK.
The steps involved in adding support for a feature are as follows.
- You fork the plugin sdk repo
Now for each feature.
- You write code that translates parameters to a URL
- You translate the response from the server to a format understood by the indexer
- You write any extra tests apart from the existing basic tests available
Finally
- You run a build script and commit the code to github.com
- You tell us about your plugin by opening an issue on our issue tracker
- We review your plugin and integrate it when it meets our quality standards
These features are currently supported in our indexer servers.
- network status
- network fee
- token discovery
- balance lookup
- event lookup
- utxo lookup
- transaction status
- forward and reverse alias lookup
- public key lookup
- broadcast transaction
Server plugins can choose to support one, some or all of these features as several plugins can be combined to construct a complete working blockchain indexer.
Step by step guide
Follow the steps below to get started with writing your first plugin.
Fork the SDK
Detailed steps on how to fork a repo on GitHub are available here.
- On Github, navigate to heatcrypto/heat-server-sdk
- In the top-right corner of the page, click Fork
Make sure the name of your fork contains the name of the blockchain and optionally your name or organization name.
In case you are creating more than one plugin you will notice that GitHub does not allow the creation of multiple forks of the same repository under one single account. From the website. To get around this restriction follow these instructions.
Rename your fork
To rename your forked repository from heat-server-sdk
to your desired name please follow the instructions below.
- On GitHub, navigate to the main page of the repository.
- Under your repository name, click Settings.
- Under the Repository Name heading, type the new name of your repository.
- Click Rename.
Best practice is to name your repo
heat-server-BLOCKCHAIN
where BLOCKCHAIN is the chain you are creating the plugin for. If that name is taken or if you wish to include your name in the repo please follow this patternheat-server-BLOCKCHAIN-YOURNAME
(for example heat-server-ripple-mike).
Naming plugins this way ensures maximum reusability and discoverability.
Code your feature
Each feature is implemented in what we call a module, all modules live in the src/modules
directory.
Modules all expose one function, each specific module must adhere to a specific signature which is shared between your plugin and our indexer server.
As an example lets look at what the transaction status module looks like before we have done any coding.
import {
TransactionStatusParam, TransactionStatusResult,
tryParse, CallContext, ModuleResponse } from 'heat-server-common'
export async function transactionStatus(
context: CallContext,
param: TransactionStatusParam): Promise<ModuleResponse<TransactionStatusResult>> {
try {
const { req, protocol, host, logger } = context
const { blockchain, assetType, addrXpub, transactionId } = param
const url = `${protocol}://${host}/api/GET-TRANSACTION-STATUS?transactionId=${transactionId}`;
const json = await req.get(url);
const data = tryParse(json, logger);
const confirmations: number = 0;
const isAccepted: boolean = false;
return {
value: {
confirmations,
isAccepted,
},
};
} catch (e) {
return {
error: e.message,
};
}
}
heat-server-common
All plugins make use of heatcrypto/heat-server-common as it allows sharing of code between all plugins and our indexing server.
CallContext
Each module receives a CallContext
as its first argument. The CallContext is provided by the indexing server.
When running unit tests you have to provide the CallContext yourself. This however is basic as all you have to do is call the
createContext(label: string)
function in each of your tests.
/**
* Each module is provided with a CallContext each time it is invoked
*/
interface CallContext {
/**
* Network protocol (http or https)
*/
protocol: string;
/**
* Host (plus optional port) of api server
*/
host: string;
/**
* Main logger, created as prefixlogger with prefix that
* identifies the plugin
*/
logger: LoggerService;
/**
* Middleware that deals with address conversions and
* possible other future topics
*/
middleWare?: ExplorerMiddleware;
/**
* A configured instance of MonitoredRequest is provided,
* this does GET and POST requests
*/
req: MonitoredRequest;
}
Call Parameters
Modules are not much good if you cant pass parameters to them. That’s where the second parameter to each module comes into play. Each module has its own interface for their parameter as well as for their result. All these interfaces can be found listed here or look in the right hand side menu on this page at each feature and look for Plugin Input
.
Typically many more parameters are provided to your module than you actually need, the reason for this stems from the fact that these parameters (like which
blockchain
orassetType
) are used much higher in the call hierarchy in the indexing servers.
API url
Its your task to construct the GET url to the blockchain api server.
const url = `${protocol}://${host}/api/GET-TRANSACTION-STATUS?transactionId=${transactionId}`;
Its crucial to use the
protocol
andhost
provided by the CallContext. Otherwise your plugin cannot be used in parallel when more than one blockchain API server is available.
Download API response
Once we have the URL for our request we can download this data with either GET or POST and parse the response (if its json).
The
tryParse
method provided byheat-server-common
is the preferred and advised way to parse JSON. Using this method ensures proper error handling and logging.
The
req
object (MonitoredRequest) provided by CallContext should be used for all your GET and POST requests, this allows us to tune the specifics of the underlying HTTP client.
const json = await req.get(url);
const data = tryParse(json, logger);
To do POST requests are used as follows.
const json = await req.post(url, { form: { transactionBytes: transactionHex } }, [200]);
const data = tryParse(json, logger);
Translate response
Finally, you translate the response from the blockchain API server to the expected result by the indexing server. The details of this result can be found in the Result interface as can be seen in the example above.
Test your code
To test your code we use unit tests which have sensible defaults and can run be run as is.
Let’s look at the unit test for the transaction status feature we just coded. Below is the code as it is provided to you by the SDK you forked in the previous steps.
import * as chai from 'chai';
const { isObject, isNumber, isBoolean } = chai.assert
import 'mocha';
import { createContext } from './test_config'
import { transactionStatus } from '../src/modules/transaction_status';
import { Blockchains, AssetTypes } from 'heat-server-common';
describe('Transaction Status', () => {
it('should work', async () => {
const blockchain: Blockchains = Blockchains.ETHEREUM
const assetType: AssetTypes = AssetTypes.NATIVE
const addrXpub: string = '0x12345'
const transactionId: string = '0x67890'
let resp = await transactionStatus(createContext('Transaction'), {
blockchain, assetType, addrXpub, transactionId
})
isObject(resp)
let result = resp.value
isObject(result)
isNumber(result.confirmations)
isBoolean(result.isAccepted)
});
});
As can be seen, we have set up the mocha
test framework and are using chai
assertion library.
While a basic test is provided you should spend time and code your own.
Configure your test
Before you can run your tests you need to configure them by opening test/test_config.ts
and set your protocol
and host
values.
export const testConfig = {
protocol: 'http',
host: 'localhost:3000'
}
Run your tests
To run your tests on the command line run npm run test
.
Deploying your plugin
When you have completed writing your module code and unit tests its time to prepare your plugin for deployment.
To do so open src/explorer.ts
and at a minimum set the ID
value to a globally unique identifier that identifies your plugin.
We strongly advise you using an id that is made up of the last part of your repo name. The part after
heat-server-
specifically.
So if your plugin is namedheat-server-bitcoin-mike
please use it as an IDbitcoin-mike
Optionally you can comment out or add any modules you did or did not implement in the modules
variable.
It’s good practice to remove any module you did not implement as it helps the indexing server identify when it’s trying to call a feature that is not implemented in your plugin.
Building your code
Finally, we build the plugin by running a script on the command line. Open a command prompt at your plugin root and run npm run prepublish
, this will transpile your typescript into javascript and creates the dist
folder.
Commit build to github.com
For the indexer server to directly load your plugin code from Github you should add the compiled code in the dist
folder to your repo and push these changes to Github.
To do so open .gitignore
and remove the line that says /dist
.
Now on the commandline add, commit and push the code in /dist
.
$ git add dist
$ git commit -m "Add dist to git"
$ git push origin master
Enable your plugin in Heat Wallet
Now that you have created your plugin we will review it before making it available on our indexer platform.
Please create a new issue at https://github.com/heatcrypto/heat-server-sdk/issues and cleary state you request a review and include your plugin repo on github.