[feat] http && net && rpc && baseProvider

@types
neeboo 6 years ago
parent e777beb79b
commit b823f1a03c
  1. 67
      packages/harmony-account/src/wallet.ts
  2. 5
      packages/harmony-network/package.json
  3. 85
      packages/harmony-network/src/baseProvider.ts
  4. 34
      packages/harmony-network/src/defaultFetcher.ts
  5. 161
      packages/harmony-network/src/http.ts
  6. 6
      packages/harmony-network/src/index.ts
  7. 44
      packages/harmony-network/src/net.ts
  8. 42
      packages/harmony-network/src/rpc.ts
  9. 55
      packages/harmony-network/src/types.ts

@ -3,13 +3,33 @@ import { isPrivateKey } from '@harmony/utils';
import { Account } from './account'; import { Account } from './account';
class Wallet { class Wallet {
/**
* @memberof Wallet
*
*/
private accountMap: Map<string, Account> = new Map(); private accountMap: Map<string, Account> = new Map();
/**
* @memberof Wallet
* @return {string[]} accounts addresses
*/
get accounts(): string[] { get accounts(): string[] {
return [...this.accountMap.keys()]; return [...this.accountMap.keys()];
} }
/**
* @function generateMnemonic
* @memberof Wallet
* @return {string} Mnemonics
*/
generateMnemonic(): string { generateMnemonic(): string {
return bip39.generateMnemonic(); return bip39.generateMnemonic();
} }
/**
* @function addByMnemonic
* @memberof Wallet
* @description add account using Mnemonic phrases
* @param {string} phrase - Mnemonic phrase
* @param {index} index - index to hdKey root
*/
addByMnemonic(phrase: string, index: number = 0) { addByMnemonic(phrase: string, index: number = 0) {
if (!this.isValidMnemonic(phrase)) { if (!this.isValidMnemonic(phrase)) {
throw new Error(`Invalid mnemonic phrase: ${phrase}`); throw new Error(`Invalid mnemonic phrase: ${phrase}`);
@ -20,6 +40,13 @@ class Wallet {
const privateKey = childKey.privateKey.toString('hex'); const privateKey = childKey.privateKey.toString('hex');
return this.addByPrivateKey(privateKey); return this.addByPrivateKey(privateKey);
} }
/**
* @function addByPrivateKey
* @memberof Wallet
* @description add an account using privateKey
* @param {string} privateKey - privateKey to add
* @return {Account} return added Account
*/
addByPrivateKey(privateKey: string): Account { addByPrivateKey(privateKey: string): Account {
try { try {
const newAcc = Account.add(privateKey); const newAcc = Account.add(privateKey);
@ -33,6 +60,16 @@ class Wallet {
throw error; throw error;
} }
} }
/**
* @function encryptAccount
* @memberof Wallet
* @description to encrypt an account that lives in the wallet,
* if encrypted, returns original one, if not found, throw error
* @param {string} address - address in accounts
* @param {string} password - string that used to encrypt
* @param {EncryptOptions} options - encryption options
* @return {Promise<Account>}
*/
async encryptAccount( async encryptAccount(
address: string, address: string,
password: string, password: string,
@ -60,7 +97,15 @@ class Wallet {
throw error; throw error;
} }
} }
/**
* @function decryptAccount
* @memberof Wallet
* @description to decrypt an account that lives in the wallet,if not encrypted, return original,
* if not found, throw error
* @param {string} address - address in accounts
* @param {string} password - string that used to encrypt
* @return {Promise<Account>}
*/
async decryptAccount(address: string, password: string): Promise<Account> { async decryptAccount(address: string, password: string): Promise<Account> {
try { try {
const foundAcc = this.getAccount(address); const foundAcc = this.getAccount(address);
@ -85,14 +130,34 @@ class Wallet {
} }
} }
/**
* @function getAccount
* @memberof Wallet
* @description get Account instance using address as param
* @param {string} address - address hex
* @return {Account} Account instance which lives in Wallet
*/
getAccount(address: string): Account | undefined { getAccount(address: string): Account | undefined {
return this.accountMap.get(address); return this.accountMap.get(address);
} }
/**
* @function removeAccount
* @memberof Wallet
* @description remove Account using address as param
* @param {string} address: - address hex
*/
removeAccount(address: string): void { removeAccount(address: string): void {
this.accountMap.delete(address); this.accountMap.delete(address);
} }
/**
* @function isValidMnemonic
* @memberof Wallet
* @description check if Mnemonic is valid
* @param {string} phrase - Mnemonic phrase
* @return {boolean}
*/
private isValidMnemonic(phrase: string): boolean { private isValidMnemonic(phrase: string): boolean {
if (phrase.trim().split(/\s+/g).length < 12) { if (phrase.trim().split(/\s+/g).length < 12) {
return false; return false;

@ -12,5 +12,8 @@
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"author": "neeboo@firestack.one", "author": "neeboo@firestack.one",
"license": "ISC" "license": "ISC",
"dependencies": {
"cross-fetch": "^3.0.2"
}
} }

@ -0,0 +1,85 @@
import { ReqMiddleware, ResMiddleware, MiddlewareType } from './types';
import { RPCMethod } from './rpc';
class BaseProvider {
middlewares = {
request: {
use: (fn: ReqMiddleware, match: string | RPCMethod | RegExp = '*') => {
this.pushMiddleware(fn, MiddlewareType.REQ, match);
},
},
response: {
use: (fn: ResMiddleware, match: string | RPCMethod | RegExp = '*') => {
this.pushMiddleware(fn, MiddlewareType.RES, match);
},
},
};
protected url: string;
protected reqMiddleware: ReqMiddleware = new Map().set('*', []);
protected resMiddleware: ResMiddleware = new Map().set('*', []);
constructor(
url: string,
reqMiddleware: ReqMiddleware = new Map(),
resMiddleware: ResMiddleware = new Map(),
) {
this.reqMiddleware = reqMiddleware;
this.resMiddleware = resMiddleware;
this.url = url;
}
protected pushMiddleware(
fn: any,
type: MiddlewareType,
match: string | RPCMethod | RegExp,
) {
if (type !== MiddlewareType.REQ && type !== MiddlewareType.RES) {
throw new Error('Please specify the type of middleware being added');
}
if (type === MiddlewareType.REQ) {
const current = this.reqMiddleware.get(match) || [];
this.reqMiddleware.set(match, [...current, <ReqMiddleware>fn]);
} else {
const current = this.resMiddleware.get(match) || [];
this.resMiddleware.set(match, [...current, <ResMiddleware>fn]);
}
}
protected getMiddleware(
method: RPCMethod,
): [ReqMiddleware[], ResMiddleware[]] {
const requests: ReqMiddleware[] = [];
const responses: ResMiddleware[] = [];
for (const [key, transformers] of this.reqMiddleware.entries()) {
if (typeof key === 'string' && key !== '*' && key === method) {
requests.push(...transformers);
}
if (key instanceof RegExp && key.test(method)) {
requests.push(...transformers);
}
if (key === '*') {
requests.push(...transformers);
}
}
for (const [key, transformers] of this.resMiddleware.entries()) {
if (typeof key === 'string' && key !== '*' && key === method) {
responses.push(...transformers);
}
if (key instanceof RegExp && key.test(method)) {
responses.push(...transformers);
}
if (key === '*') {
responses.push(...transformers);
}
}
return [requests, responses];
}
}
export { BaseProvider };

@ -0,0 +1,34 @@
import fetch from 'cross-fetch';
import { RPCRequest, RPCResponseBody, RPCError, RPCResult } from './types';
export const fetchRPC = {
requestHandler: (request: RPCRequest<any[]>, headers: any) =>
fetch(request.url, {
method:
request.options && request.options.method
? request.options.method
: 'POST',
cache: 'no-cache',
mode: 'cors',
redirect: 'follow',
referrer: 'no-referrer',
body: JSON.stringify(request.payload),
headers: {
...headers,
...(request.options && request.options.headers
? request.options.headers
: {}),
},
}),
responseHandler: (
response: Response,
request: RPCRequest<any>,
handler: any,
) =>
response
.json()
.then((body: RPCResponseBody<RPCResult, RPCError>) => {
return { ...body, req: request };
})
.then(handler),
};

@ -0,0 +1,161 @@
import { BaseProvider } from './baseProvider';
import { fetchRPC } from './defaultFetcher';
import {
composeMiddleware,
performRPC,
DEFAULT_TIMEOUT,
DEFAULT_HEADERS,
} from './net';
import { RPCRequestPayload } from './types';
const defaultOptions = {
method: 'POST',
timeout: DEFAULT_TIMEOUT,
headers: DEFAULT_HEADERS,
user: null,
password: null,
};
class HttpProvider extends BaseProvider {
url: string;
fetcher: any;
options: any;
constructor(url: string, options: any, fetcher: any) {
super(url);
this.url = url || 'http://localhost:4201';
this.fetcher = fetcher || fetchRPC;
if (options) {
this.options = {
method: options.method || defaultOptions.method,
timeout: options.timeout || defaultOptions.timeout,
user: options.user || defaultOptions.user,
password: options.password || defaultOptions.password,
headers: options.headers || defaultOptions.headers,
};
} else {
this.options = defaultOptions;
}
}
/**
* @function send
* @memberof HttpProvider.prototype
* @param {Object} payload - payload object
* @param {Function} callback - callback function
* @return {any} - RPC Response
*/
send(payload: RPCRequestPayload<object>, callback: any): Promise<any> {
return this.requestFunc({ payload, callback });
}
/**
* @function sendServer
* @memberof HttpProvider.prototype
* @param {String} endpoint - endpoint to server
* @param {Object} payload - payload object
* @param {Function} callback - callback function
* @return {Function} - RPC Response
*/
sendServer(
endpoint: string,
payload: RPCRequestPayload<object>,
callback: any,
): Promise<any> {
return this.requestFunc({ endpoint, payload, callback });
}
requestFunc({
endpoint,
payload,
callback,
}: {
endpoint?: string;
payload: RPCRequestPayload<object>;
callback?: any;
}): Promise<any> {
const [tReq, tRes] = this.getMiddleware(payload.method);
const reqMiddleware = composeMiddleware(
...tReq,
(obj: object) => this.optionsHandler(obj),
(obj: object) => this.endpointHandler(obj, endpoint),
this.payloadHandler,
);
const resMiddleware = composeMiddleware(
(data: object) => this.callbackHandler(data, callback),
...tRes,
);
const req = reqMiddleware(payload);
return performRPC(req, resMiddleware, this.fetcher);
}
/**
* @function payloadHandler
* @memberof HttpProvider.prototype
* @param {Object} payload - payload object
* @return {Object} - to payload object
*/
payloadHandler(payload: RPCRequestPayload<object>): object {
return { payload };
}
/**
* @function endpointHandler
* @memberof HttpProvider.prototype
* @param {Object} obj - payload object
* @param {String} endpoint - add the endpoint to payload object
* @return {Object} - assign a new object
*/
endpointHandler(obj: object, endpoint?: string): object {
return {
...obj,
url:
endpoint !== null && endpoint !== undefined
? `${this.url}${endpoint}`
: this.url,
};
}
/**
* @function optionsHandler
* @memberof HttpProvider.prototype
* @param {Object} obj - options object
* @return {Object} - assign a new option object
*/
optionsHandler(obj: object) {
if (this.options.user && this.options.password) {
const AUTH_TOKEN = `Basic ${Buffer.from(
`${this.options.user}:${this.options.password}`,
).toString('base64')}`;
this.options.headers.Authorization = AUTH_TOKEN;
}
return { ...obj, options: this.options };
}
/**
* @function callbackHandler
* @memberof HttpProvider.prototype
* @param {Object} data - from server
* @param {Function} cb - callback function
* @return {Object|Function} - return object or callback function
*/
callbackHandler(data: any, cb: any): any {
if (cb) {
cb(null, data);
}
return data;
}
subscribe() {
throw new Error('HTTPProvider does not support subscriptions.');
}
unsubscribe() {
throw new Error('HTTPProvider does not support subscriptions.');
}
}
export { HttpProvider };

@ -0,0 +1,6 @@
export * from './baseProvider';
export * from './defaultFetcher';
export * from './http';
export * from './net';
export * from './rpc';
export * from './types';

@ -0,0 +1,44 @@
export const DEFAULT_TIMEOUT: number = 120000;
export const DEFAULT_HEADERS: object = { 'Content-Type': 'application/json' };
function _fetch(fetchPromise: Promise<any>, timeout: number) {
let abortFn: () => void;
const abortPromise = new Promise((resolve, reject) => {
abortFn = () => reject(new Error(`request Timeout in ${timeout} ms`));
});
const abortablePromise = Promise.race([fetchPromise, abortPromise]);
setTimeout(() => {
abortFn();
}, timeout);
return abortablePromise;
}
export const performRPC = async (request: any, handler: any, fetcher: any) => {
try {
const response = await _fetch(
fetcher.requestHandler(request, DEFAULT_HEADERS),
request.options && request.options.timeout
? request.options.timeout
: DEFAULT_TIMEOUT,
);
return fetcher.responseHandler(response, request, handler);
} catch (err) {
throw err;
}
};
export function composeMiddleware(...fns: any[]): any {
if (fns.length === 0) {
return (arg: any) => arg;
}
if (fns.length === 1) {
return fns[0];
}
return fns.reduce((a, b) => (arg: any) => a(b(arg)));
}

@ -0,0 +1,42 @@
export const enum RPCMethod {
// account related
FetchBalance = 'FetchBalance',
// block info related
GetLatestBlock = 'GetLatestBlock',
GetBlock = 'GetBlock',
GetEstimtedGas = 'GetEstimatedGas',
GetLatestTransactions = 'GetLatestTransactions',
GetLatestDSBlocks = 'GetLatestDSBlocks',
// transaction related
SendTransaction = 'SendTransaction',
SendTransactionToShard = 'SendTransactionToShard',
SendTransactionToBlock = 'SendTransactionToBlock',
GetTransaction = 'GetTransaction',
}
export const enum RPCErrorCode {
// Standard JSON-RPC 2.0 errors
// RPC_INVALID_REQUEST is internally mapped to HTTP_BAD_REQUEST (400).
// It should not be used for application-layer errors.
RPC_INVALID_REQUEST = -32600,
// RPC_METHOD_NOT_FOUND is internally mapped to HTTP_NOT_FOUND (404).
// It should not be used for application-layer errors.
RPC_METHOD_NOT_FOUND = -32601,
RPC_INVALID_PARAMS = -32602,
// RPC_INTERNAL_ERROR should only be used for genuine errors in bitcoind
// (for example datadir corruption).
RPC_INTERNAL_ERROR = -32603,
RPC_PARSE_ERROR = -32700,
// General application defined errors
RPC_MISC_ERROR = -1, // std::exception thrown in command handling
RPC_TYPE_ERROR = -3, // Unexpected type was passed as parameter
RPC_INVALID_ADDRESS_OR_KEY = -5, // Invalid address or key
RPC_INVALID_PARAMETER = -8, // Invalid, missing or duplicate parameter
RPC_DATABASE_ERROR = -20, // Database error
RPC_DESERIALIZATION_ERROR = -22, // Error parsing or validating structure in raw format
RPC_VERIFY_ERROR = -25, // General error during transaction or block submission
RPC_VERIFY_REJECTED = -26, // Transaction or block was rejected by network rules
RPC_IN_WARMUP = -28, // Client still warming up
RPC_METHOD_DEPRECATED = -32, // RPC method is deprecated
}

@ -0,0 +1,55 @@
import { RPCMethod, RPCErrorCode } from './rpc';
export type ReqMiddleware = Map<string | RPCMethod | RegExp, any[]>;
export type ResMiddleware = Map<string | RPCMethod | RegExp, any[]>;
export const enum MiddlewareType {
REQ,
RES,
}
export interface Middleware {
request: object;
response: object;
}
export interface RPCRequestPayload<T> {
id: number;
jsonrpc: string;
method: RPCMethod;
params: T;
}
export interface RPCRequestOptions {
headers: [];
method: string;
}
export interface RPCRequest<T> {
url: string;
payload: RPCRequestPayload<T>;
options: RPCRequestOptions;
}
export interface RPCResponseBase {
jsonrpc: string;
id: string;
}
export interface RPCResponseBody<R, E> extends RPCResponseBase {
result: R;
error: E;
}
export interface RPCError {
code: RPCErrorCode;
message: string;
data: any;
}
export interface RPCResult {
resultString: string;
resultMap: Map<string, any>;
resultList: any[];
raw: any;
}
Loading…
Cancel
Save