@hardkas/kaspa-rpc 0.5.1-alpha → 0.5.3-alpha

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/dist/index.d.ts CHANGED
@@ -120,7 +120,6 @@ interface RpcHealthResult {
120
120
  readonly circuitState?: string | undefined;
121
121
  readonly stale?: boolean | undefined;
122
122
  }
123
-
124
123
  declare function checkKaspaRpcHealth(options?: RpcHealthCheckOptions): Promise<RpcHealthResult>;
125
124
  declare function waitForKaspaRpcReady(options?: RpcReadinessWaitOptions): Promise<RpcHealthResult>;
126
125
 
@@ -171,6 +170,39 @@ declare class LoadBalancedRpcProvider implements KaspaRpcClient {
171
170
  private withFailover;
172
171
  }
173
172
 
173
+ interface WrpcRequest {
174
+ id: number;
175
+ method: string;
176
+ params?: Record<string, unknown>;
177
+ }
178
+ interface WrpcResponse {
179
+ id?: number;
180
+ result?: unknown;
181
+ params?: unknown;
182
+ error?: {
183
+ message: string;
184
+ code?: number;
185
+ };
186
+ }
187
+ declare class KaspaWrpcClient {
188
+ private url;
189
+ private ws;
190
+ private requestId;
191
+ private pending;
192
+ onNotification?: (msg: WrpcResponse) => void;
193
+ debug: boolean;
194
+ constructor(url: string);
195
+ getUrl(): string;
196
+ connect(timeoutMs?: number): Promise<void>;
197
+ request(method: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<unknown>;
198
+ getServerInfo(): Promise<unknown>;
199
+ getBlockDagInfo(): Promise<unknown>;
200
+ getVirtualSelectedParentBlueScore(): Promise<unknown>;
201
+ getUtxosByAddresses(addresses: string[]): Promise<unknown>;
202
+ ping(): Promise<boolean>;
203
+ disconnect(): void;
204
+ }
205
+
174
206
  interface KaspaNodeInfo {
175
207
  serverVersion?: string | undefined;
176
208
  isSynced?: boolean | undefined;
@@ -290,4 +322,4 @@ declare class MockKaspaRpcClient implements KaspaRpcClient {
290
322
  close(): Promise<void>;
291
323
  }
292
324
 
293
- export { type BlockDagInfo, type CircuitBreakerOptions, CircuitState, JsonWrpcKaspaClient, type JsonWrpcKaspaClientOptions, type KaspaAddressBalance, KaspaJsonRpcClient, type KaspaNodeInfo, type KaspaRpcClient, type KaspaRpcHealth, type KaspaRpcOutpoint, type KaspaRpcUtxo, type KaspaSubmitTransactionResult, LoadBalancedRpcProvider, type LoadBalancerOptions, type MempoolEntry, MockKaspaRpcClient, type ResilienceReport, type RetryOptions, RpcCircuitOpenError, type RpcClientOptions, type RpcConfidence, RpcError, type RpcHealthCheckOptions, type RpcHealthResult, type RpcHealthState, RpcRateLimitError, type RpcReadinessWaitOptions, RpcTimeoutError, type RpcTrace, RpcUnavailableError, RpcValidationError, type ServerInfo, calculateConfidence, checkKaspaRpcHealth, classifyRpcError, mapKaspaAddressBalance, mapKaspaNodeInfo, mapKaspaRpcUtxos, mapKaspaSubmitTransactionResult, waitForKaspaRpcReady };
325
+ export { type BlockDagInfo, type CircuitBreakerOptions, CircuitState, JsonWrpcKaspaClient, type JsonWrpcKaspaClientOptions, type KaspaAddressBalance, KaspaJsonRpcClient, type KaspaNodeInfo, type KaspaRpcClient, type KaspaRpcHealth, type KaspaRpcOutpoint, type KaspaRpcUtxo, type KaspaSubmitTransactionResult, KaspaWrpcClient, LoadBalancedRpcProvider, type LoadBalancerOptions, type MempoolEntry, MockKaspaRpcClient, type ResilienceReport, type RetryOptions, RpcCircuitOpenError, type RpcClientOptions, type RpcConfidence, RpcError, type RpcHealthCheckOptions, type RpcHealthResult, type RpcHealthState, RpcRateLimitError, type RpcReadinessWaitOptions, RpcTimeoutError, type RpcTrace, RpcUnavailableError, RpcValidationError, type ServerInfo, type WrpcRequest, type WrpcResponse, calculateConfidence, checkKaspaRpcHealth, classifyRpcError, mapKaspaAddressBalance, mapKaspaNodeInfo, mapKaspaRpcUtxos, mapKaspaSubmitTransactionResult, waitForKaspaRpcReady };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/index.ts
2
- import { WebSocket } from "ws";
2
+ import { WebSocket as WebSocket2 } from "ws";
3
3
 
4
4
  // src/errors.ts
5
5
  var RpcError = class extends Error {
@@ -438,11 +438,180 @@ var KaspaJsonRpcClient = class {
438
438
  }
439
439
  };
440
440
 
441
+ // src/wrpc-client.ts
442
+ import WebSocket from "ws";
443
+ var KaspaWrpcClient = class {
444
+ url;
445
+ ws = null;
446
+ requestId = 0;
447
+ pending = /* @__PURE__ */ new Map();
448
+ onNotification;
449
+ debug = false;
450
+ constructor(url) {
451
+ if (url.startsWith("http://")) {
452
+ this.url = url.replace("http://", "ws://");
453
+ } else if (url.startsWith("https://")) {
454
+ this.url = url.replace("https://", "wss://");
455
+ } else if (!url.startsWith("ws://") && !url.startsWith("wss://")) {
456
+ this.url = `ws://${url}`;
457
+ } else {
458
+ this.url = url;
459
+ }
460
+ }
461
+ getUrl() {
462
+ return this.url;
463
+ }
464
+ async connect(timeoutMs = 5e3) {
465
+ return new Promise((resolve, reject) => {
466
+ const timer = setTimeout(() => {
467
+ this.disconnect();
468
+ reject(new Error(`WebSocket connection timeout after ${timeoutMs}ms to ${this.url}`));
469
+ }, timeoutMs);
470
+ try {
471
+ this.ws = new WebSocket(this.url);
472
+ } catch (err) {
473
+ clearTimeout(timer);
474
+ reject(new Error(`Failed to create WebSocket to ${this.url}: ${err}`));
475
+ return;
476
+ }
477
+ this.ws.on("open", () => {
478
+ clearTimeout(timer);
479
+ resolve();
480
+ });
481
+ this.ws.on("error", (err) => {
482
+ clearTimeout(timer);
483
+ reject(new Error(`WebSocket error: ${err.message}`));
484
+ });
485
+ this.ws.on("message", (data) => {
486
+ try {
487
+ const response = JSON.parse(data.toString());
488
+ if (response.id !== void 0) {
489
+ const pending = this.pending.get(response.id);
490
+ if (pending) {
491
+ clearTimeout(pending.timer);
492
+ this.pending.delete(response.id);
493
+ pending.resolve(response);
494
+ }
495
+ } else {
496
+ if (this.onNotification) {
497
+ this.onNotification(response);
498
+ }
499
+ }
500
+ } catch (parseErr) {
501
+ if (this.debug) {
502
+ console.debug("[wRPC] Non-JSON message received:", data.toString().slice(0, 200));
503
+ }
504
+ }
505
+ });
506
+ this.ws.on("close", () => {
507
+ for (const [, pending] of this.pending) {
508
+ clearTimeout(pending.timer);
509
+ pending.reject(new Error("WebSocket closed"));
510
+ }
511
+ this.pending.clear();
512
+ });
513
+ });
514
+ }
515
+ async request(method, params, timeoutMs = 5e3) {
516
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
517
+ throw new Error("WebSocket not connected. Call connect() first.");
518
+ }
519
+ const id = ++this.requestId;
520
+ return new Promise((resolve, reject) => {
521
+ const timer = setTimeout(() => {
522
+ this.pending.delete(id);
523
+ reject(new Error(`wRPC timeout: ${method} (${timeoutMs}ms)`));
524
+ }, timeoutMs);
525
+ this.pending.set(id, {
526
+ resolve: (response) => {
527
+ if (response.error) {
528
+ const errMsg = response.error.message || `wRPC error code ${response.error.code || "unknown"}`;
529
+ reject(new Error(errMsg));
530
+ } else {
531
+ resolve(response.result !== void 0 ? response.result : response.params);
532
+ }
533
+ },
534
+ reject,
535
+ timer
536
+ });
537
+ this.ws.send(JSON.stringify({ id, method, params: params || {} }));
538
+ });
539
+ }
540
+ async getServerInfo() {
541
+ return this.request("getServerInfo");
542
+ }
543
+ async getBlockDagInfo() {
544
+ return this.request("getBlockDagInfo");
545
+ }
546
+ async getVirtualSelectedParentBlueScore() {
547
+ return this.request("getVirtualSelectedParentBlueScore");
548
+ }
549
+ async getUtxosByAddresses(addresses) {
550
+ return this.request("getUtxosByAddresses", { addresses });
551
+ }
552
+ async ping() {
553
+ try {
554
+ await this.getServerInfo();
555
+ return true;
556
+ } catch {
557
+ return false;
558
+ }
559
+ }
560
+ disconnect() {
561
+ if (this.ws) {
562
+ try {
563
+ this.ws.close();
564
+ } catch {
565
+ }
566
+ this.ws = null;
567
+ }
568
+ for (const [, pending] of this.pending) {
569
+ clearTimeout(pending.timer);
570
+ }
571
+ this.pending.clear();
572
+ }
573
+ };
574
+
441
575
  // src/health.ts
442
576
  async function checkKaspaRpcHealth(options) {
443
- const url = options?.url || "http://127.0.0.1:18210";
444
- const client = new KaspaJsonRpcClient({ url, timeoutMs: options?.timeoutMs });
577
+ const url = options?.url || "ws://127.0.0.1:18210";
445
578
  const checkedAt = (/* @__PURE__ */ new Date()).toISOString();
579
+ const timeoutMs = options?.timeoutMs || 2e3;
580
+ if (url.startsWith("ws://") || url.startsWith("wss://") || url.includes("18210") || url.includes("18110")) {
581
+ const client2 = new KaspaWrpcClient(url);
582
+ const start = Date.now();
583
+ try {
584
+ await client2.connect(timeoutMs);
585
+ const info = await client2.getServerInfo();
586
+ const dagInfo = await client2.getBlockDagInfo();
587
+ const latencyMs = Date.now() - start;
588
+ client2.disconnect();
589
+ return {
590
+ endpoint: url,
591
+ status: "healthy",
592
+ ready: true,
593
+ checkedAt,
594
+ latencyMs,
595
+ networkId: dagInfo?.networkId || "simnet",
596
+ virtualDaaScore: dagInfo?.virtualDaaScore?.toString() || "0",
597
+ serverVersion: info?.serverVersion || "unknown",
598
+ isSynced: info?.isSynced ?? true,
599
+ lastError: null,
600
+ stale: !(info?.isSynced ?? true)
601
+ };
602
+ } catch (e) {
603
+ client2.disconnect();
604
+ return {
605
+ endpoint: url,
606
+ status: "unreachable",
607
+ ready: false,
608
+ checkedAt,
609
+ error: e.message,
610
+ lastError: e.message
611
+ };
612
+ }
613
+ }
614
+ const client = new KaspaJsonRpcClient({ url, timeoutMs });
446
615
  try {
447
616
  const health = await client.healthCheck();
448
617
  return {
@@ -487,7 +656,7 @@ async function waitForKaspaRpcReady(options) {
487
656
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
488
657
  }
489
658
  return lastResult || {
490
- endpoint: options?.url || "http://127.0.0.1:18210",
659
+ endpoint: options?.url || "ws://127.0.0.1:18210",
491
660
  status: "unreachable",
492
661
  ready: false,
493
662
  checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -766,11 +935,11 @@ var JsonWrpcKaspaClient = class {
766
935
  });
767
936
  }
768
937
  async connect() {
769
- if (this.socket && this.socket.readyState === WebSocket.OPEN) {
938
+ if (this.socket && this.socket.readyState === WebSocket2.OPEN) {
770
939
  return this.socket;
771
940
  }
772
941
  return new Promise((resolve, reject) => {
773
- const ws = new WebSocket(this.rpcUrl);
942
+ const ws = new WebSocket2(this.rpcUrl);
774
943
  const timeout = setTimeout(() => {
775
944
  ws.close();
776
945
  reject(new Error(`Cannot connect to Kaspa RPC at ${this.rpcUrl}. Connection timed out.`));
@@ -916,6 +1085,7 @@ export {
916
1085
  CircuitState,
917
1086
  JsonWrpcKaspaClient,
918
1087
  KaspaJsonRpcClient,
1088
+ KaspaWrpcClient,
919
1089
  LoadBalancedRpcProvider,
920
1090
  MockKaspaRpcClient,
921
1091
  RpcCircuitOpenError,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hardkas/kaspa-rpc",
3
- "version": "0.5.1-alpha",
3
+ "version": "0.5.3-alpha",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -9,8 +9,8 @@
9
9
  },
10
10
  "dependencies": {
11
11
  "ws": "^8.18.0",
12
- "@hardkas/core": "0.5.1-alpha",
13
- "@hardkas/tx-builder": "0.5.1-alpha"
12
+ "@hardkas/core": "0.5.3-alpha",
13
+ "@hardkas/tx-builder": "0.5.3-alpha"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/ws": "^8.5.13",