UNPKG

rchain-toolkit

Version:

Node JS utils and methods to interact with rchain's rnode api and proto interfaces

596 lines (557 loc) 15.4 kB
import { DeployData, LightBlockInfo } from "./models"; import { getDeployOptions, publicKeyFromPrivateKey } from "./utils"; import * as https from 'https'; import * as http from 'http'; interface UrlValidated { options: { host: string; port: string; rejectUnauthorized?: boolean; cert?: string; ca?: string[]; }; protocol: "http" | "https"; lib: any; } export interface ValidateUrlOptions { url: string; rejectUnauthorized?: boolean; cert?: string; ca?: string[]; } const validateUrl = (options: ValidateUrlOptions): UrlValidated => { if (options.url.startsWith("http://")) { return { protocol: "http", lib: http, options: { host: options.url.substr(7).split(":")[0], port: options.url.substr(7).split(":")[1], }, }; } else if (options.url.startsWith("https://")) { return { protocol: "https", lib: https, options: { host: options.url.substr(8).split(":")[0], port: options.url.substr(8).split(":")[1], rejectUnauthorized: options.rejectUnauthorized, cert: options.cert, ca: options.ca, }, }; } else { throw new Error( "URL most be http://ip_or_domain:port or https://ip_or_domain:port" ); } }; // ============== // Deploy // ============== export interface DeployOptions { data: DeployData; deployer: string; signature: string; sigAlgorithm: "secp256k1"; } export interface DeployResponse { names: string[]; blockNumber: number; } export const deploy = async ( urlOrOptions: string | ValidateUrlOptions, options: DeployOptions, timeout: undefined | number = undefined ): Promise<string> => { let urlValidated: undefined | UrlValidated; if (typeof urlOrOptions === "string") { urlValidated = validateUrl({ url: urlOrOptions }); } else { urlValidated = validateUrl(urlOrOptions); } const uv: UrlValidated = urlValidated; let pd: undefined | string = undefined; if (typeof timeout === "number") { pd = await prepareDeploy(urlOrOptions, { deployer: options.deployer, timestamp: options.data.timestamp, nameQty: 1, }); } return new Promise((resolve, reject) => { const req = uv.lib.request( { headers: { "Content-Type": "application/json", }, method: "POST", path: "/api/deploy", host: uv.options.host, ...(uv.options.port ? { port: uv.options.port } : {}), ...(uv.options.cert ? { cert: uv.options.port } : {}), ...(uv.options.rejectUnauthorized ? { port: uv.options.rejectUnauthorized } : {}), ...(uv.options.ca ? { port: uv.options.ca } : {}), }, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; res.on("end", () => { if (!data.toString().startsWith('"Success!')) { throw new Error(data.toString()) } if (typeof timeout === "number") { let s = new Date().getTime(); let ongoning = false; const interval = setInterval(async () => { if (ongoning) { return; } ongoning = true; if (new Date().getTime() - timeout > s) { clearInterval(interval); throw new Error("TIMEOUT"); } const dan = await dataAtName(urlOrOptions, { name: { UnforgPrivate: { data: JSON.parse(pd as string).names[0] }, }, depth: 3, }); if ( dan && JSON.parse(dan) && JSON.parse(dan).exprs && JSON.parse(dan).exprs.length ) { resolve(dan); clearInterval(interval); } ongoning = false; }, 4000); } else { resolve(data); } }); }); } ); req.write(JSON.stringify(options)); req.end(); req.on("error", (e) => { reject(e); }); }); }; // ============== // Easy eploy // ============== export interface DeployResponse { names: string[]; blockNumber: number; } export const easyDeploy = async ( urlOrOptions: string | ValidateUrlOptions, term: string, privateKey: string, phloPrice: number | 'auto', phloLimit: number , timeout: undefined | number = undefined ): Promise<string> => { let urlValidated: undefined | UrlValidated; if (typeof urlOrOptions === "string") { urlValidated = validateUrl({ url: urlOrOptions }); } else { urlValidated = validateUrl(urlOrOptions); } const uv: UrlValidated = urlValidated; let phloPriceOk = 1; if (phloPrice === 'auto') { phloPriceOk = JSON.parse(await status(urlOrOptions)).minPhloPrice as number; } else { phloPriceOk = phloPrice; } const publicKey = publicKeyFromPrivateKey(privateKey); const vab = await validAfterBlockNumber(urlOrOptions); const d = new Date().valueOf(); const options = getDeployOptions( "secp256k1", d, term, privateKey, publicKey, phloPriceOk, phloLimit, vab ); let pd: undefined | string = undefined; if (typeof timeout === "number") { pd = await prepareDeploy(urlOrOptions, { deployer: publicKey, timestamp: d, nameQty: 1, }); } return new Promise((resolve, reject) => { const req = uv.lib.request( { headers: { "Content-Type": "application/json", }, method: "POST", path: "/api/deploy", host: uv.options.host, ...(uv.options.port ? { port: uv.options.port } : {}), ...(uv.options.cert ? { cert: uv.options.port } : {}), ...(uv.options.rejectUnauthorized ? { port: uv.options.rejectUnauthorized } : {}), ...(uv.options.ca ? { port: uv.options.ca } : {}), }, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; res.on("end", () => { if (!data.toString().startsWith('"Success!')) { throw new Error(data.toString()) } if (typeof timeout === "number") { let s = new Date().getTime(); let ongoning = false; const interval = setInterval(async () => { if (ongoning) { return; } ongoning = true; if (new Date().getTime() - timeout > s) { clearInterval(interval); throw new Error("TIMEOUT"); } const dan = await dataAtName(urlOrOptions, { name: { UnforgPrivate: { data: JSON.parse(pd as string).names[0] }, }, depth: 3, }); if ( dan && JSON.parse(dan) && JSON.parse(dan).exprs && JSON.parse(dan).exprs.length ) { resolve(dan); clearInterval(interval); } ongoning = false; }, 4000); } else { resolve(data); } }); }); } ); req.write(JSON.stringify(options)); req.end(); req.on("error", (e) => { reject(e); }); }); }; // ============== // Status // ============== export interface StatusResponse { address: string; version: { api: string; node: string; }; peers: number; nodes: number; minPhloPrice: string; } export const status = ( urlOrOptions: string | ValidateUrlOptions ): Promise<string> => { let urlValidated: undefined | UrlValidated; if (typeof urlOrOptions === "string") { urlValidated = validateUrl({ url: urlOrOptions }); } else { urlValidated = validateUrl(urlOrOptions); } const uv: UrlValidated = urlValidated; return new Promise((resolve, reject) => { const req = uv.lib.request( { headers: { "Content-Type": "application/json", }, method: "GET", path: "/api/status", host: uv.options.host, ...(uv.options.port ? { port: uv.options.port } : {}), ...(uv.options.cert ? { cert: uv.options.cert } : {}), ...(uv.options.rejectUnauthorized ? { rejectUnauthorized: uv.options.rejectUnauthorized } : {}), ...(uv.options.ca ? { ca: uv.options.ca } : {}), }, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; res.on("end", () => { resolve(data); }); }); } ); req.end(); req.on("error", (e) => { reject(e); }); }); }; // ============== // Valid after block number // ============== export const validAfterBlockNumber = async ( urlOrOptions: string | ValidateUrlOptions ): Promise<number> => { let validAfterBlockNumberResponse; validAfterBlockNumberResponse = JSON.parse( await blocks(urlOrOptions, { position: 1, }) )[0].blockNumber; return validAfterBlockNumberResponse; }; // ============== // Exploratory deploy // ============== export interface ExploreDeployOptions { term: string; } export interface ExploreDeployResponse { names: string[]; blockNumber: number; } export const exploreDeploy = ( urlOrOptions: string | ValidateUrlOptions, options: ExploreDeployOptions ): Promise<any> => { let urlValidated: undefined | UrlValidated; if (typeof urlOrOptions === "string") { urlValidated = validateUrl({ url: urlOrOptions }); } else { urlValidated = validateUrl(urlOrOptions); } const uv: UrlValidated = urlValidated; return new Promise((resolve, reject) => { const req = uv.lib.request( { headers: { "Content-Type": "application/json", }, method: "POST", path: "/api/explore-deploy", host: uv.options.host, ...(uv.options.port ? { port: uv.options.port } : {}), ...(uv.options.cert ? { cert: uv.options.cert } : {}), ...(uv.options.rejectUnauthorized ? { rejectUnauthorized: uv.options.rejectUnauthorized } : {}), ...(uv.options.ca ? { ca: uv.options.ca } : {}), }, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; res.on("end", () => { resolve(data); }); }); } ); req.write(options.term); req.end(); req.on("error", (e) => { reject(e); }); }); }; // ============== // Blocks by position // ============== export interface BlocksOptions { position: number; } export interface BlocksResponse { blocks: LightBlockInfo[]; } export const blocks = ( urlOrOptions: string | ValidateUrlOptions, options: BlocksOptions ): Promise<any> => { return new Promise((resolve, reject) => { let urlValidated: undefined | UrlValidated; if (typeof urlOrOptions === "string") { urlValidated = validateUrl({ url: urlOrOptions }); } else { urlValidated = validateUrl(urlOrOptions); } const uv: UrlValidated = urlValidated; const req = urlValidated.lib.request( { headers: { "Content-Type": "application/json", }, method: "GET", path: "/api/blocks/" + options.position, host: uv.options.host, ...(uv.options.port ? { port: uv.options.port } : {}), ...(uv.options.cert ? { cert: uv.options.cert } : {}), ...(uv.options.rejectUnauthorized ? { rejectUnauthorized: uv.options.rejectUnauthorized } : {}), ...(uv.options.ca ? { ca: uv.options.ca } : {}), }, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; res.on("end", () => { resolve(data); }); }); } ); req.end(); req.on("error", (e) => { reject(e); }); }); }; // ============== // PrepareDeploy // ============== export interface PrepareDeployOptions { deployer: string; timestamp: number; nameQty: number; } export interface PrepareDeployResponse { names: string[]; blockNumber: number; } export const prepareDeploy = ( urlOrOptions: string | ValidateUrlOptions, options: PrepareDeployOptions ): Promise<string> => { let urlValidated: undefined | UrlValidated; if (typeof urlOrOptions === "string") { urlValidated = validateUrl({ url: urlOrOptions }); } else { urlValidated = validateUrl(urlOrOptions); } const uv: UrlValidated = urlValidated; return new Promise((resolve, reject) => { const req = uv.lib.request( { headers: { "Content-Type": "application/json", }, method: "POST", path: "/api/prepare-deploy", host: uv.options.host, ...(uv.options.port ? { port: uv.options.port } : {}), ...(uv.options.cert ? { cert: uv.options.cert } : {}), ...(uv.options.rejectUnauthorized ? { rejectUnauthorized: uv.options.rejectUnauthorized } : {}), ...(uv.options.ca ? { ca: uv.options.ca } : {}), }, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; res.on("end", () => { resolve(data); }); }); } ); req.write(JSON.stringify(options)); req.end(); req.on("error", (e) => { reject(e); }); }); }; // data-at-name type NameType = "UnforgPrivate" | "UnforgDeploy" | "UnforgDeployer"; export interface DataAtNameOptions { name: { [nameType: string]: { data: string; }; }; depth: number; } export interface DataAtNameReponse { exprs: { expr: { [type: string]: { data: any } }; block: { blockHash: string; blockNumber: number }; }[]; blockNumber: number; } export const dataAtName = ( urlOrOptions: string | ValidateUrlOptions, options: DataAtNameOptions ): Promise<string> => { let urlValidated: undefined | UrlValidated; if (typeof urlOrOptions === "string") { urlValidated = validateUrl({ url: urlOrOptions }); } else { urlValidated = validateUrl(urlOrOptions); } const uv: UrlValidated = urlValidated; return new Promise((resolve, reject) => { const req = uv.lib.request( { headers: { "Content-Type": "application/json", }, method: "POST", path: "/api/data-at-name", host: uv.options.host, ...(uv.options.port ? { port: uv.options.port } : {}), ...(uv.options.cert ? { cert: uv.options.cert } : {}), ...(uv.options.rejectUnauthorized ? { rejectUnauthorized: uv.options.rejectUnauthorized } : {}), ...(uv.options.ca ? { ca: uv.options.ca } : {}), }, (res) => { let data = ""; res.on("data", (chunk) => { data += chunk; res.on("end", () => { resolve(data); }); }); } ); req.write(JSON.stringify(options)); req.end(); req.on("error", (e) => { reject(e); }); }); };