-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathrpc.ts
237 lines (223 loc) · 10.7 KB
/
rpc.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
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 } from "./rpc_types";
import { whole_to_raw } from "./util";
/** 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: threshold ? whole_to_raw(threshold, this.DECIMALS).toString() : undefined,
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: threshold ? `${threshold}` : undefined,
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;
}
}
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");
}
}
}
}