@elsoul/erpc-sdk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/index.cjs +1286 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +785 -0
- package/dist/index.d.ts +785 -0
- package/dist/index.js +1257 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1257 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var REDACTED = "[REDACTED]";
|
|
3
|
+
var credentialVariants = (credential) => {
|
|
4
|
+
const queryEncoded = new URLSearchParams({ value: credential }).toString().slice("value=".length);
|
|
5
|
+
let componentEncoded;
|
|
6
|
+
try {
|
|
7
|
+
componentEncoded = encodeURIComponent(credential);
|
|
8
|
+
} catch {
|
|
9
|
+
}
|
|
10
|
+
return [.../* @__PURE__ */ new Set([credential, componentEncoded, queryEncoded])].filter((value) => Boolean(value)).sort((left, right) => right.length - left.length);
|
|
11
|
+
};
|
|
12
|
+
var redactString = (value, variants) => {
|
|
13
|
+
let redacted = value;
|
|
14
|
+
for (const variant of variants) redacted = redacted.replaceAll(variant, REDACTED);
|
|
15
|
+
return redacted;
|
|
16
|
+
};
|
|
17
|
+
var redactValue = (value, variants, seen, depth) => {
|
|
18
|
+
if (typeof value === "string") return redactString(value, variants);
|
|
19
|
+
if (typeof value !== "object" || value === null) return value;
|
|
20
|
+
if (depth >= 32 || seen.has(value)) return REDACTED;
|
|
21
|
+
seen.add(value);
|
|
22
|
+
if (Array.isArray(value)) {
|
|
23
|
+
return value.map((item) => redactValue(item, variants, seen, depth + 1));
|
|
24
|
+
}
|
|
25
|
+
const result = {};
|
|
26
|
+
for (const [key, item] of Object.entries(value)) {
|
|
27
|
+
Object.defineProperty(result, key, {
|
|
28
|
+
configurable: true,
|
|
29
|
+
enumerable: true,
|
|
30
|
+
value: redactValue(item, variants, seen, depth + 1),
|
|
31
|
+
writable: true
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
var redactJsonRpcError = (error, credential) => {
|
|
37
|
+
if (!credential) return error;
|
|
38
|
+
const variants = credentialVariants(credential);
|
|
39
|
+
const result = {
|
|
40
|
+
code: error.code,
|
|
41
|
+
message: redactString(error.message, variants)
|
|
42
|
+
};
|
|
43
|
+
if (error.data !== void 0) {
|
|
44
|
+
result.data = redactValue(error.data, variants, /* @__PURE__ */ new WeakSet(), 0);
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
var ErpcError = class extends Error {
|
|
49
|
+
code;
|
|
50
|
+
constructor(code, message, options) {
|
|
51
|
+
super(message, options);
|
|
52
|
+
this.name = "ErpcError";
|
|
53
|
+
this.code = code;
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var ErpcConfigError = class extends ErpcError {
|
|
57
|
+
constructor(message) {
|
|
58
|
+
super("ERPC_CONFIG", message);
|
|
59
|
+
this.name = "ErpcConfigError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
var ErpcTransportError = class extends ErpcError {
|
|
63
|
+
constructor(message, options) {
|
|
64
|
+
super("ERPC_TRANSPORT", message, options);
|
|
65
|
+
this.name = "ErpcTransportError";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
var ErpcHttpError = class extends ErpcError {
|
|
69
|
+
status;
|
|
70
|
+
constructor(status, message = `ERPC request failed with HTTP ${status}`) {
|
|
71
|
+
super("ERPC_HTTP", message);
|
|
72
|
+
this.name = "ErpcHttpError";
|
|
73
|
+
this.status = status;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
var ErpcTimeoutError = class extends ErpcError {
|
|
77
|
+
timeoutMs;
|
|
78
|
+
constructor(timeoutMs) {
|
|
79
|
+
super("ERPC_TIMEOUT", `ERPC request timed out after ${timeoutMs}ms`);
|
|
80
|
+
this.name = "ErpcTimeoutError";
|
|
81
|
+
this.timeoutMs = timeoutMs;
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var ErpcAbortedError = class extends ErpcError {
|
|
85
|
+
constructor() {
|
|
86
|
+
super("ERPC_ABORTED", "ERPC request was aborted");
|
|
87
|
+
this.name = "ErpcAbortedError";
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
var ErpcInvalidResponseError = class extends ErpcError {
|
|
91
|
+
constructor(message = "ERPC returned an invalid response") {
|
|
92
|
+
super("ERPC_INVALID_RESPONSE", message);
|
|
93
|
+
this.name = "ErpcInvalidResponseError";
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var ErpcJsonRpcError = class extends ErpcError {
|
|
97
|
+
rpcCode;
|
|
98
|
+
data;
|
|
99
|
+
constructor(error) {
|
|
100
|
+
super("ERPC_RPC", error.message);
|
|
101
|
+
this.name = "ErpcJsonRpcError";
|
|
102
|
+
this.rpcCode = error.code;
|
|
103
|
+
if (error.data !== void 0) this.data = error.data;
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
var ErpcBatchPolicyError = class extends ErpcError {
|
|
107
|
+
constructor(message) {
|
|
108
|
+
super("ERPC_BATCH_POLICY", message);
|
|
109
|
+
this.name = "ErpcBatchPolicyError";
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
// src/transport/rest.ts
|
|
114
|
+
var controlledSignal = (timeoutMs, external) => {
|
|
115
|
+
const controller = new AbortController();
|
|
116
|
+
let didTimeOut = false;
|
|
117
|
+
let timeout = setTimeout(() => {
|
|
118
|
+
didTimeOut = true;
|
|
119
|
+
controller.abort();
|
|
120
|
+
}, timeoutMs);
|
|
121
|
+
const abort = () => controller.abort();
|
|
122
|
+
if (external?.aborted) controller.abort();
|
|
123
|
+
else external?.addEventListener("abort", abort, { once: true });
|
|
124
|
+
const stopTimeout = () => {
|
|
125
|
+
if (timeout === void 0) return;
|
|
126
|
+
clearTimeout(timeout);
|
|
127
|
+
timeout = void 0;
|
|
128
|
+
};
|
|
129
|
+
return {
|
|
130
|
+
signal: controller.signal,
|
|
131
|
+
stopTimeout,
|
|
132
|
+
timeoutMs,
|
|
133
|
+
timedOut: () => didTimeOut,
|
|
134
|
+
cleanup: () => {
|
|
135
|
+
stopTimeout();
|
|
136
|
+
external?.removeEventListener("abort", abort);
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
var normalizeError = (error, controlled, external) => {
|
|
141
|
+
if (error instanceof ErpcAbortedError || error instanceof ErpcHttpError || error instanceof ErpcInvalidResponseError || error instanceof ErpcTimeoutError || error instanceof ErpcTransportError) {
|
|
142
|
+
return error;
|
|
143
|
+
}
|
|
144
|
+
if (controlled.timedOut()) {
|
|
145
|
+
return new ErpcTimeoutError(controlled.timeoutMs);
|
|
146
|
+
}
|
|
147
|
+
if (external?.aborted) return new ErpcAbortedError();
|
|
148
|
+
return new ErpcTransportError("Unable to reach ERPC");
|
|
149
|
+
};
|
|
150
|
+
var applyQuery = (url, query) => {
|
|
151
|
+
if (!query) return;
|
|
152
|
+
for (const [key, value] of Object.entries(query)) {
|
|
153
|
+
if (value === void 0) continue;
|
|
154
|
+
if (Array.isArray(value)) {
|
|
155
|
+
for (const item of value) url.searchParams.append(key, item);
|
|
156
|
+
} else {
|
|
157
|
+
url.searchParams.set(key, String(value));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
var RestTransport = class {
|
|
162
|
+
endpoint;
|
|
163
|
+
#apiKey;
|
|
164
|
+
#endpoint;
|
|
165
|
+
#fetch;
|
|
166
|
+
#headers;
|
|
167
|
+
#timeoutMs;
|
|
168
|
+
constructor(config) {
|
|
169
|
+
this.#apiKey = config.apiKey;
|
|
170
|
+
this.#endpoint = new URL(config.endpoint);
|
|
171
|
+
this.#fetch = config.fetch;
|
|
172
|
+
this.#headers = config.headers;
|
|
173
|
+
this.#timeoutMs = config.timeoutMs;
|
|
174
|
+
const endpoint = new URL(config.endpoint);
|
|
175
|
+
endpoint.search = "";
|
|
176
|
+
endpoint.hash = "";
|
|
177
|
+
this.endpoint = endpoint.toString();
|
|
178
|
+
}
|
|
179
|
+
url(path, query) {
|
|
180
|
+
const url = new URL(this.#endpoint);
|
|
181
|
+
const base = url.pathname === "/" ? "" : url.pathname.replace(/\/+$/, "");
|
|
182
|
+
url.pathname = `${base}/${path.replace(/^\/+/, "")}`.replace(/\/{2,}/g, "/");
|
|
183
|
+
applyQuery(url, query);
|
|
184
|
+
return url;
|
|
185
|
+
}
|
|
186
|
+
async get(path, query, options = {}) {
|
|
187
|
+
const controlled = controlledSignal(this.#timeoutMs, options.signal);
|
|
188
|
+
try {
|
|
189
|
+
const response = await this.#get(path, query, controlled.signal);
|
|
190
|
+
if (!response.ok) throw new ErpcHttpError(response.status);
|
|
191
|
+
const text = await response.text();
|
|
192
|
+
try {
|
|
193
|
+
return JSON.parse(text);
|
|
194
|
+
} catch {
|
|
195
|
+
throw new ErpcInvalidResponseError("ERPC returned malformed JSON");
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw normalizeError(error, controlled, options.signal);
|
|
199
|
+
} finally {
|
|
200
|
+
controlled.cleanup();
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async stream(path, query, options = {}) {
|
|
204
|
+
const controlled = controlledSignal(this.#timeoutMs, options.signal);
|
|
205
|
+
try {
|
|
206
|
+
const response = await this.#get(path, query, controlled.signal);
|
|
207
|
+
if (!response.ok) throw new ErpcHttpError(response.status);
|
|
208
|
+
controlled.stopTimeout();
|
|
209
|
+
return {
|
|
210
|
+
response,
|
|
211
|
+
close: controlled.cleanup,
|
|
212
|
+
normalizeError: (error) => normalizeError(error, controlled, options.signal)
|
|
213
|
+
};
|
|
214
|
+
} catch (error) {
|
|
215
|
+
controlled.cleanup();
|
|
216
|
+
throw normalizeError(error, controlled, options.signal);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
#get(path, query, signal) {
|
|
220
|
+
return this.#fetch(this.url(path, query), {
|
|
221
|
+
method: "GET",
|
|
222
|
+
headers: {
|
|
223
|
+
...this.#headers,
|
|
224
|
+
authorization: `Bearer ${this.#apiKey}`,
|
|
225
|
+
accept: "application/json"
|
|
226
|
+
},
|
|
227
|
+
signal
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// src/account.ts
|
|
233
|
+
var AccountClient = class {
|
|
234
|
+
#transport;
|
|
235
|
+
constructor(transport) {
|
|
236
|
+
this.#transport = transport;
|
|
237
|
+
}
|
|
238
|
+
getTokenBalance(options) {
|
|
239
|
+
return this.#transport.get("/v3/erpc/token-balance", void 0, options);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
// src/config.ts
|
|
244
|
+
var DEFAULT_ENDPOINT = "https://edge.erpc.global";
|
|
245
|
+
var DEFAULT_ACCOUNT_ENDPOINT = "https://solana-rpc.erpc.global";
|
|
246
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
247
|
+
var requireFetch = (provided) => {
|
|
248
|
+
const implementation = provided ?? globalThis.fetch;
|
|
249
|
+
if (typeof implementation !== "function") {
|
|
250
|
+
throw new ErpcConfigError(
|
|
251
|
+
"A Fetch API implementation is required in this runtime"
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
return implementation;
|
|
255
|
+
};
|
|
256
|
+
var resolveConfig = (config) => {
|
|
257
|
+
const apiKey = config.apiKey.trim();
|
|
258
|
+
if (!apiKey) throw new ErpcConfigError("apiKey must not be empty");
|
|
259
|
+
let endpoint;
|
|
260
|
+
let accountEndpoint;
|
|
261
|
+
try {
|
|
262
|
+
endpoint = new URL(config.endpoint ?? DEFAULT_ENDPOINT);
|
|
263
|
+
accountEndpoint = new URL(
|
|
264
|
+
config.accountEndpoint ?? DEFAULT_ACCOUNT_ENDPOINT
|
|
265
|
+
);
|
|
266
|
+
} catch {
|
|
267
|
+
throw new ErpcConfigError("endpoint must be an absolute HTTP(S) URL");
|
|
268
|
+
}
|
|
269
|
+
if (endpoint.protocol !== "https:" && endpoint.protocol !== "http:") {
|
|
270
|
+
throw new ErpcConfigError("endpoint must use HTTP or HTTPS");
|
|
271
|
+
}
|
|
272
|
+
if (accountEndpoint.protocol !== "https:" && accountEndpoint.protocol !== "http:") {
|
|
273
|
+
throw new ErpcConfigError("accountEndpoint must use HTTP or HTTPS");
|
|
274
|
+
}
|
|
275
|
+
endpoint.search = "";
|
|
276
|
+
endpoint.hash = "";
|
|
277
|
+
endpoint.pathname = endpoint.pathname.replace(/\/+$/, "") || "/";
|
|
278
|
+
accountEndpoint.search = "";
|
|
279
|
+
accountEndpoint.hash = "";
|
|
280
|
+
accountEndpoint.pathname = accountEndpoint.pathname.replace(/\/+$/, "") || "/";
|
|
281
|
+
const timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
282
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
283
|
+
throw new ErpcConfigError("timeoutMs must be a positive finite number");
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
accountEndpoint,
|
|
287
|
+
apiKey,
|
|
288
|
+
endpoint,
|
|
289
|
+
fetch: requireFetch(config.fetch),
|
|
290
|
+
headers: config.headers ?? {},
|
|
291
|
+
timeoutMs,
|
|
292
|
+
...config.webSocket === void 0 ? {} : { webSocket: config.webSocket }
|
|
293
|
+
};
|
|
294
|
+
};
|
|
295
|
+
var endpointWithPath = (endpoint, path) => {
|
|
296
|
+
const url = new URL(endpoint);
|
|
297
|
+
const base = endpoint.pathname === "/" ? "" : endpoint.pathname;
|
|
298
|
+
url.pathname = `${base}/${path.replace(/^\/+/, "")}`.replace(/\/{2,}/g, "/");
|
|
299
|
+
return url;
|
|
300
|
+
};
|
|
301
|
+
var websocketUrl = (endpoint, apiKey, path = "") => {
|
|
302
|
+
const url = endpointWithPath(endpoint, path);
|
|
303
|
+
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
304
|
+
url.searchParams.set("api-key", apiKey);
|
|
305
|
+
return url;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// src/price.ts
|
|
309
|
+
var updateQuery = (options) => ({
|
|
310
|
+
"ids[]": options.ids,
|
|
311
|
+
encoding: options.encoding,
|
|
312
|
+
parsed: options.parsed,
|
|
313
|
+
ignore_invalid_price_ids: options.ignoreInvalidPriceIds
|
|
314
|
+
});
|
|
315
|
+
var parseSseBlock = (block) => {
|
|
316
|
+
const data = [];
|
|
317
|
+
let event;
|
|
318
|
+
let id;
|
|
319
|
+
for (const line of block.split(/\r?\n/)) {
|
|
320
|
+
if (!line || line.startsWith(":")) continue;
|
|
321
|
+
const separator = line.indexOf(":");
|
|
322
|
+
const field = separator === -1 ? line : line.slice(0, separator);
|
|
323
|
+
const raw = separator === -1 ? "" : line.slice(separator + 1);
|
|
324
|
+
const value = raw.startsWith(" ") ? raw.slice(1) : raw;
|
|
325
|
+
if (field === "data") data.push(value);
|
|
326
|
+
else if (field === "event") event = value;
|
|
327
|
+
else if (field === "id") id = value;
|
|
328
|
+
}
|
|
329
|
+
if (data.length === 0) return null;
|
|
330
|
+
let parsed;
|
|
331
|
+
try {
|
|
332
|
+
parsed = JSON.parse(data.join("\n"));
|
|
333
|
+
} catch {
|
|
334
|
+
throw new ErpcInvalidResponseError("ERPC returned malformed stream data");
|
|
335
|
+
}
|
|
336
|
+
const result = { data: parsed };
|
|
337
|
+
if (event !== void 0) result.event = event;
|
|
338
|
+
if (id !== void 0) result.id = id;
|
|
339
|
+
return result;
|
|
340
|
+
};
|
|
341
|
+
var PriceClient = class {
|
|
342
|
+
#transport;
|
|
343
|
+
constructor(transport) {
|
|
344
|
+
this.#transport = transport;
|
|
345
|
+
}
|
|
346
|
+
getPriceFeeds(options = {}, sendOptions) {
|
|
347
|
+
return this.#transport.get(
|
|
348
|
+
"/v2/price_feeds",
|
|
349
|
+
{ query: options.query, asset_type: options.assetType },
|
|
350
|
+
sendOptions
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
getLatestPriceUpdates(options, sendOptions) {
|
|
354
|
+
return this.#transport.get(
|
|
355
|
+
"/v2/updates/price/latest",
|
|
356
|
+
updateQuery(options),
|
|
357
|
+
sendOptions
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
getPriceUpdatesAtTimestamp(publishTime, options, sendOptions) {
|
|
361
|
+
return this.#transport.get(
|
|
362
|
+
`/v2/updates/price/${encodeURIComponent(String(publishTime))}`,
|
|
363
|
+
updateQuery(options),
|
|
364
|
+
sendOptions
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
getLatestPublisherStakeCaps(options = {}, sendOptions) {
|
|
368
|
+
return this.#transport.get(
|
|
369
|
+
"/v2/updates/publisher_stake_caps/latest",
|
|
370
|
+
options,
|
|
371
|
+
sendOptions
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
async *streamPriceUpdates(options, sendOptions) {
|
|
375
|
+
const stream = await this.#transport.stream(
|
|
376
|
+
"/v2/updates/price/stream",
|
|
377
|
+
{
|
|
378
|
+
...updateQuery(options),
|
|
379
|
+
allow_unordered: options.allowUnordered,
|
|
380
|
+
benchmarks_only: options.benchmarksOnly
|
|
381
|
+
},
|
|
382
|
+
sendOptions
|
|
383
|
+
);
|
|
384
|
+
const response = stream.response;
|
|
385
|
+
if (!response.body) {
|
|
386
|
+
stream.close();
|
|
387
|
+
throw new ErpcInvalidResponseError("ERPC returned an empty stream");
|
|
388
|
+
}
|
|
389
|
+
const reader = response.body.getReader();
|
|
390
|
+
const decoder = new TextDecoder();
|
|
391
|
+
let buffer = "";
|
|
392
|
+
try {
|
|
393
|
+
while (true) {
|
|
394
|
+
const { done, value } = await reader.read();
|
|
395
|
+
buffer += decoder.decode(value, { stream: !done });
|
|
396
|
+
let boundary = buffer.search(/\r?\n\r?\n/);
|
|
397
|
+
while (boundary !== -1) {
|
|
398
|
+
const separatorLength = buffer.slice(boundary).startsWith("\r\n") ? 4 : 2;
|
|
399
|
+
const block = buffer.slice(0, boundary);
|
|
400
|
+
buffer = buffer.slice(boundary + separatorLength);
|
|
401
|
+
const event = parseSseBlock(block);
|
|
402
|
+
if (event) yield event;
|
|
403
|
+
boundary = buffer.search(/\r?\n\r?\n/);
|
|
404
|
+
}
|
|
405
|
+
if (done) break;
|
|
406
|
+
}
|
|
407
|
+
const finalEvent = parseSseBlock(buffer);
|
|
408
|
+
if (finalEvent) yield finalEvent;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
throw stream.normalizeError(error);
|
|
411
|
+
} finally {
|
|
412
|
+
try {
|
|
413
|
+
await reader.cancel();
|
|
414
|
+
} catch {
|
|
415
|
+
} finally {
|
|
416
|
+
reader.releaseLock();
|
|
417
|
+
stream.close();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// src/rpc/client.ts
|
|
424
|
+
var PendingRpcRequest = class {
|
|
425
|
+
#send;
|
|
426
|
+
constructor(send) {
|
|
427
|
+
this.#send = send;
|
|
428
|
+
}
|
|
429
|
+
send(options) {
|
|
430
|
+
return this.#send(options);
|
|
431
|
+
}
|
|
432
|
+
};
|
|
433
|
+
var PendingRpcBatchRequest = class {
|
|
434
|
+
#send;
|
|
435
|
+
constructor(send) {
|
|
436
|
+
this.#send = send;
|
|
437
|
+
}
|
|
438
|
+
send(options) {
|
|
439
|
+
return this.#send(options);
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
var methodParams = (mode, args) => {
|
|
443
|
+
if (mode === "positional") return args;
|
|
444
|
+
if (args.length === 0) return void 0;
|
|
445
|
+
return args[0];
|
|
446
|
+
};
|
|
447
|
+
var createRpcNamespace = (config) => {
|
|
448
|
+
const base = {
|
|
449
|
+
endpoint: config.transport.endpoint,
|
|
450
|
+
batch: (calls) => {
|
|
451
|
+
const wireCalls = calls;
|
|
452
|
+
config.validateBatch?.(wireCalls);
|
|
453
|
+
return new PendingRpcBatchRequest(
|
|
454
|
+
(options) => config.transport.batch(
|
|
455
|
+
wireCalls,
|
|
456
|
+
options
|
|
457
|
+
)
|
|
458
|
+
);
|
|
459
|
+
},
|
|
460
|
+
raw: (method, params) => new PendingRpcRequest(
|
|
461
|
+
(options) => config.transport.request(method, params, options)
|
|
462
|
+
),
|
|
463
|
+
request: (method, ...params) => new PendingRpcRequest(
|
|
464
|
+
(options) => config.transport.request(
|
|
465
|
+
method,
|
|
466
|
+
params[0],
|
|
467
|
+
options
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
};
|
|
471
|
+
return new Proxy(base, {
|
|
472
|
+
get(target, property, receiver) {
|
|
473
|
+
if (typeof property !== "string" || property in target) {
|
|
474
|
+
return Reflect.get(target, property, receiver);
|
|
475
|
+
}
|
|
476
|
+
if (property === "then") return void 0;
|
|
477
|
+
return (...args) => new PendingRpcRequest(
|
|
478
|
+
(options) => config.transport.request(
|
|
479
|
+
property,
|
|
480
|
+
methodParams(config.parameterMode, args),
|
|
481
|
+
options
|
|
482
|
+
)
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
// src/rpc/ethereum.ts
|
|
489
|
+
var ETHEREUM_RPC_METHODS = [
|
|
490
|
+
"eth_accounts",
|
|
491
|
+
"eth_baseFee",
|
|
492
|
+
"eth_blobBaseFee",
|
|
493
|
+
"eth_blockNumber",
|
|
494
|
+
"eth_call",
|
|
495
|
+
"eth_callMany",
|
|
496
|
+
"eth_capabilities",
|
|
497
|
+
"eth_chainId",
|
|
498
|
+
"eth_createAccessList",
|
|
499
|
+
"eth_estimateGas",
|
|
500
|
+
"eth_feeHistory",
|
|
501
|
+
"eth_gasPrice",
|
|
502
|
+
"eth_getAccount",
|
|
503
|
+
"eth_getBalance",
|
|
504
|
+
"eth_getBlockByHash",
|
|
505
|
+
"eth_getBlockByNumber",
|
|
506
|
+
"eth_getBlockReceipts",
|
|
507
|
+
"eth_getBlockTransactionCountByHash",
|
|
508
|
+
"eth_getBlockTransactionCountByNumber",
|
|
509
|
+
"eth_getCode",
|
|
510
|
+
"eth_getFilterChanges",
|
|
511
|
+
"eth_getFilterLogs",
|
|
512
|
+
"eth_getLogs",
|
|
513
|
+
"eth_getProof",
|
|
514
|
+
"eth_getRawTransactionByHash",
|
|
515
|
+
"eth_getStorageAt",
|
|
516
|
+
"eth_getTransactionByBlockHashAndIndex",
|
|
517
|
+
"eth_getTransactionByBlockNumberAndIndex",
|
|
518
|
+
"eth_getTransactionByHash",
|
|
519
|
+
"eth_getTransactionBySenderAndNonce",
|
|
520
|
+
"eth_getTransactionCount",
|
|
521
|
+
"eth_getTransactionReceipt",
|
|
522
|
+
"eth_getUncleCountByBlockHash",
|
|
523
|
+
"eth_getUncleCountByBlockNumber",
|
|
524
|
+
"eth_maxPriorityFeePerGas",
|
|
525
|
+
"eth_newBlockFilter",
|
|
526
|
+
"eth_newFilter",
|
|
527
|
+
"eth_newPendingTransactionFilter",
|
|
528
|
+
"eth_sendRawTransaction",
|
|
529
|
+
"eth_signTransaction",
|
|
530
|
+
"eth_simulateV1",
|
|
531
|
+
"eth_submitWork",
|
|
532
|
+
"eth_syncing",
|
|
533
|
+
"eth_uninstallFilter",
|
|
534
|
+
"net_listening",
|
|
535
|
+
"net_peerCount",
|
|
536
|
+
"net_version",
|
|
537
|
+
"txpool_content",
|
|
538
|
+
"txpool_contentFrom",
|
|
539
|
+
"txpool_inspect",
|
|
540
|
+
"txpool_status",
|
|
541
|
+
"web3_clientVersion",
|
|
542
|
+
"web3_sha3"
|
|
543
|
+
];
|
|
544
|
+
var ETHEREUM_SUBSCRIPTION_METHODS = [
|
|
545
|
+
"eth_subscribe",
|
|
546
|
+
"eth_unsubscribe"
|
|
547
|
+
];
|
|
548
|
+
var createEthereumClient = (transport) => ({
|
|
549
|
+
rpc: createRpcNamespace({
|
|
550
|
+
transport,
|
|
551
|
+
parameterMode: "positional"
|
|
552
|
+
})
|
|
553
|
+
});
|
|
554
|
+
|
|
555
|
+
// src/rpc/solana.ts
|
|
556
|
+
var SOLANA_RPC_METHODS = [
|
|
557
|
+
"getAccountInfo",
|
|
558
|
+
"getBalance",
|
|
559
|
+
"getBlock",
|
|
560
|
+
"getBlockCommitment",
|
|
561
|
+
"getBlockHeight",
|
|
562
|
+
"getBlockProduction",
|
|
563
|
+
"getBlockTime",
|
|
564
|
+
"getBlocks",
|
|
565
|
+
"getBlocksWithLimit",
|
|
566
|
+
"getClusterNodes",
|
|
567
|
+
"getEpochInfo",
|
|
568
|
+
"getEpochSchedule",
|
|
569
|
+
"getFeeForMessage",
|
|
570
|
+
"getFirstAvailableBlock",
|
|
571
|
+
"getGenesisHash",
|
|
572
|
+
"getHealth",
|
|
573
|
+
"getHighestSnapshotSlot",
|
|
574
|
+
"getIdentity",
|
|
575
|
+
"getInflationGovernor",
|
|
576
|
+
"getInflationRate",
|
|
577
|
+
"getInflationReward",
|
|
578
|
+
"getLargestAccounts",
|
|
579
|
+
"getLatestBlockhash",
|
|
580
|
+
"getLeaderSchedule",
|
|
581
|
+
"getMaxRetransmitSlot",
|
|
582
|
+
"getMaxShredInsertSlot",
|
|
583
|
+
"getMinimumBalanceForRentExemption",
|
|
584
|
+
"getMultipleAccounts",
|
|
585
|
+
"getParsedTransaction",
|
|
586
|
+
"getPriorityFeeEstimate",
|
|
587
|
+
"getProgramAccounts",
|
|
588
|
+
"getProgramAccountsV2",
|
|
589
|
+
"getRecentPerformanceSamples",
|
|
590
|
+
"getRecentPrioritizationFees",
|
|
591
|
+
"getSignatureStatuses",
|
|
592
|
+
"getSignaturesForAddress",
|
|
593
|
+
"getSlot",
|
|
594
|
+
"getSlotLeader",
|
|
595
|
+
"getSlotLeaders",
|
|
596
|
+
"getStakeMinimumDelegation",
|
|
597
|
+
"getSupply",
|
|
598
|
+
"getTokenAccountBalance",
|
|
599
|
+
"getTokenAccountsByDelegate",
|
|
600
|
+
"getTokenAccountsByOwner",
|
|
601
|
+
"getTokenLargestAccounts",
|
|
602
|
+
"getTokenSupply",
|
|
603
|
+
"getTransaction",
|
|
604
|
+
"getTransactionCount",
|
|
605
|
+
"getVersion",
|
|
606
|
+
"getVoteAccounts",
|
|
607
|
+
"isBlockhashValid",
|
|
608
|
+
"minimumLedgerSlot",
|
|
609
|
+
"requestAirdrop",
|
|
610
|
+
"sendTransaction",
|
|
611
|
+
"simulateTransaction"
|
|
612
|
+
];
|
|
613
|
+
var SOLANA_DAS_METHODS = [
|
|
614
|
+
"getAsset",
|
|
615
|
+
"getAssetBatch",
|
|
616
|
+
"getAssetProof",
|
|
617
|
+
"getAssetProofBatch",
|
|
618
|
+
"getAssetsByAuthority",
|
|
619
|
+
"getAssetsByCreator",
|
|
620
|
+
"getAssetsByGroup",
|
|
621
|
+
"getAssetsByOwner",
|
|
622
|
+
"getNftEditions",
|
|
623
|
+
"getSignaturesForAsset",
|
|
624
|
+
"getTokenAccounts",
|
|
625
|
+
"getTokensByDelegate",
|
|
626
|
+
"getTokensByOwner",
|
|
627
|
+
"searchAssets"
|
|
628
|
+
];
|
|
629
|
+
var SOLANA_HISTORY_METHODS = [
|
|
630
|
+
"getTransactionsForAddress",
|
|
631
|
+
"getTransfersByAddress"
|
|
632
|
+
];
|
|
633
|
+
var SOLANA_LEADER_METHODS = [
|
|
634
|
+
"getLeaderSlots",
|
|
635
|
+
"getValidatorsInformation"
|
|
636
|
+
];
|
|
637
|
+
var SOLANA_ANALYTICS_METHODS = [
|
|
638
|
+
"jetEpochSummary",
|
|
639
|
+
"jetProgramStats",
|
|
640
|
+
"jetSlotStats",
|
|
641
|
+
"jetTopPrograms",
|
|
642
|
+
"jetTpsTimeseries"
|
|
643
|
+
];
|
|
644
|
+
var heavyRpcMethods = /* @__PURE__ */ new Set([
|
|
645
|
+
"getPriorityFeeEstimate",
|
|
646
|
+
"getProgramAccounts",
|
|
647
|
+
"getProgramAccountsV2",
|
|
648
|
+
"getTokenLargestAccounts"
|
|
649
|
+
]);
|
|
650
|
+
var validateStandardBatch = (calls) => {
|
|
651
|
+
let containsHeavy = false;
|
|
652
|
+
let containsStandard = false;
|
|
653
|
+
for (const call of calls) {
|
|
654
|
+
if (heavyRpcMethods.has(call.method)) containsHeavy = true;
|
|
655
|
+
else containsStandard = true;
|
|
656
|
+
}
|
|
657
|
+
if (containsHeavy && containsStandard) {
|
|
658
|
+
throw new ErpcBatchPolicyError(
|
|
659
|
+
"Solana indexed and standard RPC methods cannot share a batch"
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
var rejectLeaderBatch = (calls) => {
|
|
664
|
+
if (calls.length > 0) {
|
|
665
|
+
throw new ErpcBatchPolicyError("Leader RPC methods do not support batching");
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
var createSolanaClient = (transport) => ({
|
|
669
|
+
rpc: createRpcNamespace({
|
|
670
|
+
transport,
|
|
671
|
+
parameterMode: "positional",
|
|
672
|
+
validateBatch: validateStandardBatch
|
|
673
|
+
}),
|
|
674
|
+
das: createRpcNamespace({
|
|
675
|
+
transport,
|
|
676
|
+
parameterMode: "named"
|
|
677
|
+
}),
|
|
678
|
+
history: createRpcNamespace({
|
|
679
|
+
transport,
|
|
680
|
+
parameterMode: "positional"
|
|
681
|
+
}),
|
|
682
|
+
leaders: createRpcNamespace({
|
|
683
|
+
transport,
|
|
684
|
+
parameterMode: "positional",
|
|
685
|
+
validateBatch: rejectLeaderBatch
|
|
686
|
+
}),
|
|
687
|
+
analytics: createRpcNamespace({
|
|
688
|
+
transport,
|
|
689
|
+
parameterMode: "positional"
|
|
690
|
+
})
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
// src/transport/websocket.ts
|
|
694
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
695
|
+
var isRpcError = (value) => isObject(value) && typeof value.code === "number" && typeof value.message === "string";
|
|
696
|
+
var publicEndpoint = (endpoint) => {
|
|
697
|
+
const url = new URL(endpoint);
|
|
698
|
+
url.search = "";
|
|
699
|
+
url.hash = "";
|
|
700
|
+
return url.toString();
|
|
701
|
+
};
|
|
702
|
+
var WebSocketJsonRpcTransport = class {
|
|
703
|
+
endpoint;
|
|
704
|
+
maxBatchSize;
|
|
705
|
+
#connectionUrl;
|
|
706
|
+
#credential;
|
|
707
|
+
#listeners = /* @__PURE__ */ new Set();
|
|
708
|
+
#pending = /* @__PURE__ */ new Map();
|
|
709
|
+
#timeoutMs;
|
|
710
|
+
#webSocket;
|
|
711
|
+
#connectPromise;
|
|
712
|
+
#nextId = 1;
|
|
713
|
+
#socket;
|
|
714
|
+
constructor(config) {
|
|
715
|
+
const implementation = config.webSocket ?? globalThis.WebSocket;
|
|
716
|
+
if (typeof implementation === "function") this.#webSocket = implementation;
|
|
717
|
+
this.#credential = config.endpoint.searchParams.get("api-key") ?? void 0;
|
|
718
|
+
this.#connectionUrl = config.endpoint.toString();
|
|
719
|
+
this.endpoint = publicEndpoint(config.endpoint);
|
|
720
|
+
this.maxBatchSize = config.maxBatchSize ?? 256;
|
|
721
|
+
this.#timeoutMs = config.timeoutMs;
|
|
722
|
+
}
|
|
723
|
+
async connect() {
|
|
724
|
+
if (this.#socket?.readyState === 1) return;
|
|
725
|
+
if (this.#connectPromise) return this.#connectPromise;
|
|
726
|
+
if (!this.#webSocket) {
|
|
727
|
+
throw new ErpcConfigError(
|
|
728
|
+
"A WebSocket implementation is required for subscriptions"
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
this.#connectPromise = new Promise((resolve, reject) => {
|
|
732
|
+
const socket = new this.#webSocket(this.#connectionUrl);
|
|
733
|
+
this.#socket = socket;
|
|
734
|
+
const timeout = setTimeout(() => {
|
|
735
|
+
socket.close();
|
|
736
|
+
reject(new ErpcTimeoutError(this.#timeoutMs));
|
|
737
|
+
}, this.#timeoutMs);
|
|
738
|
+
socket.addEventListener(
|
|
739
|
+
"open",
|
|
740
|
+
() => {
|
|
741
|
+
clearTimeout(timeout);
|
|
742
|
+
resolve();
|
|
743
|
+
},
|
|
744
|
+
{ once: true }
|
|
745
|
+
);
|
|
746
|
+
socket.addEventListener(
|
|
747
|
+
"error",
|
|
748
|
+
() => {
|
|
749
|
+
clearTimeout(timeout);
|
|
750
|
+
reject(new ErpcTransportError("Unable to connect to ERPC WebSocket"));
|
|
751
|
+
},
|
|
752
|
+
{ once: true }
|
|
753
|
+
);
|
|
754
|
+
socket.addEventListener("message", (event) => {
|
|
755
|
+
void this.#onMessage(event.data);
|
|
756
|
+
});
|
|
757
|
+
socket.addEventListener("close", () => {
|
|
758
|
+
this.#socket = void 0;
|
|
759
|
+
this.#connectPromise = void 0;
|
|
760
|
+
const error = new ErpcTransportError("ERPC WebSocket connection closed");
|
|
761
|
+
for (const pending of this.#pending.values()) pending.reject(error);
|
|
762
|
+
this.#pending.clear();
|
|
763
|
+
});
|
|
764
|
+
}).catch((error) => {
|
|
765
|
+
this.#connectPromise = void 0;
|
|
766
|
+
throw error;
|
|
767
|
+
});
|
|
768
|
+
return this.#connectPromise;
|
|
769
|
+
}
|
|
770
|
+
close(code = 1e3, reason = "Client closed connection") {
|
|
771
|
+
this.#socket?.close(code, reason);
|
|
772
|
+
this.#socket = void 0;
|
|
773
|
+
this.#connectPromise = void 0;
|
|
774
|
+
}
|
|
775
|
+
onNotification(listener) {
|
|
776
|
+
this.#listeners.add(listener);
|
|
777
|
+
return () => this.#listeners.delete(listener);
|
|
778
|
+
}
|
|
779
|
+
async request(method, params, options = {}) {
|
|
780
|
+
await this.connect();
|
|
781
|
+
const id = this.#id();
|
|
782
|
+
const promise = this.#waitFor(id, options);
|
|
783
|
+
const request = { jsonrpc: "2.0", id, method };
|
|
784
|
+
if (params !== void 0) request.params = params;
|
|
785
|
+
this.#send(request, id);
|
|
786
|
+
return promise;
|
|
787
|
+
}
|
|
788
|
+
async batch(calls, options = {}) {
|
|
789
|
+
if (calls.length === 0) return [];
|
|
790
|
+
if (calls.length > this.maxBatchSize) {
|
|
791
|
+
throw new ErpcInvalidResponseError(
|
|
792
|
+
`A batch may contain at most ${this.maxBatchSize} calls`
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
await this.connect();
|
|
796
|
+
const requests = calls.map((call) => {
|
|
797
|
+
const id = this.#id();
|
|
798
|
+
const request = {
|
|
799
|
+
jsonrpc: "2.0",
|
|
800
|
+
id,
|
|
801
|
+
method: call.method
|
|
802
|
+
};
|
|
803
|
+
if (call.params !== void 0) request.params = call.params;
|
|
804
|
+
return { id, request, response: this.#waitFor(id, options) };
|
|
805
|
+
});
|
|
806
|
+
try {
|
|
807
|
+
this.#socket?.send(JSON.stringify(requests.map(({ request }) => request)));
|
|
808
|
+
} catch (error) {
|
|
809
|
+
for (const { id } of requests) this.#reject(id, error);
|
|
810
|
+
}
|
|
811
|
+
return Promise.all(requests.map(({ response }) => response));
|
|
812
|
+
}
|
|
813
|
+
#id() {
|
|
814
|
+
const id = this.#nextId;
|
|
815
|
+
this.#nextId = id >= Number.MAX_SAFE_INTEGER ? 1 : id + 1;
|
|
816
|
+
return id;
|
|
817
|
+
}
|
|
818
|
+
#send(request, id) {
|
|
819
|
+
try {
|
|
820
|
+
this.#socket?.send(JSON.stringify(request));
|
|
821
|
+
} catch (error) {
|
|
822
|
+
this.#reject(id, error);
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
#waitFor(id, options) {
|
|
826
|
+
return new Promise((resolve, reject) => {
|
|
827
|
+
const timeout = setTimeout(() => {
|
|
828
|
+
this.#pending.delete(id);
|
|
829
|
+
reject(new ErpcTimeoutError(this.#timeoutMs));
|
|
830
|
+
}, this.#timeoutMs);
|
|
831
|
+
const abort = () => {
|
|
832
|
+
clearTimeout(timeout);
|
|
833
|
+
this.#pending.delete(id);
|
|
834
|
+
reject(new ErpcAbortedError());
|
|
835
|
+
};
|
|
836
|
+
options.signal?.addEventListener("abort", abort, { once: true });
|
|
837
|
+
const cleanup = () => {
|
|
838
|
+
clearTimeout(timeout);
|
|
839
|
+
options.signal?.removeEventListener("abort", abort);
|
|
840
|
+
};
|
|
841
|
+
this.#pending.set(id, {
|
|
842
|
+
resolve: (value) => resolve(value),
|
|
843
|
+
reject,
|
|
844
|
+
cleanup
|
|
845
|
+
});
|
|
846
|
+
if (options.signal?.aborted) abort();
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
#reject(id, _reason) {
|
|
850
|
+
const pending = this.#pending.get(id);
|
|
851
|
+
if (!pending) return;
|
|
852
|
+
pending.cleanup();
|
|
853
|
+
this.#pending.delete(id);
|
|
854
|
+
pending.reject(
|
|
855
|
+
new ErpcTransportError("Unable to send ERPC WebSocket request")
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
async #onMessage(data) {
|
|
859
|
+
const text = await this.#messageText(data);
|
|
860
|
+
if (text === null) return;
|
|
861
|
+
let parsed;
|
|
862
|
+
try {
|
|
863
|
+
parsed = JSON.parse(text);
|
|
864
|
+
} catch {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
if (Array.isArray(parsed)) {
|
|
868
|
+
for (const item of parsed) this.#dispatch(item);
|
|
869
|
+
} else {
|
|
870
|
+
this.#dispatch(parsed);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
async #messageText(data) {
|
|
874
|
+
if (typeof data === "string") return data;
|
|
875
|
+
if (data instanceof ArrayBuffer) return new TextDecoder().decode(data);
|
|
876
|
+
if (ArrayBuffer.isView(data)) {
|
|
877
|
+
return new TextDecoder().decode(
|
|
878
|
+
new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
if (typeof Blob !== "undefined" && data instanceof Blob) return data.text();
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
#dispatch(message) {
|
|
885
|
+
if (!isObject(message)) return;
|
|
886
|
+
const id = message.id;
|
|
887
|
+
if (typeof id === "number" || typeof id === "string") {
|
|
888
|
+
const pending = this.#pending.get(id);
|
|
889
|
+
if (!pending) return;
|
|
890
|
+
pending.cleanup();
|
|
891
|
+
this.#pending.delete(id);
|
|
892
|
+
if (isRpcError(message.error)) {
|
|
893
|
+
pending.reject(
|
|
894
|
+
new ErpcJsonRpcError(
|
|
895
|
+
redactJsonRpcError(message.error, this.#credential)
|
|
896
|
+
)
|
|
897
|
+
);
|
|
898
|
+
} else if ("result" in message) pending.resolve(message.result);
|
|
899
|
+
else pending.reject(new ErpcInvalidResponseError());
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
if (message.jsonrpc === "2.0" && typeof message.method === "string" && "params" in message) {
|
|
903
|
+
const notification = {
|
|
904
|
+
jsonrpc: "2.0",
|
|
905
|
+
method: message.method,
|
|
906
|
+
params: message.params
|
|
907
|
+
};
|
|
908
|
+
for (const listener of this.#listeners) listener(notification);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
};
|
|
912
|
+
|
|
913
|
+
// src/subscriptions.ts
|
|
914
|
+
var notificationParams = (notification) => {
|
|
915
|
+
if (typeof notification.params !== "object" || notification.params === null) {
|
|
916
|
+
return null;
|
|
917
|
+
}
|
|
918
|
+
const params = notification.params;
|
|
919
|
+
if (typeof params.subscription !== "number" && typeof params.subscription !== "string" || !("result" in params)) {
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
return {
|
|
923
|
+
subscription: params.subscription,
|
|
924
|
+
result: params.result
|
|
925
|
+
};
|
|
926
|
+
};
|
|
927
|
+
var RpcSubscription = class {
|
|
928
|
+
id;
|
|
929
|
+
#unsubscribe;
|
|
930
|
+
#closed = false;
|
|
931
|
+
constructor(id, unsubscribe) {
|
|
932
|
+
this.id = id;
|
|
933
|
+
this.#unsubscribe = unsubscribe;
|
|
934
|
+
}
|
|
935
|
+
async unsubscribe() {
|
|
936
|
+
if (this.#closed) return true;
|
|
937
|
+
const result = await this.#unsubscribe();
|
|
938
|
+
if (result) this.#closed = true;
|
|
939
|
+
return result;
|
|
940
|
+
}
|
|
941
|
+
};
|
|
942
|
+
var EthereumSubscriptions = class {
|
|
943
|
+
rpc;
|
|
944
|
+
#transport;
|
|
945
|
+
constructor(transport) {
|
|
946
|
+
this.#transport = transport;
|
|
947
|
+
this.rpc = createRpcNamespace({
|
|
948
|
+
transport,
|
|
949
|
+
parameterMode: "positional"
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
close() {
|
|
953
|
+
this.#transport.close();
|
|
954
|
+
}
|
|
955
|
+
onNotification(listener) {
|
|
956
|
+
return this.#transport.onNotification(listener);
|
|
957
|
+
}
|
|
958
|
+
async subscribe(subscription, listener, ...options) {
|
|
959
|
+
let id;
|
|
960
|
+
const removeListener = this.#transport.onNotification((notification) => {
|
|
961
|
+
const params = notificationParams(notification);
|
|
962
|
+
if (params && params.subscription === id) listener(params.result);
|
|
963
|
+
});
|
|
964
|
+
try {
|
|
965
|
+
id = await this.#transport.request("eth_subscribe", [
|
|
966
|
+
subscription,
|
|
967
|
+
...options
|
|
968
|
+
]);
|
|
969
|
+
} catch (error) {
|
|
970
|
+
removeListener();
|
|
971
|
+
throw error;
|
|
972
|
+
}
|
|
973
|
+
return new RpcSubscription(id, async () => {
|
|
974
|
+
const result = await this.#transport.request("eth_unsubscribe", [
|
|
975
|
+
id
|
|
976
|
+
]);
|
|
977
|
+
if (result) removeListener();
|
|
978
|
+
return result;
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
var SOLANA_ENHANCED_SUBSCRIPTION_METHODS = [
|
|
983
|
+
"accountSubscribe",
|
|
984
|
+
"accountUnsubscribe",
|
|
985
|
+
"transactionSubscribe",
|
|
986
|
+
"transactionUnsubscribe"
|
|
987
|
+
];
|
|
988
|
+
var SolanaSubscriptions = class {
|
|
989
|
+
rpc;
|
|
990
|
+
#transport;
|
|
991
|
+
constructor(transport) {
|
|
992
|
+
this.#transport = transport;
|
|
993
|
+
this.rpc = createRpcNamespace({
|
|
994
|
+
transport,
|
|
995
|
+
parameterMode: "positional"
|
|
996
|
+
});
|
|
997
|
+
}
|
|
998
|
+
close() {
|
|
999
|
+
this.#transport.close();
|
|
1000
|
+
}
|
|
1001
|
+
onNotification(listener) {
|
|
1002
|
+
return this.#transport.onNotification(listener);
|
|
1003
|
+
}
|
|
1004
|
+
accountSubscribe(address, listener, options) {
|
|
1005
|
+
const params = options === void 0 ? [address] : [address, options];
|
|
1006
|
+
return this.#subscribe(
|
|
1007
|
+
"accountSubscribe",
|
|
1008
|
+
params,
|
|
1009
|
+
"accountUnsubscribe",
|
|
1010
|
+
listener
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
transactionSubscribe(filter, listener, options) {
|
|
1014
|
+
const params = options === void 0 ? [filter] : [filter, options];
|
|
1015
|
+
return this.#subscribe(
|
|
1016
|
+
"transactionSubscribe",
|
|
1017
|
+
params,
|
|
1018
|
+
"transactionUnsubscribe",
|
|
1019
|
+
listener
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
async rawSubscribe(subscribeMethod, params, unsubscribeMethod, listener) {
|
|
1023
|
+
return this.#subscribe(
|
|
1024
|
+
subscribeMethod,
|
|
1025
|
+
params,
|
|
1026
|
+
unsubscribeMethod,
|
|
1027
|
+
listener
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
async #subscribe(subscribeMethod, params, unsubscribeMethod, listener) {
|
|
1031
|
+
let id;
|
|
1032
|
+
const removeListener = this.#transport.onNotification((notification) => {
|
|
1033
|
+
const value = notificationParams(notification);
|
|
1034
|
+
if (value && value.subscription === id) listener(value.result);
|
|
1035
|
+
});
|
|
1036
|
+
try {
|
|
1037
|
+
id = await this.#transport.request(subscribeMethod, params);
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
removeListener();
|
|
1040
|
+
throw error;
|
|
1041
|
+
}
|
|
1042
|
+
return new RpcSubscription(id, async () => {
|
|
1043
|
+
const result = await this.#transport.request(unsubscribeMethod, [
|
|
1044
|
+
id
|
|
1045
|
+
]);
|
|
1046
|
+
if (result) removeListener();
|
|
1047
|
+
return result;
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
};
|
|
1051
|
+
|
|
1052
|
+
// src/transport/http.ts
|
|
1053
|
+
var controlledSignal2 = (timeoutMs, external) => {
|
|
1054
|
+
const controller = new AbortController();
|
|
1055
|
+
let didTimeOut = false;
|
|
1056
|
+
const timeout = setTimeout(() => {
|
|
1057
|
+
didTimeOut = true;
|
|
1058
|
+
controller.abort();
|
|
1059
|
+
}, timeoutMs);
|
|
1060
|
+
const abort = () => controller.abort();
|
|
1061
|
+
if (external?.aborted) controller.abort();
|
|
1062
|
+
else external?.addEventListener("abort", abort, { once: true });
|
|
1063
|
+
return {
|
|
1064
|
+
signal: controller.signal,
|
|
1065
|
+
timedOut: () => didTimeOut,
|
|
1066
|
+
cleanup: () => {
|
|
1067
|
+
clearTimeout(timeout);
|
|
1068
|
+
external?.removeEventListener("abort", abort);
|
|
1069
|
+
}
|
|
1070
|
+
};
|
|
1071
|
+
};
|
|
1072
|
+
var isObject2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1073
|
+
var isFailure = (value) => isObject2(value) && isObject2(value.error) && typeof value.error.code === "number" && typeof value.error.message === "string";
|
|
1074
|
+
var parseJson = (text) => {
|
|
1075
|
+
try {
|
|
1076
|
+
return JSON.parse(text);
|
|
1077
|
+
} catch {
|
|
1078
|
+
throw new ErpcInvalidResponseError("ERPC returned malformed JSON");
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
var HttpJsonRpcTransport = class {
|
|
1082
|
+
endpoint;
|
|
1083
|
+
maxBatchSize;
|
|
1084
|
+
#apiKey;
|
|
1085
|
+
#fetch;
|
|
1086
|
+
#headers;
|
|
1087
|
+
#timeoutMs;
|
|
1088
|
+
#nextId = 1;
|
|
1089
|
+
constructor(config) {
|
|
1090
|
+
this.#apiKey = config.apiKey;
|
|
1091
|
+
this.#fetch = config.fetch;
|
|
1092
|
+
this.#headers = config.headers;
|
|
1093
|
+
this.#timeoutMs = config.timeoutMs;
|
|
1094
|
+
this.maxBatchSize = config.maxBatchSize ?? 256;
|
|
1095
|
+
const endpoint = new URL(config.endpoint);
|
|
1096
|
+
endpoint.search = "";
|
|
1097
|
+
endpoint.hash = "";
|
|
1098
|
+
this.endpoint = endpoint.toString();
|
|
1099
|
+
}
|
|
1100
|
+
async request(method, params, options = {}) {
|
|
1101
|
+
const id = this.#id();
|
|
1102
|
+
const request = this.#request(id, method, params);
|
|
1103
|
+
const response = await this.#post(request, options);
|
|
1104
|
+
return this.#unwrap(response, id);
|
|
1105
|
+
}
|
|
1106
|
+
async batch(calls, options = {}) {
|
|
1107
|
+
if (calls.length === 0) return [];
|
|
1108
|
+
if (calls.length > this.maxBatchSize) {
|
|
1109
|
+
throw new ErpcInvalidResponseError(
|
|
1110
|
+
`A batch may contain at most ${this.maxBatchSize} calls`
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
const requests = calls.map((call) => {
|
|
1114
|
+
const id = this.#id();
|
|
1115
|
+
return this.#request(id, call.method, call.params);
|
|
1116
|
+
});
|
|
1117
|
+
const raw = await this.#post(requests, options);
|
|
1118
|
+
if (!Array.isArray(raw)) {
|
|
1119
|
+
if (isFailure(raw)) {
|
|
1120
|
+
throw new ErpcJsonRpcError(redactJsonRpcError(raw.error, this.#apiKey));
|
|
1121
|
+
}
|
|
1122
|
+
throw new ErpcInvalidResponseError("ERPC returned a non-array batch response");
|
|
1123
|
+
}
|
|
1124
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1125
|
+
for (const response of raw) {
|
|
1126
|
+
if (!isObject2(response) || !("id" in response)) {
|
|
1127
|
+
throw new ErpcInvalidResponseError("ERPC returned an invalid batch item");
|
|
1128
|
+
}
|
|
1129
|
+
const id = response.id;
|
|
1130
|
+
if (typeof id !== "number" && typeof id !== "string") {
|
|
1131
|
+
throw new ErpcInvalidResponseError("ERPC returned an invalid batch id");
|
|
1132
|
+
}
|
|
1133
|
+
byId.set(id, response);
|
|
1134
|
+
}
|
|
1135
|
+
const results = requests.map((request) => {
|
|
1136
|
+
const response = byId.get(request.id);
|
|
1137
|
+
if (response === void 0) {
|
|
1138
|
+
throw new ErpcInvalidResponseError(
|
|
1139
|
+
`ERPC omitted batch response id ${String(request.id)}`
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
return this.#unwrap(response, request.id);
|
|
1143
|
+
});
|
|
1144
|
+
return results;
|
|
1145
|
+
}
|
|
1146
|
+
#id() {
|
|
1147
|
+
const id = this.#nextId;
|
|
1148
|
+
this.#nextId = id >= Number.MAX_SAFE_INTEGER ? 1 : id + 1;
|
|
1149
|
+
return id;
|
|
1150
|
+
}
|
|
1151
|
+
#request(id, method, params) {
|
|
1152
|
+
const request = { jsonrpc: "2.0", id, method };
|
|
1153
|
+
if (params !== void 0) request.params = params;
|
|
1154
|
+
return request;
|
|
1155
|
+
}
|
|
1156
|
+
async #post(body, options) {
|
|
1157
|
+
const url = new URL(this.endpoint);
|
|
1158
|
+
url.searchParams.set("api-key", this.#apiKey);
|
|
1159
|
+
const controlled = controlledSignal2(this.#timeoutMs, options.signal);
|
|
1160
|
+
try {
|
|
1161
|
+
const response = await this.#fetch(url, {
|
|
1162
|
+
method: "POST",
|
|
1163
|
+
headers: {
|
|
1164
|
+
...this.#headers,
|
|
1165
|
+
"content-type": "application/json"
|
|
1166
|
+
},
|
|
1167
|
+
body: JSON.stringify(body),
|
|
1168
|
+
signal: controlled.signal
|
|
1169
|
+
});
|
|
1170
|
+
const text = await response.text();
|
|
1171
|
+
if (!response.ok) throw new ErpcHttpError(response.status);
|
|
1172
|
+
return parseJson(text);
|
|
1173
|
+
} catch (error) {
|
|
1174
|
+
if (error instanceof ErpcHttpError || error instanceof ErpcInvalidResponseError) {
|
|
1175
|
+
throw error;
|
|
1176
|
+
}
|
|
1177
|
+
if (controlled.timedOut()) throw new ErpcTimeoutError(this.#timeoutMs);
|
|
1178
|
+
if (options.signal?.aborted) throw new ErpcAbortedError();
|
|
1179
|
+
throw new ErpcTransportError("Unable to reach ERPC");
|
|
1180
|
+
} finally {
|
|
1181
|
+
controlled.cleanup();
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
#unwrap(response, expectedId) {
|
|
1185
|
+
if (!isObject2(response)) throw new ErpcInvalidResponseError();
|
|
1186
|
+
if (response.id !== expectedId) {
|
|
1187
|
+
throw new ErpcInvalidResponseError("ERPC returned an unexpected response id");
|
|
1188
|
+
}
|
|
1189
|
+
if (isFailure(response)) {
|
|
1190
|
+
throw new ErpcJsonRpcError(
|
|
1191
|
+
redactJsonRpcError(response.error, this.#apiKey)
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
if (!("result" in response)) throw new ErpcInvalidResponseError();
|
|
1195
|
+
return response.result;
|
|
1196
|
+
}
|
|
1197
|
+
};
|
|
1198
|
+
|
|
1199
|
+
// src/client.ts
|
|
1200
|
+
var createErpcClient = (config) => {
|
|
1201
|
+
const resolved = resolveConfig(config);
|
|
1202
|
+
const sharedTransport = {
|
|
1203
|
+
apiKey: resolved.apiKey,
|
|
1204
|
+
fetch: resolved.fetch,
|
|
1205
|
+
headers: resolved.headers,
|
|
1206
|
+
timeoutMs: resolved.timeoutMs
|
|
1207
|
+
};
|
|
1208
|
+
const solanaTransport = new HttpJsonRpcTransport({
|
|
1209
|
+
...sharedTransport,
|
|
1210
|
+
endpoint: resolved.endpoint
|
|
1211
|
+
});
|
|
1212
|
+
const ethereumEndpoint = endpointWithPath(resolved.endpoint, "/eth");
|
|
1213
|
+
const ethereumTransport = new HttpJsonRpcTransport({
|
|
1214
|
+
...sharedTransport,
|
|
1215
|
+
endpoint: ethereumEndpoint
|
|
1216
|
+
});
|
|
1217
|
+
const solanaWebSocket = new WebSocketJsonRpcTransport({
|
|
1218
|
+
endpoint: websocketUrl(resolved.endpoint, resolved.apiKey),
|
|
1219
|
+
timeoutMs: resolved.timeoutMs,
|
|
1220
|
+
...resolved.webSocket === void 0 ? {} : { webSocket: resolved.webSocket }
|
|
1221
|
+
});
|
|
1222
|
+
const ethereumWebSocket = new WebSocketJsonRpcTransport({
|
|
1223
|
+
endpoint: websocketUrl(resolved.endpoint, resolved.apiKey, "/eth"),
|
|
1224
|
+
timeoutMs: resolved.timeoutMs,
|
|
1225
|
+
...resolved.webSocket === void 0 ? {} : { webSocket: resolved.webSocket }
|
|
1226
|
+
});
|
|
1227
|
+
const priceTransport = new RestTransport({
|
|
1228
|
+
...sharedTransport,
|
|
1229
|
+
endpoint: resolved.endpoint
|
|
1230
|
+
});
|
|
1231
|
+
const accountTransport = new RestTransport({
|
|
1232
|
+
...sharedTransport,
|
|
1233
|
+
endpoint: resolved.accountEndpoint
|
|
1234
|
+
});
|
|
1235
|
+
const solana = {
|
|
1236
|
+
...createSolanaClient(solanaTransport),
|
|
1237
|
+
subscriptions: new SolanaSubscriptions(solanaWebSocket)
|
|
1238
|
+
};
|
|
1239
|
+
const ethereum = {
|
|
1240
|
+
...createEthereumClient(ethereumTransport),
|
|
1241
|
+
subscriptions: new EthereumSubscriptions(ethereumWebSocket)
|
|
1242
|
+
};
|
|
1243
|
+
return {
|
|
1244
|
+
solana,
|
|
1245
|
+
ethereum,
|
|
1246
|
+
price: new PriceClient(priceTransport),
|
|
1247
|
+
account: new AccountClient(accountTransport),
|
|
1248
|
+
close: () => {
|
|
1249
|
+
solana.subscriptions.close();
|
|
1250
|
+
ethereum.subscriptions.close();
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
export { AccountClient, DEFAULT_ACCOUNT_ENDPOINT, DEFAULT_ENDPOINT, DEFAULT_TIMEOUT_MS, ETHEREUM_RPC_METHODS, ETHEREUM_SUBSCRIPTION_METHODS, ErpcAbortedError, ErpcBatchPolicyError, ErpcConfigError, ErpcError, ErpcHttpError, ErpcInvalidResponseError, ErpcJsonRpcError, ErpcTimeoutError, ErpcTransportError, EthereumSubscriptions, PendingRpcBatchRequest, PendingRpcRequest, PriceClient, RpcSubscription, SOLANA_ANALYTICS_METHODS, SOLANA_DAS_METHODS, SOLANA_ENHANCED_SUBSCRIPTION_METHODS, SOLANA_HISTORY_METHODS, SOLANA_LEADER_METHODS, SOLANA_RPC_METHODS, SolanaSubscriptions, createErpcClient };
|
|
1256
|
+
//# sourceMappingURL=index.js.map
|
|
1257
|
+
//# sourceMappingURL=index.js.map
|