Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
webmaster128 committed Nov 4, 2018
1 parent 1649792 commit 8c58a22
Show file tree
Hide file tree
Showing 9 changed files with 878 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .editorconfig
@@ -0,0 +1,11 @@
root = true

[*]
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8

[*.{js,ts}]
indent_style = space
indent_size = 2
2 changes: 2 additions & 0 deletions .gitignore
@@ -0,0 +1,2 @@
node_modules/
build/
4 changes: 4 additions & 0 deletions .prettierrc
@@ -0,0 +1,4 @@
{
printWidth: 100,
trailingComma: "all"
}
6 changes: 6 additions & 0 deletions README.md
@@ -0,0 +1,6 @@
## License & History

MIT.

Classes created until October 2018 are taken from the MIT-licensed
lisk-argus by Hendrik Hofstadt & Lisk Builders.
29 changes: 29 additions & 0 deletions package.json
@@ -0,0 +1,29 @@
{
"name": "libargus",
"version": "0.1.0",
"description": "A typesafe MIT licensed library to interact with the Lisk blockchain",
"main": "build/index.js",
"repository": "https://github.com/prolina-foundation/libargus",
"author": "Simon Warta & Lisk Builders",
"license": "MIT",
"scripts": {
"format": "prettier --write './src/**/*.ts'",
"prebuild": "yarn format",
"build": "tsc",
"test": "mocha \"build/**/*.spec.js\""
},
"dependencies": {
"request": "^2.88.0",
"request-promise-native": "^1.0.5"
},
"devDependencies": {
"@types/chai": "^4.1.7",
"@types/mocha": "^5.2.5",
"@types/request": "^2.48.0",
"@types/request-promise-native": "^1.0.15",
"chai": "^4.2.0",
"mocha": "^5.2.0",
"prettier": "^1.14.3",
"typescript": "^3.1.3"
}
}
78 changes: 78 additions & 0 deletions src/HttpApi.spec.ts
@@ -0,0 +1,78 @@
import { expect } from "chai";

import { HttpApi } from "./HttpApi";

const nodeHostname = process.env.LISK_NODE_HOSTNAME || "testnet.lisk.io";
const nodePort = Number.parseInt(process.env.LISK_NODE_PORT || "7000", 10);
const nodeSecure = !!process.env.LISK_NODE_SECURE;

describe("HttpApi", () => {
let api: HttpApi;

beforeEach(() => {
api = new HttpApi(nodeHostname, nodePort, nodeSecure);
});

it("can get node status", async () => {
const status = await api.getNodeStatus();
expect(status.data.broadhash).to.match(/^[0-9a-f]{64}$/);
expect(status.data.consensus).to.be.gt(0);
expect(status.data.consensus).to.be.lte(100);
expect(status.data.height).to.be.gt(1);
expect(status.data.height).to.be.lt(10_000_000);
expect(status.data.networkHeight).to.be.gt(1);
expect(status.data.networkHeight).to.be.lt(10_000_000);
expect(status.data.loaded).to.be.true;
expect(status.data.syncing).to.be.false;
expect(status.data.transactions).to.be.not.undefined;
});

it("can get blocks", async () => {
const response = await api.getBlocks();
expect(response.data.length).to.equal(100);
for (const block of response.data) {
expect(block.id).to.match(/^[0-9]+$/);
expect(block.height).to.be.within(1, 10_000_000);
expect(block.generatorPublicKey).to.match(/^[0-9a-f]{64}$/);
}
});

it("can get forgers", async () => {
const response = await api.getForgers();
expect(response.data.length).to.equal(100);
for (const forger of response.data) {
expect(forger.username).to.match(/^[a-zA-Z0-9_\.]+$/);
expect(forger.publicKey).to.match(/^[0-9a-f]{64}$/);
}
});

it("can get delegates", async () => {
const response = await api.getDelegates();
expect(response.data.length).to.equal(101);
for (const delegate of response.data) {
expect(delegate.rank).to.be.within(1, 101);
expect(delegate.username).to.match(/^[a-zA-Z0-9_\.]+$/);
expect(delegate.producedBlocks).to.gte(0);
expect(delegate.missedBlocks).to.gte(0);
expect(delegate.productivity).to.be.within(0, 100);
}
});

it("can get last block by delegate", async () => {
const response = await api.getDelegates();
const firstDelegate = response.data[0];

const lastBlock = await api.getLastBlockByDelegate(firstDelegate.account.publicKey);
expect(lastBlock.id).to.match(/^[0-9]+$/);
expect(lastBlock.height).to.be.within(1, 10_000_000);
expect(lastBlock.generatorPublicKey).to.eql(firstDelegate.account.publicKey);
});

it("can get block by height", async () => {
const block = await api.getBlockByHeight(1);

expect(block.id).to.match(/^[0-9]+$/);
expect(block.height).to.be.within(1, 10_000_000);
expect(block.generatorPublicKey).to.match(/^[0-9a-f]{64}$/);
});
});
136 changes: 136 additions & 0 deletions src/HttpApi.ts
@@ -0,0 +1,136 @@
import * as request from "request-promise-native";

// Lisk HTTP API
// https://app.swaggerhub.com/apis/LiskHQ/Lisk
export class HttpApi {
constructor(
protected readonly hostname: string,
protected readonly port: number,
protected readonly secure: boolean = false,
) {}

public getNodeStatus(): Promise<ResponseObject<NodeStatusExtended>> {
const options = { json: true };
return request(`${this.baseUrl()}/node/status`, options).promise();
}

public getBlocks(): Promise<ResponseList<Block>> {
const options = { json: true };
return request(`${this.baseUrl()}/blocks?limit=100`, options).promise();
}

public getForgers(): Promise<ForgerResponse> {
const options = { json: true };
return request(`${this.baseUrl()}/delegates/forgers?limit=100`, options).promise();
}

public getDelegates(): Promise<ResponseList<DelegateDetails>> {
const options = { json: true };
return request(`${this.baseUrl()}/delegates?limit=101&sort=rank:asc`, options).promise();
}

public getLastBlockByDelegate(generatorKey: string): Promise<Block> {
const options = { json: true };
return request(`${this.baseUrl()}/blocks?limit=1&generatorPublicKey=${generatorKey}`, options)
.promise()
.then(data => data.data[0]);
}

public getBlockByHeight(height: number): Promise<Block> {
const options = { json: true };
return request(`${this.baseUrl()}/blocks?limit=1&height=${height}`, options)
.promise()
.then(data => data.data[0]);
}

// method is proteced to allow adding endpoints by subclassing
protected baseUrl(): string {
const protocol = this.secure ? "https" : "http";
return `${protocol}://${this.hostname}:${this.port}/api`;
}
}

export interface Block {
readonly id: string;
readonly version: number;
readonly timestamp: number;
readonly height: number;
readonly numberOfTransactions: number;
readonly totalAmount: string;
readonly totalFee: string;
readonly reward: string;
readonly payloadLength: number;
readonly payloadHash: string;
readonly generatorPublicKey: string;
readonly blockSignature: string;
readonly confirmations: number;
readonly totalForged: string;
readonly generatorAddress: string;
readonly previousBlockId: string;
}

export interface TransactionsStats {
readonly confirmed: number;
readonly unconfirmed: number;
readonly unprocessed: number;
readonly unsigned: number;
readonly total: number;
}

export interface NodeStatusExtended {
readonly broadhash: string;
readonly consensus: number;
readonly height: number;
readonly loaded: boolean;
readonly networkHeight: number;
readonly syncing: boolean;
readonly transactions: TransactionsStats;
}

export interface ForgerMeta {
readonly lastBlock: number;
readonly lastBlockSlot: number;
readonly currentSlot: number;
readonly limit: number;
readonly offset: number;
}

export interface ForgerDetail {
readonly publicKey: string;
readonly username: string;
readonly address: string;
readonly nextSlot: number;
}

export interface ForgerResponse {
readonly meta: ForgerMeta;
readonly data: ForgerDetail[];
}

export interface Account {
readonly address: string;
readonly publicKey: string;
readonly secondPublicKey: string;
}

export type DelegateDetails = {
readonly rewards: string;
readonly vote: string;
readonly producedBlocks: number;
readonly missedBlocks: number;
readonly username: string;
readonly rank: number;
readonly approval: number;
readonly productivity: number;
readonly account: Account;
};

export interface ResponseObject<T> {
readonly meta: any;
readonly data: T;
}

export interface ResponseList<T> {
readonly meta: any;
readonly data: T[];
}
9 changes: 9 additions & 0 deletions tsconfig.json
@@ -0,0 +1,9 @@
{
"compilerOptions": {
"strict": true,
"target": "es2015",
"module": "commonjs",
"sourceMap": true,
"outDir": "build"
}
}

0 comments on commit 8c58a22

Please sign in to comment.