@fiber-pay/sdk 0.1.0-rc.1

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.
@@ -0,0 +1,723 @@
1
+ // src/address.ts
2
+ var BECH32_CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
3
+ function bech32mPolymod(values) {
4
+ const GEN = [996825010, 642813549, 513874426, 1027748829, 705979059];
5
+ let chk = 1;
6
+ for (const v of values) {
7
+ const top = chk >> 25;
8
+ chk = (chk & 33554431) << 5 ^ v;
9
+ for (let i = 0; i < 5; i++) {
10
+ if (top >> i & 1) {
11
+ chk ^= GEN[i];
12
+ }
13
+ }
14
+ }
15
+ return chk;
16
+ }
17
+ function bech32mHrpExpand(hrp) {
18
+ const ret = [];
19
+ for (let i = 0; i < hrp.length; i++) {
20
+ ret.push(hrp.charCodeAt(i) >> 5);
21
+ }
22
+ ret.push(0);
23
+ for (let i = 0; i < hrp.length; i++) {
24
+ ret.push(hrp.charCodeAt(i) & 31);
25
+ }
26
+ return ret;
27
+ }
28
+ function bech32mCreateChecksum(hrp, data) {
29
+ const values = bech32mHrpExpand(hrp).concat(data).concat([0, 0, 0, 0, 0, 0]);
30
+ const polymod = bech32mPolymod(values) ^ 734539939;
31
+ const ret = [];
32
+ for (let i = 0; i < 6; i++) {
33
+ ret.push(polymod >> 5 * (5 - i) & 31);
34
+ }
35
+ return ret;
36
+ }
37
+ function convertBits(data, fromBits, toBits, pad) {
38
+ let acc = 0;
39
+ let bits = 0;
40
+ const ret = [];
41
+ const maxv = (1 << toBits) - 1;
42
+ for (const value of data) {
43
+ acc = acc << fromBits | value;
44
+ bits += fromBits;
45
+ while (bits >= toBits) {
46
+ bits -= toBits;
47
+ ret.push(acc >> bits & maxv);
48
+ }
49
+ }
50
+ if (pad && bits > 0) {
51
+ ret.push(acc << toBits - bits & maxv);
52
+ }
53
+ return ret;
54
+ }
55
+ function bech32mEncode(hrp, data) {
56
+ const checksum = bech32mCreateChecksum(hrp, data);
57
+ const combined = data.concat(checksum);
58
+ let result = `${hrp}1`;
59
+ for (const d of combined) {
60
+ result += BECH32_CHARSET[d];
61
+ }
62
+ return result;
63
+ }
64
+ function scriptToAddress(script, network) {
65
+ const hrp = network === "mainnet" ? "ckb" : "ckt";
66
+ const hashTypeByte = script.hash_type === "type" ? 1 : script.hash_type === "data" ? 0 : script.hash_type === "data1" ? 2 : 4;
67
+ const codeHash = script.code_hash.startsWith("0x") ? script.code_hash.slice(2) : script.code_hash;
68
+ const args = script.args.startsWith("0x") ? script.args.slice(2) : script.args;
69
+ const payload = new Uint8Array(1 + 32 + 1 + args.length / 2);
70
+ payload[0] = 0;
71
+ for (let i = 0; i < 32; i++) {
72
+ payload[1 + i] = parseInt(codeHash.slice(i * 2, i * 2 + 2), 16);
73
+ }
74
+ payload[33] = hashTypeByte;
75
+ for (let i = 0; i < args.length / 2; i++) {
76
+ payload[34 + i] = parseInt(args.slice(i * 2, i * 2 + 2), 16);
77
+ }
78
+ const data = convertBits(payload, 8, 5, true);
79
+ return bech32mEncode(hrp, data);
80
+ }
81
+
82
+ // src/types/rpc.ts
83
+ var ChannelState = /* @__PURE__ */ ((ChannelState2) => {
84
+ ChannelState2["NegotiatingFunding"] = "NEGOTIATING_FUNDING";
85
+ ChannelState2["CollaboratingFundingTx"] = "COLLABORATING_FUNDING_TX";
86
+ ChannelState2["SigningCommitment"] = "SIGNING_COMMITMENT";
87
+ ChannelState2["AwaitingTxSignatures"] = "AWAITING_TX_SIGNATURES";
88
+ ChannelState2["AwaitingChannelReady"] = "AWAITING_CHANNEL_READY";
89
+ ChannelState2["ChannelReady"] = "CHANNEL_READY";
90
+ ChannelState2["ShuttingDown"] = "SHUTTING_DOWN";
91
+ ChannelState2["Closed"] = "CLOSED";
92
+ return ChannelState2;
93
+ })(ChannelState || {});
94
+
95
+ // src/types/policy.ts
96
+ import { z } from "zod";
97
+ var SpendingLimitSchema = z.object({
98
+ /** Maximum amount per single transaction (in shannons for CKB, or base units for UDT) */
99
+ maxPerTransaction: z.string().regex(/^0x[0-9a-fA-F]+$/),
100
+ /** Maximum total amount per time window */
101
+ maxPerWindow: z.string().regex(/^0x[0-9a-fA-F]+$/),
102
+ /** Time window in seconds */
103
+ windowSeconds: z.number().positive(),
104
+ /** Current spent amount in window (runtime state) */
105
+ currentSpent: z.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
106
+ /** Window start timestamp (runtime state) */
107
+ windowStart: z.number().optional()
108
+ });
109
+ var RecipientPolicySchema = z.object({
110
+ /** Allowlist mode: only allow payments to these recipients */
111
+ allowlist: z.array(z.string()).optional(),
112
+ /** Blocklist mode: block payments to these recipients */
113
+ blocklist: z.array(z.string()).optional(),
114
+ /** Allow payments to unknown recipients (not in allowlist) */
115
+ allowUnknown: z.boolean().default(true)
116
+ });
117
+ var RateLimitSchema = z.object({
118
+ /** Maximum number of transactions per window */
119
+ maxTransactions: z.number().positive(),
120
+ /** Time window in seconds */
121
+ windowSeconds: z.number().positive(),
122
+ /** Cooldown between transactions in seconds */
123
+ cooldownSeconds: z.number().nonnegative().default(0),
124
+ /** Current transaction count in window (runtime state) */
125
+ currentCount: z.number().optional(),
126
+ /** Window start timestamp (runtime state) */
127
+ windowStart: z.number().optional(),
128
+ /** Last transaction timestamp (runtime state) */
129
+ lastTransaction: z.number().optional()
130
+ });
131
+ var ChannelPolicySchema = z.object({
132
+ /** Allow opening new channels */
133
+ allowOpen: z.boolean().default(true),
134
+ /** Allow closing channels */
135
+ allowClose: z.boolean().default(true),
136
+ /** Allow force close */
137
+ allowForceClose: z.boolean().default(false),
138
+ /** Maximum funding amount for new channels */
139
+ maxFundingAmount: z.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
140
+ /** Minimum funding amount for new channels */
141
+ minFundingAmount: z.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
142
+ /** Maximum number of channels */
143
+ maxChannels: z.number().positive().optional()
144
+ });
145
+ var SecurityPolicySchema = z.object({
146
+ /** Policy name for identification */
147
+ name: z.string(),
148
+ /** Policy version */
149
+ version: z.string().default("1.0.0"),
150
+ /** Whether this policy is active */
151
+ enabled: z.boolean().default(true),
152
+ /** Spending limits configuration */
153
+ spending: SpendingLimitSchema.optional(),
154
+ /** Recipient restrictions */
155
+ recipients: RecipientPolicySchema.optional(),
156
+ /** Rate limiting configuration */
157
+ rateLimit: RateLimitSchema.optional(),
158
+ /** Channel operation policy */
159
+ channels: ChannelPolicySchema.optional(),
160
+ /** Require confirmation for amounts above this threshold */
161
+ confirmationThreshold: z.string().regex(/^0x[0-9a-fA-F]+$/).optional(),
162
+ /** Log all transactions to audit log */
163
+ auditLogging: z.boolean().default(true),
164
+ /** Custom metadata */
165
+ metadata: z.record(z.string(), z.unknown()).optional()
166
+ });
167
+
168
+ // src/utils.ts
169
+ var BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
170
+ function toHex(value) {
171
+ return `0x${value.toString(16)}`;
172
+ }
173
+ function fromHex(hex) {
174
+ return BigInt(hex);
175
+ }
176
+ function ckbToShannons(ckb) {
177
+ const amount = typeof ckb === "string" ? parseFloat(ckb) : ckb;
178
+ const shannons = BigInt(Math.floor(amount * 1e8));
179
+ return toHex(shannons);
180
+ }
181
+ function shannonsToCkb(shannons) {
182
+ return Number(fromHex(shannons)) / 1e8;
183
+ }
184
+ function randomBytes32() {
185
+ const bytes = new Uint8Array(32);
186
+ crypto.getRandomValues(bytes);
187
+ return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
188
+ }
189
+ function base58btcEncode(bytes) {
190
+ if (bytes.length === 0) return "";
191
+ let number = 0n;
192
+ for (const byte of bytes) {
193
+ number = (number << 8n) + BigInt(byte);
194
+ }
195
+ let encoded = "";
196
+ while (number > 0n) {
197
+ const remainder = Number(number % 58n);
198
+ encoded = BASE58_ALPHABET[remainder] + encoded;
199
+ number /= 58n;
200
+ }
201
+ for (let i = 0; i < bytes.length && bytes[i] === 0; i++) {
202
+ encoded = `1${encoded}`;
203
+ }
204
+ return encoded || "1";
205
+ }
206
+ async function nodeIdToPeerId(nodeId) {
207
+ const normalized = nodeId.trim().replace(/^0x/i, "");
208
+ if (!/^[0-9a-fA-F]+$/.test(normalized)) {
209
+ throw new Error("Invalid node id: expected hex string");
210
+ }
211
+ if (normalized.length !== 66) {
212
+ throw new Error(
213
+ `Invalid node id: expected 33-byte compressed pubkey, got ${normalized.length / 2} bytes`
214
+ );
215
+ }
216
+ const raw = Uint8Array.from(
217
+ normalized.match(/.{1,2}/g)?.map((byte) => Number.parseInt(byte, 16)) ?? []
218
+ );
219
+ if (raw.length !== 33) {
220
+ throw new Error(`Invalid node id: expected 33-byte compressed pubkey, got ${raw.length} bytes`);
221
+ }
222
+ const digestBuffer = await crypto.subtle.digest("SHA-256", raw);
223
+ const digest = new Uint8Array(digestBuffer);
224
+ const multihash = new Uint8Array(2 + digest.length);
225
+ multihash[0] = 18;
226
+ multihash[1] = 32;
227
+ multihash.set(digest, 2);
228
+ return base58btcEncode(multihash);
229
+ }
230
+ function buildMultiaddr(address, peerId) {
231
+ const normalizedAddress = address.trim();
232
+ const normalizedPeerId = peerId.trim();
233
+ if (!normalizedAddress.startsWith("/")) {
234
+ throw new Error('Invalid multiaddr: expected address starting with "/"');
235
+ }
236
+ if (!normalizedPeerId) {
237
+ throw new Error("Invalid peer id: empty value");
238
+ }
239
+ const withoutPeerSuffix = normalizedAddress.replace(/\/p2p\/[^/]+$/, "");
240
+ return `${withoutPeerSuffix}/p2p/${normalizedPeerId}`;
241
+ }
242
+ async function buildMultiaddrFromNodeId(address, nodeId) {
243
+ const peerId = await nodeIdToPeerId(nodeId);
244
+ return buildMultiaddr(address, peerId);
245
+ }
246
+ function buildMultiaddrFromRpcUrl(rpcUrl, peerId) {
247
+ let parsed;
248
+ try {
249
+ parsed = new URL(rpcUrl);
250
+ } catch {
251
+ throw new Error(`Invalid RPC URL: ${rpcUrl}`);
252
+ }
253
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
254
+ throw new Error(`Unsupported RPC protocol: ${parsed.protocol}`);
255
+ }
256
+ const port = parsed.port ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;
257
+ if (!Number.isFinite(port) || port <= 0 || port > 65535) {
258
+ throw new Error(`Invalid RPC port in URL: ${rpcUrl}`);
259
+ }
260
+ const p2pPort = port + 1;
261
+ if (p2pPort > 65535) {
262
+ throw new Error(`Cannot infer P2P port from RPC port ${port}`);
263
+ }
264
+ const host = parsed.hostname;
265
+ const isIpv4 = /^\d{1,3}(?:\.\d{1,3}){3}$/.test(host);
266
+ const isIpv6 = host.includes(":");
267
+ const base = isIpv4 ? `/ip4/${host}/tcp/${p2pPort}` : isIpv6 ? `/ip6/${host}/tcp/${p2pPort}` : `/dns/${host}/tcp/${p2pPort}`;
268
+ return buildMultiaddr(base, peerId);
269
+ }
270
+
271
+ // src/rpc/client.ts
272
+ var FiberRpcError = class _FiberRpcError extends Error {
273
+ constructor(code, message, data) {
274
+ super(message);
275
+ this.code = code;
276
+ this.data = data;
277
+ this.name = "FiberRpcError";
278
+ }
279
+ static fromJsonRpcError(error) {
280
+ return new _FiberRpcError(error.code, error.message, error.data);
281
+ }
282
+ };
283
+ var FiberRpcClient = class {
284
+ requestId = 0;
285
+ config;
286
+ channelStateAliases = {
287
+ NEGOTIATING_FUNDING: "NEGOTIATING_FUNDING" /* NegotiatingFunding */,
288
+ COLLABORATING_FUNDING_TX: "COLLABORATING_FUNDING_TX" /* CollaboratingFundingTx */,
289
+ SIGNING_COMMITMENT: "SIGNING_COMMITMENT" /* SigningCommitment */,
290
+ AWAITING_TX_SIGNATURES: "AWAITING_TX_SIGNATURES" /* AwaitingTxSignatures */,
291
+ AWAITING_CHANNEL_READY: "AWAITING_CHANNEL_READY" /* AwaitingChannelReady */,
292
+ CHANNEL_READY: "CHANNEL_READY" /* ChannelReady */,
293
+ SHUTTING_DOWN: "SHUTTING_DOWN" /* ShuttingDown */,
294
+ CLOSED: "CLOSED" /* Closed */
295
+ };
296
+ constructor(config) {
297
+ this.config = {
298
+ timeout: 3e4,
299
+ ...config
300
+ };
301
+ }
302
+ /**
303
+ * Make a raw JSON-RPC call
304
+ *
305
+ * Useful for advanced/experimental RPCs not wrapped by convenience methods.
306
+ *
307
+ * @example
308
+ * ```ts
309
+ * const result = await client.call<MyResult>('some_method', [{ foo: 'bar' }]);
310
+ * ```
311
+ */
312
+ async call(method, params = []) {
313
+ const request = {
314
+ jsonrpc: "2.0",
315
+ id: ++this.requestId,
316
+ method,
317
+ params
318
+ };
319
+ const headers = {
320
+ "Content-Type": "application/json",
321
+ ...this.config.headers
322
+ };
323
+ if (this.config.biscuitToken) {
324
+ headers.Authorization = `Bearer ${this.config.biscuitToken}`;
325
+ }
326
+ const controller = new AbortController();
327
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
328
+ try {
329
+ const response = await fetch(this.config.url, {
330
+ method: "POST",
331
+ headers,
332
+ body: JSON.stringify(request),
333
+ signal: controller.signal
334
+ });
335
+ if (!response.ok) {
336
+ throw new FiberRpcError(-32e3, `HTTP error: ${response.status} ${response.statusText}`);
337
+ }
338
+ const json = await response.json();
339
+ if (json.error) {
340
+ throw FiberRpcError.fromJsonRpcError(json.error);
341
+ }
342
+ if (json.result === void 0) {
343
+ throw new FiberRpcError(-32e3, "Invalid JSON-RPC response: missing result and error");
344
+ }
345
+ return json.result;
346
+ } catch (error) {
347
+ if (error instanceof FiberRpcError) {
348
+ throw error;
349
+ }
350
+ if (error instanceof Error) {
351
+ if (error.name === "AbortError") {
352
+ throw new FiberRpcError(-32e3, "Request timeout");
353
+ }
354
+ throw new FiberRpcError(-32e3, error.message);
355
+ }
356
+ throw new FiberRpcError(-32e3, "Unknown error");
357
+ } finally {
358
+ clearTimeout(timeoutId);
359
+ }
360
+ }
361
+ // ===========================================================================
362
+ // Peer Module
363
+ // ===========================================================================
364
+ /**
365
+ * Connect to a peer
366
+ */
367
+ async connectPeer(params) {
368
+ return this.call("connect_peer", [params]);
369
+ }
370
+ /**
371
+ * Disconnect from a peer
372
+ */
373
+ async disconnectPeer(params) {
374
+ return this.call("disconnect_peer", [params]);
375
+ }
376
+ /**
377
+ * List all connected peers
378
+ */
379
+ async listPeers() {
380
+ return this.call("list_peers", []);
381
+ }
382
+ // ===========================================================================
383
+ // Channel Module
384
+ // ===========================================================================
385
+ /**
386
+ * Open a new channel with a peer
387
+ */
388
+ async openChannel(params) {
389
+ return this.call("open_channel", [params]);
390
+ }
391
+ /**
392
+ * Accept a channel opening request
393
+ */
394
+ async acceptChannel(params) {
395
+ return this.call("accept_channel", [params]);
396
+ }
397
+ /**
398
+ * List all channels
399
+ */
400
+ async listChannels(params) {
401
+ const result = await this.call("list_channels", params ? [params] : [{}]);
402
+ return {
403
+ ...result,
404
+ channels: result.channels.map((channel) => this.normalizeChannel(channel))
405
+ };
406
+ }
407
+ normalizeChannelStateName(stateName) {
408
+ const alias = this.channelStateAliases[stateName];
409
+ if (alias) return alias;
410
+ const normalizedInput = stateName.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
411
+ for (const value of Object.values(ChannelState)) {
412
+ const normalizedValue = value.replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
413
+ if (normalizedValue === normalizedInput) {
414
+ return value;
415
+ }
416
+ }
417
+ return stateName;
418
+ }
419
+ normalizeChannel(channel) {
420
+ return {
421
+ ...channel,
422
+ state: {
423
+ ...channel.state,
424
+ state_name: this.normalizeChannelStateName(channel.state.state_name)
425
+ }
426
+ };
427
+ }
428
+ /**
429
+ * Shutdown (close) a channel
430
+ */
431
+ async shutdownChannel(params) {
432
+ return this.call("shutdown_channel", [params]);
433
+ }
434
+ /**
435
+ * Abandon a pending channel
436
+ */
437
+ async abandonChannel(params) {
438
+ return this.call("abandon_channel", [params]);
439
+ }
440
+ /**
441
+ * Update channel parameters
442
+ */
443
+ async updateChannel(params) {
444
+ return this.call("update_channel", [params]);
445
+ }
446
+ // ===========================================================================
447
+ // Payment Module
448
+ // ===========================================================================
449
+ /**
450
+ * Send a payment
451
+ */
452
+ async sendPayment(params) {
453
+ return this.call("send_payment", [params]);
454
+ }
455
+ /**
456
+ * Get payment status
457
+ */
458
+ async getPayment(params) {
459
+ return this.call("get_payment", [params]);
460
+ }
461
+ // ===========================================================================
462
+ // Invoice Module
463
+ // ===========================================================================
464
+ /**
465
+ * Create a new invoice
466
+ */
467
+ async newInvoice(params) {
468
+ return this.call("new_invoice", [params]);
469
+ }
470
+ /**
471
+ * Parse an invoice string
472
+ */
473
+ async parseInvoice(params) {
474
+ return this.call("parse_invoice", [params]);
475
+ }
476
+ /**
477
+ * Get invoice by payment hash
478
+ */
479
+ async getInvoice(params) {
480
+ return this.call("get_invoice", [params]);
481
+ }
482
+ /**
483
+ * Cancel an open invoice
484
+ */
485
+ async cancelInvoice(params) {
486
+ return this.call("cancel_invoice", [params]);
487
+ }
488
+ /**
489
+ * Settle a hold invoice with the preimage
490
+ * Used for conditional/escrow payments where the invoice was created
491
+ * with a payment_hash (no preimage provided upfront)
492
+ */
493
+ async settleInvoice(params) {
494
+ return this.call("settle_invoice", [params]);
495
+ }
496
+ // ===========================================================================
497
+ // Router Module
498
+ // ===========================================================================
499
+ /**
500
+ * Build a custom route for payment
501
+ * Useful for channel rebalancing (circular payments) and advanced routing
502
+ */
503
+ async buildRouter(params) {
504
+ return this.call("build_router", [params]);
505
+ }
506
+ /**
507
+ * Send a payment using a pre-built route from buildRouter()
508
+ * Use with allow_self_payment for channel rebalancing
509
+ */
510
+ async sendPaymentWithRouter(params) {
511
+ return this.call("send_payment_with_router", [params]);
512
+ }
513
+ // ===========================================================================
514
+ // Graph Module
515
+ // ===========================================================================
516
+ /**
517
+ * List nodes in the network graph
518
+ */
519
+ async graphNodes(params) {
520
+ return this.call("graph_nodes", params ? [params] : [{}]);
521
+ }
522
+ /**
523
+ * List channels in the network graph
524
+ */
525
+ async graphChannels(params) {
526
+ return this.call("graph_channels", params ? [params] : [{}]);
527
+ }
528
+ // ===========================================================================
529
+ // Info Module
530
+ // ===========================================================================
531
+ /**
532
+ * Get local node information
533
+ */
534
+ async nodeInfo() {
535
+ return this.call("node_info", []);
536
+ }
537
+ // ===========================================================================
538
+ // Utility Methods
539
+ // ===========================================================================
540
+ /**
541
+ * Check if the node is reachable
542
+ */
543
+ async ping() {
544
+ try {
545
+ await this.nodeInfo();
546
+ return true;
547
+ } catch {
548
+ return false;
549
+ }
550
+ }
551
+ /**
552
+ * Wait for the node to be ready
553
+ */
554
+ async waitForReady(options = {}) {
555
+ const { timeout = 6e4, interval = 1e3 } = options;
556
+ const start = Date.now();
557
+ while (Date.now() - start < timeout) {
558
+ if (await this.ping()) {
559
+ return;
560
+ }
561
+ await new Promise((resolve) => setTimeout(resolve, interval));
562
+ }
563
+ throw new FiberRpcError(-32e3, "Node not ready within timeout");
564
+ }
565
+ // ===========================================================================
566
+ // Polling / Watching Helpers
567
+ // ===========================================================================
568
+ /**
569
+ * Wait for a payment to reach a terminal state (Success or Failed)
570
+ * Polls get_payment at the specified interval.
571
+ *
572
+ * @returns The final payment result
573
+ * @throws FiberRpcError on timeout
574
+ */
575
+ async waitForPayment(paymentHash, options = {}) {
576
+ const { timeout = 12e4, interval = 2e3 } = options;
577
+ const start = Date.now();
578
+ while (Date.now() - start < timeout) {
579
+ const result = await this.getPayment({ payment_hash: paymentHash });
580
+ if (result.status === "Success" || result.status === "Failed") {
581
+ return result;
582
+ }
583
+ await new Promise((resolve) => setTimeout(resolve, interval));
584
+ }
585
+ throw new FiberRpcError(-32e3, `Payment ${paymentHash} did not complete within ${timeout}ms`);
586
+ }
587
+ /**
588
+ * Wait for a channel to reach ChannelReady state.
589
+ * Polls list_channels at the specified interval.
590
+ *
591
+ * @returns The channel info once ready
592
+ * @throws FiberRpcError on timeout or if channel disappears
593
+ */
594
+ async waitForChannelReady(channelId, options = {}) {
595
+ const { timeout = 3e5, interval = 5e3 } = options;
596
+ const start = Date.now();
597
+ while (Date.now() - start < timeout) {
598
+ const result = await this.listChannels({});
599
+ const channel = result.channels.find((ch) => ch.channel_id === channelId);
600
+ if (!channel) {
601
+ const allChannels = result.channels;
602
+ const found = allChannels.find((ch) => ch.channel_id === channelId);
603
+ if (!found) {
604
+ await new Promise((resolve) => setTimeout(resolve, interval));
605
+ continue;
606
+ }
607
+ }
608
+ if (channel && channel.state.state_name === "CHANNEL_READY" /* ChannelReady */) {
609
+ return channel;
610
+ }
611
+ if (channel && channel.state.state_name === "CLOSED" /* Closed */) {
612
+ throw new FiberRpcError(-32e3, `Channel ${channelId} was closed before becoming ready`);
613
+ }
614
+ await new Promise((resolve) => setTimeout(resolve, interval));
615
+ }
616
+ throw new FiberRpcError(
617
+ -32e3,
618
+ `Channel ${channelId} did not become ready within ${timeout}ms`
619
+ );
620
+ }
621
+ /**
622
+ * Wait for an invoice to reach a specific status.
623
+ * Useful for hold invoice workflows: wait for 'Received' before settling.
624
+ *
625
+ * @returns The invoice info once the target status is reached
626
+ * @throws FiberRpcError on timeout
627
+ */
628
+ async waitForInvoiceStatus(paymentHash, targetStatus, options = {}) {
629
+ const { timeout = 12e4, interval = 2e3 } = options;
630
+ const statuses = Array.isArray(targetStatus) ? targetStatus : [targetStatus];
631
+ const start = Date.now();
632
+ while (Date.now() - start < timeout) {
633
+ const result = await this.getInvoice({ payment_hash: paymentHash });
634
+ if (statuses.includes(result.status)) {
635
+ return result;
636
+ }
637
+ if (result.status === "Cancelled") {
638
+ throw new FiberRpcError(-32e3, `Invoice ${paymentHash} was cancelled`);
639
+ }
640
+ await new Promise((resolve) => setTimeout(resolve, interval));
641
+ }
642
+ throw new FiberRpcError(
643
+ -32e3,
644
+ `Invoice ${paymentHash} did not reach status [${statuses.join(", ")}] within ${timeout}ms`
645
+ );
646
+ }
647
+ /**
648
+ * Watch for incoming payments on specified invoices.
649
+ * Polls invoice statuses and calls the callback when a status changes.
650
+ * Use an AbortSignal to stop watching.
651
+ *
652
+ * @example
653
+ * ```typescript
654
+ * const controller = new AbortController();
655
+ * client.watchIncomingPayments({
656
+ * paymentHashes: [hash1, hash2],
657
+ * onPayment: (invoice) => console.log('Payment received!', invoice),
658
+ * signal: controller.signal,
659
+ * });
660
+ * // Later: controller.abort(); to stop watching
661
+ * ```
662
+ */
663
+ async watchIncomingPayments(options) {
664
+ const { paymentHashes, onPayment, interval = 3e3, signal } = options;
665
+ const knownStatuses = /* @__PURE__ */ new Map();
666
+ for (const hash of paymentHashes) {
667
+ try {
668
+ const invoice = await this.getInvoice({ payment_hash: hash });
669
+ knownStatuses.set(hash, invoice.status);
670
+ } catch {
671
+ knownStatuses.set(hash, "Open");
672
+ }
673
+ }
674
+ while (!signal?.aborted) {
675
+ for (const hash of paymentHashes) {
676
+ if (signal?.aborted) return;
677
+ try {
678
+ const invoice = await this.getInvoice({ payment_hash: hash });
679
+ const previousStatus = knownStatuses.get(hash);
680
+ if (invoice.status !== previousStatus && (invoice.status === "Received" || invoice.status === "Paid")) {
681
+ knownStatuses.set(hash, invoice.status);
682
+ onPayment(invoice);
683
+ } else if (invoice.status !== previousStatus) {
684
+ knownStatuses.set(hash, invoice.status);
685
+ }
686
+ } catch {
687
+ }
688
+ }
689
+ await new Promise((resolve) => {
690
+ if (signal?.aborted) {
691
+ resolve();
692
+ return;
693
+ }
694
+ const timer = setTimeout(resolve, interval);
695
+ signal?.addEventListener(
696
+ "abort",
697
+ () => {
698
+ clearTimeout(timer);
699
+ resolve();
700
+ },
701
+ { once: true }
702
+ );
703
+ });
704
+ }
705
+ }
706
+ };
707
+
708
+ export {
709
+ scriptToAddress,
710
+ ChannelState,
711
+ toHex,
712
+ fromHex,
713
+ ckbToShannons,
714
+ shannonsToCkb,
715
+ randomBytes32,
716
+ nodeIdToPeerId,
717
+ buildMultiaddr,
718
+ buildMultiaddrFromNodeId,
719
+ buildMultiaddrFromRpcUrl,
720
+ FiberRpcError,
721
+ FiberRpcClient
722
+ };
723
+ //# sourceMappingURL=chunk-QQDMPGVR.js.map