@lerna-labs/hydra-sdk 1.0.0-beta.20 → 1.0.0-beta.21

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.
@@ -23,7 +23,7 @@ export class HydraHttpClient {
23
23
  * `POST /decommit` — submits the decommit request to the Hydra node.
24
24
  */
25
25
  async publishDecommit(transaction) {
26
- return this.post('/decommit', { tag: 'Decommit', transaction });
26
+ return this.post('/decommit', transaction);
27
27
  }
28
28
  /** Fetch the current UTxO snapshot. `GET /snapshot/utxo` */
29
29
  async getSnapshotUtxo() {
@@ -0,0 +1,74 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { HydraWebSocket } from './hydra-websocket.js';
3
+ import type { GreetingsMessage, HeadStatus, HydraMessage, HydraMonitorOptions, HydraStatus, ServerOutput, TimestampedEvent } from './types.js';
4
+ /**
5
+ * Persistent WebSocket monitor for a Hydra head.
6
+ *
7
+ * Maintains a single long-lived WebSocket connection with auto-reconnect,
8
+ * real-time head state tracking, and proactive error surfacing.
9
+ *
10
+ * Events emitted:
11
+ * - `'message'` — `(msg: HydraWsMessage)` for every incoming message
12
+ * - `'status'` — `(status: HydraStatus, previous: HydraStatus)` on head-status changes
13
+ * - `'error:tx'` — `(msg)` on PostTxOnChainFailed or TxInvalid
14
+ * - `'error:command'` — `(msg)` on CommandFailed
15
+ * - `'error:decommit'` — `(msg)` on DecommitInvalid
16
+ * - `'connected'` — `()` WebSocket open + Greetings received
17
+ * - `'disconnected'` — `()` WebSocket closed (reconnect may follow)
18
+ * - `'reconnecting'` — `(attempt: number, delayMs: number)`
19
+ * - `'reconnect_failed'` — `()` maxAttempts exhausted
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * const monitor = new HydraMonitor({ wsUrl: 'ws://hydra-node:4102' });
24
+ * await monitor.start();
25
+ * console.log(monitor.headStatus); // 'IDLE'
26
+ * monitor.on('status', (s, prev) => console.log(`${prev} → ${s}`));
27
+ * ```
28
+ */
29
+ export declare class HydraMonitor extends EventEmitter {
30
+ readonly ws: HydraWebSocket;
31
+ private _headStatus;
32
+ private _previousStatus;
33
+ private _events;
34
+ private _stopped;
35
+ private _reconnecting;
36
+ private readonly reconnectEnabled;
37
+ private readonly baseDelayMs;
38
+ private readonly maxDelayMs;
39
+ private readonly maxAttempts;
40
+ private readonly eventBufferSize;
41
+ private readonly boundOnMessage;
42
+ private readonly boundOnClose;
43
+ constructor(options: HydraMonitorOptions);
44
+ /** Connect to the Hydra node. Resolves after Greetings received. */
45
+ start(): Promise<void>;
46
+ /** Disconnect and stop reconnecting. */
47
+ stop(): Promise<void>;
48
+ /** Whether the monitor is actively connected and listening. */
49
+ get connected(): boolean;
50
+ /** Current head status (uppercase). */
51
+ get headStatus(): HydraStatus;
52
+ /** Current head status (mixed-case, as reported in Greetings). */
53
+ get headStatusMixed(): HeadStatus;
54
+ /** The full Greetings message from the most recent connection. */
55
+ get greetings(): GreetingsMessage | null;
56
+ /** The last N events (configurable via eventBufferSize). Most recent last. */
57
+ get recentEvents(): readonly TimestampedEvent[];
58
+ /**
59
+ * Wait for headStatus to reach the target. Resolves immediately if already there.
60
+ * @param target - The HydraStatus to wait for.
61
+ * @param timeoutMs - Maximum wait time (default 60s).
62
+ */
63
+ waitForStatus(target: HydraStatus, timeoutMs?: number): Promise<void>;
64
+ /**
65
+ * Wait for the next message matching the given tag.
66
+ * @param tag - The message tag to wait for.
67
+ * @param timeoutMs - Maximum wait time (default 60s).
68
+ */
69
+ waitForMessage<T extends ServerOutput['tag']>(tag: T, timeoutMs?: number): Promise<HydraMessage<T>>;
70
+ private onMessage;
71
+ private updateStatus;
72
+ private onClose;
73
+ private reconnectLoop;
74
+ }
@@ -0,0 +1,231 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { HEAD_STATUS_TO_HYDRA, HydraWebSocket, TAG_TO_HYDRA } from './hydra-websocket.js';
3
+ const HYDRA_TO_HEAD_STATUS = {
4
+ IDLE: 'Idle',
5
+ INITIALIZING: 'Initializing',
6
+ OPEN: 'Open',
7
+ CLOSED: 'Closed',
8
+ FANOUT_POSSIBLE: 'FanoutPossible',
9
+ FINAL: 'Final',
10
+ };
11
+ /**
12
+ * Persistent WebSocket monitor for a Hydra head.
13
+ *
14
+ * Maintains a single long-lived WebSocket connection with auto-reconnect,
15
+ * real-time head state tracking, and proactive error surfacing.
16
+ *
17
+ * Events emitted:
18
+ * - `'message'` — `(msg: HydraWsMessage)` for every incoming message
19
+ * - `'status'` — `(status: HydraStatus, previous: HydraStatus)` on head-status changes
20
+ * - `'error:tx'` — `(msg)` on PostTxOnChainFailed or TxInvalid
21
+ * - `'error:command'` — `(msg)` on CommandFailed
22
+ * - `'error:decommit'` — `(msg)` on DecommitInvalid
23
+ * - `'connected'` — `()` WebSocket open + Greetings received
24
+ * - `'disconnected'` — `()` WebSocket closed (reconnect may follow)
25
+ * - `'reconnecting'` — `(attempt: number, delayMs: number)`
26
+ * - `'reconnect_failed'` — `()` maxAttempts exhausted
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const monitor = new HydraMonitor({ wsUrl: 'ws://hydra-node:4102' });
31
+ * await monitor.start();
32
+ * console.log(monitor.headStatus); // 'IDLE'
33
+ * monitor.on('status', (s, prev) => console.log(`${prev} → ${s}`));
34
+ * ```
35
+ */
36
+ export class HydraMonitor extends EventEmitter {
37
+ ws;
38
+ _headStatus = 'IDLE';
39
+ _previousStatus = 'IDLE';
40
+ _events = [];
41
+ _stopped = true;
42
+ _reconnecting = false;
43
+ reconnectEnabled;
44
+ baseDelayMs;
45
+ maxDelayMs;
46
+ maxAttempts;
47
+ eventBufferSize;
48
+ boundOnMessage = (msg) => this.onMessage(msg);
49
+ boundOnClose = () => this.onClose();
50
+ constructor(options) {
51
+ super();
52
+ this.ws = new HydraWebSocket(options.wsUrl);
53
+ this.reconnectEnabled = options.reconnect?.enabled ?? true;
54
+ this.baseDelayMs = options.reconnect?.baseDelayMs ?? 1000;
55
+ this.maxDelayMs = options.reconnect?.maxDelayMs ?? 30_000;
56
+ this.maxAttempts = options.reconnect?.maxAttempts ?? Number.POSITIVE_INFINITY;
57
+ this.eventBufferSize = options.eventBufferSize ?? 100;
58
+ }
59
+ /** Connect to the Hydra node. Resolves after Greetings received. */
60
+ async start() {
61
+ this._stopped = false;
62
+ await this.ws.waitForGreetings();
63
+ this.ws.on('message', this.boundOnMessage);
64
+ this.ws.on('close', this.boundOnClose);
65
+ // Process the Greetings that was received during waitForGreetings
66
+ if (this.ws.lastGreetings) {
67
+ this.onMessage(this.ws.lastGreetings);
68
+ }
69
+ this.emit('connected');
70
+ }
71
+ /** Disconnect and stop reconnecting. */
72
+ async stop() {
73
+ this._stopped = true;
74
+ this.ws.removeListener('message', this.boundOnMessage);
75
+ this.ws.removeListener('close', this.boundOnClose);
76
+ await this.ws.disconnect();
77
+ this.emit('disconnected');
78
+ }
79
+ /** Whether the monitor is actively connected and listening. */
80
+ get connected() {
81
+ return this.ws.connectionState === 'CONNECTED';
82
+ }
83
+ /** Current head status (uppercase). */
84
+ get headStatus() {
85
+ return this._headStatus;
86
+ }
87
+ /** Current head status (mixed-case, as reported in Greetings). */
88
+ get headStatusMixed() {
89
+ return HYDRA_TO_HEAD_STATUS[this._headStatus];
90
+ }
91
+ /** The full Greetings message from the most recent connection. */
92
+ get greetings() {
93
+ return this.ws.lastGreetings;
94
+ }
95
+ /** The last N events (configurable via eventBufferSize). Most recent last. */
96
+ get recentEvents() {
97
+ return this._events;
98
+ }
99
+ /**
100
+ * Wait for headStatus to reach the target. Resolves immediately if already there.
101
+ * @param target - The HydraStatus to wait for.
102
+ * @param timeoutMs - Maximum wait time (default 60s).
103
+ */
104
+ waitForStatus(target, timeoutMs = 60_000) {
105
+ if (this._headStatus === target)
106
+ return Promise.resolve();
107
+ return new Promise((resolve, reject) => {
108
+ const timer = setTimeout(() => {
109
+ this.removeListener('status', onStatus);
110
+ reject(new Error(`Timeout waiting for status "${target}" (current: "${this._headStatus}")`));
111
+ }, timeoutMs);
112
+ const onStatus = (status) => {
113
+ if (status === target) {
114
+ clearTimeout(timer);
115
+ this.removeListener('status', onStatus);
116
+ resolve();
117
+ }
118
+ };
119
+ this.on('status', onStatus);
120
+ });
121
+ }
122
+ /**
123
+ * Wait for the next message matching the given tag.
124
+ * @param tag - The message tag to wait for.
125
+ * @param timeoutMs - Maximum wait time (default 60s).
126
+ */
127
+ waitForMessage(tag, timeoutMs = 60_000) {
128
+ return new Promise((resolve, reject) => {
129
+ const timer = setTimeout(() => {
130
+ this.removeListener('message', onMsg);
131
+ reject(new Error(`Timeout waiting for message "${tag}"`));
132
+ }, timeoutMs);
133
+ const onMsg = (msg) => {
134
+ if (msg.tag === tag) {
135
+ clearTimeout(timer);
136
+ this.removeListener('message', onMsg);
137
+ resolve(msg);
138
+ }
139
+ };
140
+ this.on('message', onMsg);
141
+ });
142
+ }
143
+ onMessage(msg) {
144
+ // Ring buffer
145
+ this._events.push({ timestamp: Date.now(), message: msg });
146
+ if (this._events.length > this.eventBufferSize) {
147
+ this._events.shift();
148
+ }
149
+ // Forward to listeners
150
+ this.emit('message', msg);
151
+ // Status tracking
152
+ if (msg.tag === 'Greetings' && 'headStatus' in msg) {
153
+ const mapped = HEAD_STATUS_TO_HYDRA[msg.headStatus];
154
+ if (mapped)
155
+ this.updateStatus(mapped);
156
+ }
157
+ else if (msg.tag === 'HeadIsAborted') {
158
+ this.updateStatus('IDLE');
159
+ }
160
+ else {
161
+ const mapped = TAG_TO_HYDRA[msg.tag];
162
+ if (mapped)
163
+ this.updateStatus(mapped);
164
+ }
165
+ // Error routing
166
+ if (msg.tag === 'PostTxOnChainFailed' || msg.tag === 'TxInvalid') {
167
+ this.emit('error:tx', msg);
168
+ }
169
+ else if (msg.tag === 'CommandFailed') {
170
+ this.emit('error:command', msg);
171
+ }
172
+ else if (msg.tag === 'DecommitInvalid') {
173
+ this.emit('error:decommit', msg);
174
+ }
175
+ }
176
+ updateStatus(newStatus) {
177
+ if (this._headStatus !== newStatus) {
178
+ this._previousStatus = this._headStatus;
179
+ this._headStatus = newStatus;
180
+ this.emit('status', newStatus, this._previousStatus);
181
+ }
182
+ }
183
+ onClose() {
184
+ if (this._stopped)
185
+ return;
186
+ this.ws.removeListener('message', this.boundOnMessage);
187
+ this.ws.removeListener('close', this.boundOnClose);
188
+ if (this.reconnectEnabled) {
189
+ this.reconnectLoop();
190
+ }
191
+ else {
192
+ this.emit('disconnected');
193
+ }
194
+ }
195
+ async reconnectLoop() {
196
+ if (this._reconnecting)
197
+ return;
198
+ this._reconnecting = true;
199
+ this.emit('disconnected');
200
+ for (let attempt = 0; attempt < this.maxAttempts; attempt++) {
201
+ if (this._stopped) {
202
+ this._reconnecting = false;
203
+ return;
204
+ }
205
+ const delay = Math.min(this.baseDelayMs * 2 ** attempt, this.maxDelayMs);
206
+ this.emit('reconnecting', attempt + 1, delay);
207
+ await new Promise((r) => setTimeout(r, delay));
208
+ if (this._stopped) {
209
+ this._reconnecting = false;
210
+ return;
211
+ }
212
+ try {
213
+ await this.ws.waitForGreetings();
214
+ this.ws.on('message', this.boundOnMessage);
215
+ this.ws.on('close', this.boundOnClose);
216
+ // Process the Greetings from the new connection
217
+ if (this.ws.lastGreetings) {
218
+ this.onMessage(this.ws.lastGreetings);
219
+ }
220
+ this._reconnecting = false;
221
+ this.emit('connected');
222
+ return;
223
+ }
224
+ catch {
225
+ // Retry on next iteration
226
+ }
227
+ }
228
+ this._reconnecting = false;
229
+ this.emit('reconnect_failed');
230
+ }
231
+ }
@@ -1,5 +1,7 @@
1
1
  import { EventEmitter } from 'node:events';
2
- import type { ClientInput, ConnectionState, HydraStatus, HydraWsMessage } from './types.js';
2
+ import type { ClientInput, ConnectionState, HeadStatus, HydraStatus, HydraWsMessage } from './types.js';
3
+ export declare const HEAD_STATUS_TO_HYDRA: Record<HeadStatus, HydraStatus>;
4
+ export declare const TAG_TO_HYDRA: Record<string, HydraStatus>;
3
5
  /**
4
6
  * Thin WebSocket wrapper for the Hydra node.
5
7
  *
@@ -1,6 +1,6 @@
1
1
  import { EventEmitter } from 'node:events';
2
2
  import WebSocket from 'ws';
3
- const HEAD_STATUS_TO_HYDRA = {
3
+ export const HEAD_STATUS_TO_HYDRA = {
4
4
  Idle: 'IDLE',
5
5
  Initializing: 'INITIALIZING',
6
6
  Open: 'OPEN',
@@ -8,7 +8,7 @@ const HEAD_STATUS_TO_HYDRA = {
8
8
  FanoutPossible: 'FANOUT_POSSIBLE',
9
9
  Final: 'FINAL',
10
10
  };
11
- const TAG_TO_HYDRA = {
11
+ export const TAG_TO_HYDRA = {
12
12
  HeadIsInitializing: 'INITIALIZING',
13
13
  HeadIsOpen: 'OPEN',
14
14
  HeadIsClosed: 'CLOSED',
@@ -11,4 +11,4 @@
11
11
  * }
12
12
  * ```
13
13
  */
14
- export type { ClientInput, ClientMessage, ConnectionState, HeadStatus, HydraMessage, HydraStatus, HydraTransaction, HydraWsMessage, hydraStatus, hydraTransaction, ServerOutput, } from './types.js';
14
+ export type { ClientInput, ClientMessage, ConnectionState, HeadStatus, HydraMessage, HydraMonitorOptions, HydraStatus, HydraTransaction, HydraWsMessage, hydraStatus, hydraTransaction, ServerOutput, TimestampedEvent, } from './types.js';
@@ -194,3 +194,28 @@ export type HydraWsMessage = ServerOutput | ClientMessage;
194
194
  export type HydraMessage<T extends ServerOutput['tag']> = Extract<ServerOutput, {
195
195
  tag: T;
196
196
  }>;
197
+ /** Configuration options for HydraMonitor. */
198
+ export interface HydraMonitorOptions {
199
+ /** Hydra node WebSocket URL. */
200
+ wsUrl: string;
201
+ /** Auto-reconnect configuration. */
202
+ reconnect?: {
203
+ /** Whether to reconnect on unexpected close. Default: `true`. */
204
+ enabled?: boolean;
205
+ /** Initial retry delay in milliseconds. Default: `1000`. */
206
+ baseDelayMs?: number;
207
+ /** Maximum retry delay in milliseconds. Default: `30000`. */
208
+ maxDelayMs?: number;
209
+ /** Maximum reconnect attempts. Default: `Infinity` (keep trying forever). */
210
+ maxAttempts?: number;
211
+ };
212
+ /** Number of recent events to retain in the ring buffer. Default: `100`. */
213
+ eventBufferSize?: number;
214
+ }
215
+ /** A timestamped Hydra WebSocket event for the recent events buffer. */
216
+ export interface TimestampedEvent {
217
+ /** Unix timestamp (ms) when the message was received. */
218
+ timestamp: number;
219
+ /** The raw Hydra message. */
220
+ message: HydraWsMessage;
221
+ }
@@ -7,19 +7,29 @@ export interface ParsedUtxo {
7
7
  unit: string;
8
8
  quantity: string;
9
9
  }[];
10
+ datum?: string | null;
11
+ datumHash?: string | null;
12
+ inlineDatum?: unknown | null;
13
+ referenceScript?: unknown | null;
14
+ }
15
+ /** Options for UTxO query functions. */
16
+ export interface UtxoQueryOptions {
17
+ /** Include datum, datumHash, inlineDatum, and referenceScript fields. Defaults to `false`. */
18
+ includeDatums?: boolean;
10
19
  }
11
20
  /**
12
21
  * Fetch the full UTxO set from the Hydra head snapshot.
13
22
  *
14
23
  * Reads `HYDRA_API_URL` from the environment.
15
24
  *
25
+ * @param options - Query options. Set `includeDatums: true` to include datum/script fields.
16
26
  * @returns All UTxOs currently held in the Hydra head.
17
27
  */
18
- export declare function getUtxoSet(): Promise<ParsedUtxo[]>;
28
+ export declare function getUtxoSet(options?: UtxoQueryOptions): Promise<ParsedUtxo[]>;
19
29
  /**
20
30
  * Fetch UTxOs belonging to a specific address from the Hydra head snapshot.
21
31
  *
22
32
  * @param address - Bech32 address to filter by.
23
33
  * @returns UTxOs matching the given address.
24
34
  */
25
- export declare function queryUtxoByAddress(address: string): Promise<ParsedUtxo[]>;
35
+ export declare function queryUtxoByAddress(address: string, options?: UtxoQueryOptions): Promise<ParsedUtxo[]>;
@@ -4,9 +4,10 @@ import { requireEnv } from '../config.js';
4
4
  *
5
5
  * Reads `HYDRA_API_URL` from the environment.
6
6
  *
7
+ * @param options - Query options. Set `includeDatums: true` to include datum/script fields.
7
8
  * @returns All UTxOs currently held in the Hydra head.
8
9
  */
9
- export async function getUtxoSet() {
10
+ export async function getUtxoSet(options) {
10
11
  const baseUrl = requireEnv('HYDRA_API_URL');
11
12
  const url = `${baseUrl}/snapshot/utxo`;
12
13
  try {
@@ -23,12 +24,14 @@ export async function getUtxoSet() {
23
24
  unit,
24
25
  quantity,
25
26
  }));
26
- UtxoSet.push({
27
- tx_hash,
28
- output_index,
29
- address: utxo.address,
30
- amount,
31
- });
27
+ const parsed = { tx_hash, output_index, address: utxo.address, amount };
28
+ if (options?.includeDatums) {
29
+ parsed.datum = utxo.datum ?? null;
30
+ parsed.datumHash = utxo.datumHash ?? null;
31
+ parsed.inlineDatum = utxo.inlineDatum ?? null;
32
+ parsed.referenceScript = utxo.referenceScript ?? null;
33
+ }
34
+ UtxoSet.push(parsed);
32
35
  }
33
36
  return UtxoSet;
34
37
  }
@@ -43,9 +46,9 @@ export async function getUtxoSet() {
43
46
  * @param address - Bech32 address to filter by.
44
47
  * @returns UTxOs matching the given address.
45
48
  */
46
- export async function queryUtxoByAddress(address) {
49
+ export async function queryUtxoByAddress(address, options) {
47
50
  const result = [];
48
- const data = await getUtxoSet();
51
+ const data = await getUtxoSet(options);
49
52
  for (const utxo of data) {
50
53
  if (utxo.address === address) {
51
54
  result.push(utxo);
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export type { DiskCache, DiskCacheConfig } from './cache/disk-cache.js';
2
2
  export { createDiskCache } from './cache/disk-cache.js';
3
3
  export { optionalEnv, requireEnv } from './config.js';
4
- export type { HeadStatus, HydraMessage, HydraStatus, HydraTransaction, HydraWsMessage, hydraStatus, hydraTransaction, ServerOutput, } from './hydra/messages.js';
5
- export type { ParsedUtxo } from './hydra/utxo.js';
4
+ export { HydraMonitor } from './hydra/hydra-monitor.js';
5
+ export type { HeadStatus, HydraMessage, HydraMonitorOptions, HydraStatus, HydraTransaction, HydraWsMessage, hydraStatus, hydraTransaction, ServerOutput, TimestampedEvent, } from './hydra/messages.js';
6
+ export type { ParsedUtxo, UtxoQueryOptions } from './hydra/utxo.js';
6
7
  export { getUtxoSet, queryUtxoByAddress } from './hydra/utxo.js';
7
8
  export type { IpfsClient, IpfsConfig, PinResult } from './ipfs/ipfs.js';
8
9
  export { createIpfsClient } from './ipfs/ipfs.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { createDiskCache } from './cache/disk-cache.js';
2
2
  export { optionalEnv, requireEnv } from './config.js';
3
+ export { HydraMonitor } from './hydra/hydra-monitor.js';
3
4
  export { getUtxoSet, queryUtxoByAddress } from './hydra/utxo.js';
4
5
  export { createIpfsClient } from './ipfs/ipfs.js';
5
6
  export { getAdmin } from './mesh/get-admin.js';
@@ -1,4 +1,5 @@
1
1
  import { HydraHttpClient } from './hydra/hydra-http-client.js';
2
+ import type { HydraMonitor } from './hydra/hydra-monitor.js';
2
3
  import { HydraWebSocket } from './hydra/hydra-websocket.js';
3
4
  import type { HeadStatus, HydraStatus, HydraTransaction } from './hydra/types.js';
4
5
  /** UTxO reference for committing funds into a Hydra head. */
@@ -35,7 +36,8 @@ export declare class Wrangler {
35
36
  readonly ws: HydraWebSocket;
36
37
  readonly http: HydraHttpClient;
37
38
  private readonly blockfrost;
38
- constructor(url?: string, wsUrl?: string);
39
+ private readonly monitor;
40
+ constructor(url?: string, wsUrl?: string, monitor?: HydraMonitor);
39
41
  /**
40
42
  * Connect to the Hydra node with exponential-backoff retry.
41
43
  *
package/dist/wrangler.js CHANGED
@@ -3,6 +3,14 @@ import { requireEnv } from './config.js';
3
3
  import { HydraHttpClient } from './hydra/hydra-http-client.js';
4
4
  import { HydraWebSocket } from './hydra/hydra-websocket.js';
5
5
  import { toHydraUTxO, toHydraUTxOs } from './hydra/utxo-conversion.js';
6
+ const HYDRA_TO_HEAD_STATUS = {
7
+ IDLE: 'Idle',
8
+ INITIALIZING: 'Initializing',
9
+ OPEN: 'Open',
10
+ CLOSED: 'Closed',
11
+ FANOUT_POSSIBLE: 'FanoutPossible',
12
+ FINAL: 'Final',
13
+ };
6
14
  /**
7
15
  * High-level controller for Hydra head lifecycle operations.
8
16
  *
@@ -23,12 +31,20 @@ export class Wrangler {
23
31
  ws;
24
32
  http;
25
33
  blockfrost;
26
- constructor(url, wsUrl) {
34
+ monitor;
35
+ constructor(url, wsUrl, monitor) {
27
36
  const httpUrl = url || requireEnv('HYDRA_API_URL');
28
- const socketUrl = wsUrl || requireEnv('HYDRA_WS_URL');
29
37
  this.blockfrost = new BlockfrostProvider(requireEnv('BLOCKFROST_API_KEY'));
30
- this.ws = new HydraWebSocket(socketUrl);
31
38
  this.http = new HydraHttpClient(httpUrl);
39
+ this.monitor = monitor ?? null;
40
+ if (monitor) {
41
+ // Share the monitor's WebSocket — no new connections
42
+ this.ws = monitor.ws;
43
+ }
44
+ else {
45
+ const socketUrl = wsUrl || requireEnv('HYDRA_WS_URL');
46
+ this.ws = new HydraWebSocket(socketUrl);
47
+ }
32
48
  }
33
49
  /**
34
50
  * Connect to the Hydra node with exponential-backoff retry.
@@ -40,6 +56,9 @@ export class Wrangler {
40
56
  * @param baseDelayMs - Initial retry delay in milliseconds (default 1000). Doubles each attempt, capped at 30 s.
41
57
  */
42
58
  async connectWithRetry(maxAttempts = 5, baseDelayMs = 1000) {
59
+ // When using a monitor, the WebSocket is already connected
60
+ if (this.monitor?.connected)
61
+ return;
43
62
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
44
63
  try {
45
64
  const connected = await this.ws.waitForGreetings();
@@ -167,6 +186,10 @@ export class Wrangler {
167
186
  * @returns The head status string (e.g. `"Idle"`, `"Open"`, `"Closed"`).
168
187
  */
169
188
  async getHeadStatus(timeoutMs = 5000) {
189
+ // When using a monitor, read the cached status directly — no new WebSocket
190
+ if (this.monitor?.connected) {
191
+ return HYDRA_TO_HEAD_STATUS[this.monitor.headStatus];
192
+ }
170
193
  return this.awaitMessage((message, resolve, _reject) => {
171
194
  if (message.tag === 'Greetings') {
172
195
  resolve(message.headStatus);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lerna-labs/hydra-sdk",
3
- "version": "1.0.0-beta.20",
3
+ "version": "1.0.0-beta.21",
4
4
  "description": "TypeScript SDK for managing Cardano Hydra Heads — lifecycle, UTxO queries, wallet management, transaction submission, and signature verification",
5
5
  "keywords": [
6
6
  "cardano",