mirror of
https://github.com/stjet/banani.git
synced 2025-12-29 01:29:24 +00:00
* add get_stats method and StatsType definitions * add get_confirmation_history method and ConfirmationHistoryRPC definitions * added documentation comment for get_confirmation_history method
250 lines
11 KiB
TypeScript
250 lines
11 KiB
TypeScript
import type { Address, BlockHash, BlockCountRPC, BlockInfoRPC, BlocksRPC, BlocksInfoRPC, RepresentativesRPC, RepresentativesOnlineRPC, RepresentativesOnlineWeightRPC, AccountHistoryRPC, AccountHistoryRawRPC, AccountInfoRPC, AccountBalanceRPC, AccountsBalancesRPC, AccountRepresentativeRPC, AccountsRepresentativesRPC, AccountWeightRPC, AccountReceivableRPC, AccountReceivableThresholdRPC, AccountReceivableSourceRPC, DelegatorsRPC, DelegatorsCountRPC, TelemetryRPC, TelemetryRawRPC, TelemetryAddressRPC, VersionRPC, StatsType, StatsRPC, ConfirmationHistoryRPC } from "./rpc_types";
|
|
|
|
/** Implement this interface if the built-in RPC class does not fit your needs. The easiest way to do this is by just extending the built-in RPC class */
|
|
export interface RPCInterface {
|
|
rpc_url: string;
|
|
use_pending: boolean;
|
|
DECIMALS?: number;
|
|
call(payload: Record<string, any>): Promise<Record<string, string>>;
|
|
get_block_info(block_hash: BlockHash): Promise<BlockInfoRPC>;
|
|
get_account_info(account: Address, include_confirmed?: boolean, representative?: boolean, weight?: boolean, pending?: boolean): Promise<AccountInfoRPC>;
|
|
get_account_receivable(account: Address, count?: number, threshold?: `${number}`, source?: boolean): Promise<AccountReceivableRPC | AccountReceivableThresholdRPC | AccountReceivableSourceRPC>;
|
|
}
|
|
|
|
/** Sends RPC requests to the RPC node, also has wrappers for actions that only read the network (write actions are handled by the Wallet class) */
|
|
export class RPC implements RPCInterface {
|
|
rpc_url: string;
|
|
|
|
use_pending: boolean;
|
|
DECIMALS = undefined; //for nano, change to nano decimals
|
|
|
|
debug: boolean = false;
|
|
|
|
/** HTTP headers to send with any RPC requests, defaults to { "Content-Type": "application/json" } */
|
|
headers: Record<string, string> | undefined;
|
|
|
|
/**
|
|
* @param {boolean} [use_pending = false] If true, uses "pending" instead of "receivable" in RPC action names, for compatibility with older versions of the node
|
|
*/
|
|
constructor(rpc_url: string, use_pending: boolean = false) {
|
|
this.rpc_url = rpc_url;
|
|
this.use_pending = use_pending;
|
|
}
|
|
|
|
//Network information related
|
|
|
|
/** The function that sends the RPC POST request */
|
|
async call<T extends Record<string, any>>(payload: Record<string, any>): Promise<T> {
|
|
if (this.debug) console.log(JSON.stringify(payload));
|
|
const resp = await fetch(this.rpc_url, {
|
|
method: "POST",
|
|
headers: this.headers ?? { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
});
|
|
if (!resp.ok && this.debug) console.log(await resp.text());
|
|
if (!resp.ok) throw Error(`Request to RPC node failed with status code ${resp.status}`);
|
|
const resp_json = await resp.json();
|
|
if (resp_json.error) throw Error(`RPC node response: ${resp_json.error}`);
|
|
return resp_json;
|
|
}
|
|
/** See https://docs.nano.org/commands/rpc-protocol/#block_count */
|
|
async get_block_count(): Promise<BlockCountRPC> {
|
|
return (await this.call({
|
|
action: "block_count",
|
|
})) as BlockCountRPC;
|
|
}
|
|
/** See https://docs.nano.org/commands/rpc-protocol/#block_info */
|
|
async get_block_info(block_hash: BlockHash): Promise<BlockInfoRPC> {
|
|
return (await this.call({
|
|
action: "block_info",
|
|
hash: block_hash,
|
|
json_block: true,
|
|
})) as BlockInfoRPC;
|
|
}
|
|
/** See https://docs.nano.org/commands/rpc-protocol/#blocks */
|
|
async get_blocks(block_hashes: BlockHash[]): Promise<BlocksRPC> {
|
|
return (await this.call({
|
|
action: "blocks",
|
|
hashes: block_hashes,
|
|
json_block: true,
|
|
})) as BlocksRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#blocks_info */
|
|
async get_blocks_info(block_hashes: BlockHash[]): Promise<BlocksInfoRPC> {
|
|
return (await this.call({
|
|
action: "blocks_info",
|
|
hashes: block_hashes,
|
|
json_block: true,
|
|
})) as BlocksInfoRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#representatives */
|
|
async get_representatives(): Promise<RepresentativesRPC> {
|
|
return (await this.call({
|
|
action: "representatives",
|
|
})) as RepresentativesRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#representatives_online */
|
|
async get_representatives_online<T extends boolean>(weight?: T): Promise<T extends true ? RepresentativesOnlineWeightRPC : RepresentativesOnlineRPC> {
|
|
return (await this.call({
|
|
action: "representatives_online",
|
|
weight: weight ? true : undefined, //better not to include "weight" if false, rather than sending "weight": false
|
|
})) as T extends true ? RepresentativesOnlineWeightRPC : RepresentativesOnlineRPC;
|
|
}
|
|
|
|
//Account information related
|
|
|
|
/** https://docs.nano.org/commands/rpc-protocol/#account_history */
|
|
async get_account_history<T extends boolean>(account: Address, count: number, raw?: T, head?: BlockHash, offset?: number, reverse?: boolean, account_filter?: Address[]): Promise<T extends true ? AccountHistoryRawRPC : AccountHistoryRPC> {
|
|
return (await this.call({
|
|
action: "account_history",
|
|
account,
|
|
count: `${count}`,
|
|
raw: raw ? true : undefined,
|
|
head,
|
|
offset: offset ? `${offset}` : undefined,
|
|
reverse: reverse ? true : undefined,
|
|
account_filter,
|
|
})) as T extends true ? AccountHistoryRawRPC : AccountHistoryRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#account_info */
|
|
async get_account_info(account: Address, include_confirmed?: boolean, representative?: boolean, weight?: boolean, pending?: boolean): Promise<AccountInfoRPC> {
|
|
return (await this.call({
|
|
action: "account_info",
|
|
account,
|
|
include_confirmed: include_confirmed ? true : undefined,
|
|
representative: representative ? true : undefined,
|
|
weight: weight ? true : undefined,
|
|
pending: pending ? true : undefined,
|
|
})) as AccountInfoRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#account_balance */
|
|
async get_account_balance(account: Address): Promise<AccountBalanceRPC> {
|
|
return (await this.call({
|
|
action: "account_balance",
|
|
account,
|
|
})) as AccountBalanceRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#accounts_balances */
|
|
async get_accounts_balances(accounts: Address[]): Promise<AccountsBalancesRPC> {
|
|
return (await this.call({
|
|
action: "accounts_balances",
|
|
accounts,
|
|
})) as AccountsBalancesRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#account_representative */
|
|
async get_account_representative(account: Address): Promise<AccountRepresentativeRPC> {
|
|
return (await this.call({
|
|
action: "account_representative",
|
|
account,
|
|
})) as AccountRepresentativeRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#accounts_representatives */
|
|
async get_accounts_representatives(account: Address): Promise<AccountsRepresentativesRPC> {
|
|
return (await this.call({
|
|
action: "accounts_representatives",
|
|
account,
|
|
})) as AccountsRepresentativesRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#account_weight */
|
|
async get_account_weight(account: Address): Promise<AccountWeightRPC> {
|
|
return (await this.call({
|
|
action: "account_weight",
|
|
account,
|
|
})) as AccountWeightRPC;
|
|
}
|
|
//I hate nano node rpc here. I don't want to do the conditional type thing here, so have a union
|
|
/** Keep in mind "threshold" parameter is in raw. https://docs.nano.org/commands/rpc-protocol/#receivable */
|
|
async get_account_receivable(account: Address, count?: number, threshold?: `${number}`, source?: boolean): Promise<AccountReceivableRPC | AccountReceivableThresholdRPC | AccountReceivableSourceRPC> {
|
|
return (await this.call({
|
|
action: this.use_pending ? "pending" : "receivable",
|
|
account,
|
|
count: count ? `${count}` : undefined,
|
|
threshold,
|
|
source: source ? true : undefined,
|
|
})) as AccountReceivableRPC | AccountReceivableThresholdRPC | AccountReceivableSourceRPC;
|
|
}
|
|
/** Keep in mind "threshold" parameter is in raw. https://docs.nano.org/commands/rpc-protocol/#delegators */
|
|
async get_delegators(account: Address, threshold?: `${number}`, count?: number, start?: Address): Promise<DelegatorsRPC> {
|
|
return (await this.call({
|
|
action: "delegators",
|
|
account,
|
|
threshold,
|
|
count: count ? `${count}` : undefined,
|
|
start,
|
|
})) as DelegatorsRPC;
|
|
}
|
|
/** https://docs.nano.org/commands/rpc-protocol/#account_weight */
|
|
async get_delegators_count(account: Address): Promise<DelegatorsCountRPC> {
|
|
return (await this.call({
|
|
action: "account_weight",
|
|
account,
|
|
})) as DelegatorsCountRPC;
|
|
}
|
|
|
|
/** https://docs.nano.org/commands/rpc-protocol/#telemetry */
|
|
async get_telemetry(raw?: boolean, address?: string, port?: number): Promise<TelemetryRPC | { metrics: TelemetryRawRPC[] } | TelemetryAddressRPC> {
|
|
return (await this.call({
|
|
action: "telemetry",
|
|
raw: raw ? raw : undefined,
|
|
address: address ? address : undefined,
|
|
port: port ? `${port}` : undefined,
|
|
})) as TelemetryRPC | { metrics: TelemetryRawRPC[] } | TelemetryAddressRPC;
|
|
}
|
|
|
|
/** https://docs.nano.org/commands/rpc-protocol/#version */
|
|
async get_version(): Promise<VersionRPC> {
|
|
return (await this.call({
|
|
action: "version",
|
|
})) as VersionRPC;
|
|
}
|
|
|
|
/** https://docs.nano.org/commands/rpc-protocol/#stats */
|
|
async get_stats<T extends StatsType>(type: T): Promise<StatsRPC<T>> {
|
|
return (await this.call({
|
|
action: "stats",
|
|
type,
|
|
})) as StatsRPC<T>;
|
|
}
|
|
|
|
/** https://docs.nano.org/commands/rpc-protocol/#confirmation_history */
|
|
async get_confirmation_history(hash?: string): Promise<ConfirmationHistoryRPC> {
|
|
return (await this.call({ action: "confirmation_history" })) as ConfirmationHistoryRPC;
|
|
}
|
|
}
|
|
|
|
export class RPCWithBackup extends RPC {
|
|
readonly rpc_urls: string[];
|
|
readonly timeout: number;
|
|
|
|
/**
|
|
* @param {number} [timeout] Request to RPC timeout, in milliseconds. If RPC request fails or timeouts, tries the next RPC
|
|
*/
|
|
constructor(rpc_urls: string[], timeout: number, use_pending: boolean = false) {
|
|
if (rpc_urls.length < 2) throw Error("Must provide at least two RPC URLs");
|
|
super(rpc_urls[0], use_pending);
|
|
this.rpc_urls = rpc_urls;
|
|
this.timeout = timeout;
|
|
}
|
|
async call<T extends Record<string, any>>(payload: Record<string, any>): Promise<T> {
|
|
let i = 0;
|
|
while (true) {
|
|
try {
|
|
const resp = await fetch(this.rpc_urls[i], {
|
|
method: "POST",
|
|
headers: this.headers ?? { "Content-Type": "application/json" },
|
|
body: JSON.stringify(payload),
|
|
signal: (AbortSignal as any).timeout(this.timeout), //for old typescript versions
|
|
});
|
|
if (!resp.ok) throw Error(`Request to RPC node failed with status code ${resp.status}`);
|
|
const resp_json = await resp.json();
|
|
if (resp_json.error) throw Error(`RPC node response: ${resp_json.error}`);
|
|
return resp_json;
|
|
} catch (e) {
|
|
//increment (so try next RPC in provided list), if all RPCs exhausted (all failed), throw error
|
|
//typescript says e might not inherit from Error which is technically true, but in this case it always will be
|
|
if (!this.rpc_urls[++i]) throw Error(e instanceof Error ? e.toString() : "RPC call error");
|
|
}
|
|
}
|
|
}
|
|
}
|