fiberprobe 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/README.md ADDED
@@ -0,0 +1,197 @@
1
+ # fnn-ts
2
+
3
+ A TypeScript SDK for Fiber Network Node (FNN) that answers the question every payment-channel application eventually asks: **will this payment actually go through?**
4
+
5
+ Built for the "Gone in 60ms" Fiber Network Infrastructure Hackathon, Category 2: Node, Routing & Diagnostics.
6
+ ---
7
+ ## The problem
8
+
9
+ Fiber, like Lightning, splits channel state into two categories. Total capacity is public, broadcast via gossip. The actual balance split between the two peers in a channel is private and changes with every payment. This is by design: publishing live balances would leak the entire financial graph of the network.
10
+
11
+ The result is that no RPC can honestly answer "can I pay this amount right now?" A `dry_run` flag exists on `send_payment`, but we tested it directly against live FNN v0.8.1 nodes and confirmed it does not help: dry-run sessions return `Created` immediately with no route detail, and are never queryable afterward, not even the instant after creation (`get_payment` returns `Payment session not found`). Whatever data a real router computes during a dry run is discarded, not surfaced.
12
+
13
+ Every wallet, checkout flow, and payment bot built on Fiber runs into this same wall. `fnn-ts` is the SDK layer that solves it properly, and it does so two ways, because there are two different questions a developer actually needs answered.
14
+
15
+ ## Two tiers, because one estimate isn't enough
16
+
17
+ ### `canPay()` — free, instant, approximate
18
+
19
+ Resolves the destination from an invoice, checks for a direct channel, and falls back to a breadth-first search over the public channel graph if no direct channel exists. Each hop is scored against whatever `outbound_liquidity` operators have chosen to publish (most don't; it's optional and frequently `null`), combined using weakest-link logic since a payment fails the moment any single hop can't forward it. Costs nothing, takes milliseconds, and is right most of the time. But it can only ever be an estimate, because gossip-announced capacity is not the same as current usable liquidity.
20
+
21
+ ### `probePay()` — the ground truth
22
+
23
+ This is the part of the SDK we're most confident about, because we built it by directly verifying how Lightning-style networks solve this exact problem, then proved it works against our own live Fiber testnet nodes before writing a line of production code.
24
+
25
+ The mechanism: generate a random 32-byte value, hash it, and send a real HTLC to the destination's pubkey using that fake hash as the payment hash, no invoice involved. The payment travels the real route with real HTLCs locked at every hop. Because the true preimage was never generated by the destination and never revealed by us, no node on the path, including the destination, can ever claim the funds. Two outcomes are possible:
26
+
27
+ - The destination receives the HTLC and rejects it with `IncorrectOrUnknownPaymentDetails`. This is the "successful failure": the only reason it failed is that we made up the hash. Every hop along the entire route had enough live liquidity to carry this exact amount, at this exact moment. Verified live on our own 3-node testnet topology (Alice → Bob → Carol, 2 hops): resolved in 577ms.
28
+ - The HTLC gets rejected by an intermediate hop, e.g. `TemporaryChannelFailure` or an explicit insufficient-liquidity error. That hop, specifically, doesn't have the capacity right now. We tested this too, by probing for an amount larger than any real channel could carry, and got back the actual RPC error with real numbers: `max outbound liquidity 89100000000 is insufficient, required amount: 409600000000000`.
29
+
30
+ No funds move. No invoice is required. The cost is a brief HTLC lock at each hop while the probe resolves (observed under 600ms end to end on testnet) and it should be used when a real payment is imminent and the answer needs to be right, not just probably right.
31
+
32
+ Fiber currently implements this using HTLCs, same as Bitcoin Lightning. PTLCs (Point Time-Locked Contracts) are a documented future upgrade in the Fiber litepaper aimed at closing a route-correlation privacy gap that HTLC-based probing shares with Lightning. `probePay()` is built against what Fiber ships today; when PTLCs land, the probing mechanism will need the corresponding update, and we've noted this as a tracked item below.
33
+
34
+ ### `canReceive()` — inbound capacity, correctly computed
35
+
36
+ Sums usable inbound capacity across `ChannelReady` channels: `remote_balance` minus `received_tlc_balance`, so in-flight HTLCs aren't double-counted as available. Simple on paper, but we found and fixed two real bugs here during live testing (see below) that would have silently under-reported capacity in production.
37
+
38
+ ---
39
+
40
+ ## What's actually been proven, not just implemented
41
+
42
+ Everything in this SDK has been run against real FNN v0.8.1 nodes on Fiber testnet, not mocked. Along the way we found and fixed five bugs that only showed up under real conditions:
43
+
44
+ 1. **`graph_channels`' `limit` param must be a hex string**, not a plain integer, like every other numeric RPC field. A naive integer call fails outright.
45
+ 2. **The public graph paginates.** Testnet has 600+ channels; a single 500-entry page silently missed our own freshly opened channel. Fixed with cursor-based pagination via the `after` param, looping until `last_cursor` stabilizes.
46
+ 3. **`funding_udt_type_script` is returned as `null`, not omitted**, for native CKB channels. A `!== undefined` check let every native channel get misclassified as a UDT channel and excluded from `canReceive()`, reporting 0 capacity on a channel that actually had 891 CKB available.
47
+ 4. **`outbound_liquidity` is `null`, not `undefined`**, when an operator hasn't published it. Same class of bug, this time crashing `canPay()`'s hop scoring on real graph data.
48
+ 5. **`peer_id` was renamed to `pubkey`** across `Channel` and related types in FNN v0.8.0; our first type pass, written from RPC docs alone, still used the old field name.
49
+
50
+ None of these would have surfaced from reading documentation. They surfaced from running three real nodes, opening real channels, and generating real invoices.
51
+
52
+ ---
53
+
54
+ ## Quick start
55
+
56
+ ```ts
57
+ import { FiberClient, PaymentChecker } from 'fnn-ts'
58
+
59
+ const client = new FiberClient('http://127.0.0.1:8227') // your FNN node's RPC address
60
+ const checker = new PaymentChecker(client)
61
+
62
+ // Fast, free, approximate
63
+ const estimate = await checker.canPay({ invoice: 'fibt1...' })
64
+ console.log(estimate.confidence, estimate.hopCount)
65
+
66
+ // Slower, costs a brief HTLC lock, gives you the real answer
67
+ const probe = await checker.probePay({
68
+ targetPubkey: estimate.destinationPubkey!,
69
+ amount: estimate.amount!,
70
+ })
71
+ console.log(probe.isViable, probe.latencyMs, probe.terminalError)
72
+
73
+ // Inbound capacity, correctly computed
74
+ const rx = await checker.canReceive({ amount: '0x3B9ACA00' }) // 10 CKB
75
+ console.log(rx.canReceive, rx.totalInboundCapacity)
76
+ ```
77
+
78
+ Every method returns typed results. `FiberError` and its subclasses (`RouteNotFoundError`, `InsufficientCapacityError`, `TemporaryChannelFailureError`, `PeerUnreachableError`, `PaymentTimeoutError`, `FeeInsufficientError`, `AmountBelowMinimumError`) each carry a machine-readable `code` and a plain-English `suggestion`, so an application can branch on failure type without parsing raw RPC strings itself.
79
+
80
+ For payment and invoice lifecycle without manual polling loops, `FiberEventEmitter` wraps adaptive backoff (250ms up to 10s, capped attempts) behind `watchPayment()`, `watchInvoice()`, and `watchChannel()`, each returning a cancel function.
81
+
82
+ ---
83
+
84
+ ## Running the demo yourself
85
+
86
+ The demo runs three real FNN nodes locally on Fiber testnet and a React app that calls the SDK against them live. Nothing is mocked.
87
+
88
+ ### 1. Get the FNN binary
89
+
90
+ ```bash
91
+ mkdir fnn-node && cd fnn-node
92
+ wget https://github.com/nervosnetwork/fiber/releases/download/v0.8.1/fnn_v0.8.1-x86_64-linux.tar.gz
93
+ tar xzf fnn_v0.8.1-x86_64-linux.tar.gz
94
+ ```
95
+
96
+ (For other platforms, check `https://api.github.com/repos/nervosnetwork/fiber/releases/tags/v0.8.1` for the matching asset.)
97
+
98
+ ### 2. Set up three nodes
99
+
100
+ Repeat this for three directories (e.g. `fnn-node`, `fnn-node-bob`, `fnn-node-carol`), each with a distinct P2P and RPC port. The shipped `config/testnet/config.yml` needs only two lines changed per node:
101
+
102
+ ```yaml
103
+ fiber:
104
+ listening_addr: "/ip4/0.0.0.0/tcp/8228" # unique per node: 8228, 8229, 8231...
105
+ rpc:
106
+ listening_addr: "127.0.0.1:8227" # unique per node: 8227, 8237, 8247...
107
+ ```
108
+
109
+ For each node, generate a CKB account and export its key:
110
+
111
+ ```bash
112
+ ckb-cli account new
113
+ ckb-cli account export --lock-arg <LOCK_ARG> --extended-privkey-path ./ckb/exported-key
114
+ head -n 1 ./ckb/exported-key > ./ckb/key
115
+ chmod 600 ./ckb/key && rm ./ckb/exported-key
116
+ ```
117
+
118
+ Fund each node's testnet address at the Pudge Faucet: `https://faucet.nervos.org/`
119
+
120
+ Start each node:
121
+
122
+ ```bash
123
+ FIBER_SECRET_KEY_PASSWORD=<your password> RUST_LOG=info ./fnn -c config.yml -d .
124
+ ```
125
+
126
+ ### 3. Connect peers and open channels
127
+
128
+ ```bash
129
+ # From node A's RPC, connect to node B's P2P address (from its startup log)
130
+ curl -X POST -H "Content-Type: application/json" \
131
+ -d '{"jsonrpc":"2.0","id":1,"method":"connect_peer","params":[{"address":"/ip4/127.0.0.1/tcp/<PORT>/p2p/<PEER_ID>"}]}' \
132
+ http://127.0.0.1:\<RPC_PORT\>
133
+
134
+ # Open a channel (funding well above both sides' auto-accept minimum avoids a manual accept step)
135
+ curl -X POST -H "Content-Type: application/json" \
136
+ -d '{"jsonrpc":"2.0","id":2,"method":"open_channel","params":[{"pubkey":"<TARGET_PUBKEY>","funding_amount":"0x174876e800","public":true}]}' \
137
+ http://127.0.0.1:<RPC_PORT>
138
+ ```
139
+
140
+ Poll `list_channels` until `state.state_name` reads `ChannelReady`. This project's own test topology has no direct channel between the sender and the second recipient, forcing `canPay()`'s BFS and `probePay()`'s live route to actually traverse a real intermediate hop rather than a trivial direct case.
141
+
142
+ ### 4. Run the demo app
143
+
144
+ ```bash
145
+ cd demo
146
+ npm install
147
+ npm run dev
148
+ ```
149
+
150
+ Open `http://localhost:5173`. The Vite dev server proxies `/rpc-alice`, `/rpc-bob`, `/rpc-carol` to each node's RPC port (see `demo/vite.config.ts`), so the browser never needs CORS access to raw node ports directly.
151
+
152
+ ---
153
+
154
+ ## API reference
155
+
156
+ | Module | What it does |
157
+ |---|---|
158
+ | `FiberClient` | Fully typed HTTP JSON-RPC client. Every method typed against the live FNN v0.8.1 RPC surface, not just the docs. |
159
+ | `PaymentChecker.canPay()` | Free static route + liquidity estimate via direct-channel check or graph BFS. |
160
+ | `PaymentChecker.probePay()` | Live empirical route verification via riskless HTLC probing. Ground truth, not an estimate. |
161
+ | `PaymentChecker.canReceive()` | Real inbound capacity across `ChannelReady` channels, correctly excluding in-flight HTLCs. |
162
+ | `FiberEventEmitter` | Adaptive-backoff watchers for payment, invoice, and channel lifecycle, no manual polling loops. |
163
+ | `FiberError` + subclasses | Typed error hierarchy parsed from raw RPC failure strings, each with a `code` and a `suggestion`. |
164
+
165
+ ---
166
+
167
+ ## Honest limitations
168
+
169
+ - `probePay()` briefly locks a real HTLC on every hop along the route while it resolves. In our testing this resolved in well under a second, but if an intermediate node is unreachable, the HTLC could sit until `tlc_expiry_delta` lapses rather than failing fast. Set `timeoutSeconds` accordingly for your use case.
170
+ - `canPay()`'s confidence score reflects gossip-announced liquidity, which most operators don't publish (`outbound_liquidity` is frequently `null`). Treat it as a heuristic, not a guarantee, which is exactly why `probePay()` exists.
171
+ - No local "Mission Control"-style penalty cache yet (see roadmap). Every `probePay()` call is independent; nothing is remembered between calls in this version.
172
+ - Built and tested against FNN v0.8.1's current HTLC implementation. PTLC support is on Fiber's roadmap, not yet shipped, and this SDK will need updates when it lands.
173
+
174
+ ---
175
+
176
+ ## Roadmap
177
+
178
+ - Local in-memory penalty tracking for `probePay()`: remember which channels failed recently and skip them in the next BFS pass without re-probing, with a decay window so temporary conditions aren't treated as permanent.
179
+ - PTLC support once Fiber ships it, since the probing mechanism's privacy properties depend on the underlying lock type.
180
+ - Multi-asset (UDT/RGB++) coverage in `canReceive()` beyond the CKB-native path already implemented.
181
+ - Wider RPC coverage in `FiberClient` (currently covers node, peer, channel, payment, invoice, and graph operations; not yet CCH cross-chain endpoints).
182
+
183
+ ---
184
+
185
+ ## Why this belongs in the ecosystem, not just a hackathon repo
186
+
187
+ Every wallet, merchant checkout, and payment bot that gets built on Fiber will hit the exact liquidity-visibility wall this SDK addresses, on day one of integration. Right now each of them would either reinvent this from scratch or ship with silent failure modes, exactly the kind of gap that makes a new payment network frustrating to build on.
188
+
189
+ `fnn-ts` is meant to be the layer that sits between `fnn`'s RPC and application code, so that question doesn't have to be solved twice. It's typed, it's tested against real infrastructure rather than assumptions from documentation, and the failures it caught along the way (five of them, detailed above) are the kind that would otherwise ship silently into production and degrade user experience the first time someone hits an edge case.
190
+
191
+ ---
192
+
193
+ ## Project info
194
+
195
+ Category 2: Node, Routing & Diagnostics Infrastructure, "Gone in 60ms" Fiber Network Infrastructure Hackathon (July 2026).
196
+
197
+ Repository: `github.com/Linnnetteseven/fnn-ts`
@@ -0,0 +1,70 @@
1
+ /**
2
+ * FiberError — typed error hierarchy for Fiber RPC failures.
3
+ *
4
+ * The Fiber RPC returns payment failures as raw `failed_error` strings.
5
+ * This module parses those strings into structured, actionable error
6
+ * classes so application code can branch on error type and surface
7
+ * meaningful messages to developers and users.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const err = FiberError.parse(payment.failed_error)
12
+ * if (err instanceof InsufficientCapacityError) {
13
+ * console.log(err.suggestion)
14
+ * }
15
+ * ```
16
+ */
17
+ export declare abstract class FiberError extends Error {
18
+ abstract readonly code: string;
19
+ abstract readonly suggestion: string;
20
+ readonly raw?: string;
21
+ constructor(message: string, raw?: string);
22
+ /**
23
+ * Parse a raw Fiber RPC failed_error string into a typed FiberError.
24
+ * Matches against known error patterns using normalised lowercase comparison.
25
+ *
26
+ * @param raw - The failed_error string from a Payment or RPC response
27
+ */
28
+ static parse(raw: string): FiberError;
29
+ }
30
+ export declare class RouteNotFoundError extends FiberError {
31
+ readonly code = "ROUTE_NOT_FOUND";
32
+ readonly suggestion: string;
33
+ constructor(raw?: string);
34
+ }
35
+ export declare class InsufficientCapacityError extends FiberError {
36
+ readonly code = "INSUFFICIENT_CAPACITY";
37
+ readonly suggestion: string;
38
+ constructor(raw?: string);
39
+ }
40
+ export declare class TemporaryChannelFailureError extends FiberError {
41
+ readonly code = "TEMPORARY_CHANNEL_FAILURE";
42
+ readonly suggestion: string;
43
+ constructor(raw?: string);
44
+ }
45
+ export declare class PeerUnreachableError extends FiberError {
46
+ readonly code = "PEER_UNREACHABLE";
47
+ readonly suggestion: string;
48
+ constructor(raw?: string);
49
+ }
50
+ export declare class PaymentTimeoutError extends FiberError {
51
+ readonly code = "PAYMENT_TIMEOUT";
52
+ readonly suggestion: string;
53
+ constructor(raw?: string);
54
+ }
55
+ export declare class FeeInsufficientError extends FiberError {
56
+ readonly code = "FEE_INSUFFICIENT";
57
+ readonly suggestion = "The fee offered was rejected by a node on the route. Increase max_fee_amount and retry.";
58
+ constructor(raw?: string);
59
+ }
60
+ export declare class AmountBelowMinimumError extends FiberError {
61
+ readonly code = "AMOUNT_BELOW_MINIMUM";
62
+ readonly suggestion: string;
63
+ constructor(raw?: string);
64
+ }
65
+ export declare class UnknownFiberError extends FiberError {
66
+ readonly code = "UNKNOWN";
67
+ readonly suggestion = "An unrecognised error was returned by the Fiber node. Inspect the raw field for the original error string.";
68
+ constructor(raw?: string);
69
+ }
70
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAIH,8BAAsB,UAAW,SAAQ,KAAK;IAC5C,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAQ,MAAM,CAAA;IACpC,QAAQ,CAAC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IACpC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IAErB,YAAY,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,EAOxC;IAED;;;;;OAKG;IACH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAgCpC;CACF;AAID,qBAAa,kBAAmB,SAAQ,UAAU;IAChD,QAAQ,CAAC,IAAI,qBAA0B;IACvC,QAAQ,CAAC,UAAU,SAGsC;IACzD,YAAY,GAAG,CAAC,EAAE,MAAM,EAEvB;CACF;AAED,qBAAa,yBAA0B,SAAQ,UAAU;IACvD,QAAQ,CAAC,IAAI,2BAAgC;IAC7C,QAAQ,CAAC,UAAU,SAG+B;IAClD,YAAY,GAAG,CAAC,EAAE,MAAM,EAEvB;CACF;AAED,qBAAa,4BAA6B,SAAQ,UAAU;IAC1D,QAAQ,CAAC,IAAI,+BAAoC;IACjD,QAAQ,CAAC,UAAU,SAE+C;IAClE,YAAY,GAAG,CAAC,EAAE,MAAM,EAEvB;CACF;AAED,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,QAAQ,CAAC,IAAI,sBAA2B;IACxC,QAAQ,CAAC,UAAU,SAGmC;IACtD,YAAY,GAAG,CAAC,EAAE,MAAM,EAEvB;CACF;AAED,qBAAa,mBAAoB,SAAQ,UAAU;IACjD,QAAQ,CAAC,IAAI,qBAA0B;IACvC,QAAQ,CAAC,UAAU,SAEwD;IAC3E,YAAY,GAAG,CAAC,EAAE,MAAM,EAEvB;CACF;AAED,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,QAAQ,CAAC,IAAI,sBAA2B;IACxC,QAAQ,CAAC,UAAU,6FAA4F;IAC/G,YAAY,GAAG,CAAC,EAAE,MAAM,EAEvB;CACF;AAED,qBAAa,uBAAwB,SAAQ,UAAU;IACrD,QAAQ,CAAC,IAAI,0BAA+B;IAC5C,QAAQ,CAAC,UAAU,SAEwC;IAC3D,YAAY,GAAG,CAAC,EAAE,MAAM,EAEvB;CACF;AAED,qBAAa,iBAAkB,SAAQ,UAAU;IAC/C,QAAQ,CAAC,IAAI,aAAkB;IAC/B,QAAQ,CAAC,UAAU,gHAA+G;IAClI,YAAY,GAAG,CAAC,EAAE,MAAM,EAEvB;CACF"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * FiberError — typed error hierarchy for Fiber RPC failures.
3
+ *
4
+ * The Fiber RPC returns payment failures as raw `failed_error` strings.
5
+ * This module parses those strings into structured, actionable error
6
+ * classes so application code can branch on error type and surface
7
+ * meaningful messages to developers and users.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const err = FiberError.parse(payment.failed_error)
12
+ * if (err instanceof InsufficientCapacityError) {
13
+ * console.log(err.suggestion)
14
+ * }
15
+ * ```
16
+ */
17
+ // ── Base class ────────────────────────────────────────────────────────────────
18
+ export class FiberError extends Error {
19
+ raw;
20
+ constructor(message, raw) {
21
+ super(message);
22
+ this.name = this.constructor.name;
23
+ if (raw !== undefined) {
24
+ this.raw = raw;
25
+ }
26
+ Object.setPrototypeOf(this, new.target.prototype);
27
+ }
28
+ /**
29
+ * Parse a raw Fiber RPC failed_error string into a typed FiberError.
30
+ * Matches against known error patterns using normalised lowercase comparison.
31
+ *
32
+ * @param raw - The failed_error string from a Payment or RPC response
33
+ */
34
+ static parse(raw) {
35
+ const n = raw.toLowerCase();
36
+ if (n.includes('no_route') ||
37
+ n.includes('noroute') ||
38
+ n.includes('unknown_next_peer')) {
39
+ return new RouteNotFoundError(raw);
40
+ }
41
+ if (n.includes('insufficient') || n.includes('capacity')) {
42
+ return new InsufficientCapacityError(raw);
43
+ }
44
+ if (n.includes('temporary_channel_failure') ||
45
+ n.includes('temporarychannelfailure')) {
46
+ return new TemporaryChannelFailureError(raw);
47
+ }
48
+ if ((n.includes('peer') || n.includes('node')) &&
49
+ (n.includes('unreachable') ||
50
+ n.includes('disconnected') ||
51
+ n.includes('not_connected'))) {
52
+ return new PeerUnreachableError(raw);
53
+ }
54
+ if (n.includes('timeout') || n.includes('expir')) {
55
+ return new PaymentTimeoutError(raw);
56
+ }
57
+ if (n.includes('fee')) {
58
+ return new FeeInsufficientError(raw);
59
+ }
60
+ if (n.includes('amount') && n.includes('minimum')) {
61
+ return new AmountBelowMinimumError(raw);
62
+ }
63
+ return new UnknownFiberError(raw);
64
+ }
65
+ }
66
+ // ── Concrete error types ──────────────────────────────────────────────────────
67
+ export class RouteNotFoundError extends FiberError {
68
+ code = 'ROUTE_NOT_FOUND';
69
+ suggestion = 'No payment route exists to the destination. Verify the recipient ' +
70
+ 'has at least one open, public channel with sufficient inbound capacity, ' +
71
+ 'and that your node is connected to the Fiber network.';
72
+ constructor(raw) {
73
+ super('No route found to the payment destination.', raw);
74
+ }
75
+ }
76
+ export class InsufficientCapacityError extends FiberError {
77
+ code = 'INSUFFICIENT_CAPACITY';
78
+ suggestion = 'Outbound channel capacity is too low for this amount plus fees. ' +
79
+ 'Open a new channel with more funding, or ask your peer to push ' +
80
+ 'liquidity to your side on an existing channel.';
81
+ constructor(raw) {
82
+ super('Insufficient outbound capacity to route this payment.', raw);
83
+ }
84
+ }
85
+ export class TemporaryChannelFailureError extends FiberError {
86
+ code = 'TEMPORARY_CHANNEL_FAILURE';
87
+ suggestion = 'A channel on the route reported a transient failure. ' +
88
+ 'This is usually self-resolving — wait a few seconds and retry.';
89
+ constructor(raw) {
90
+ super('A channel on the payment route reported a temporary failure.', raw);
91
+ }
92
+ }
93
+ export class PeerUnreachableError extends FiberError {
94
+ code = 'PEER_UNREACHABLE';
95
+ suggestion = 'The destination peer appears offline or disconnected. ' +
96
+ 'Check your own node connectivity, then verify the recipient node ' +
97
+ 'is reachable on the Fiber network before retrying.';
98
+ constructor(raw) {
99
+ super('The destination peer is unreachable.', raw);
100
+ }
101
+ }
102
+ export class PaymentTimeoutError extends FiberError {
103
+ code = 'PAYMENT_TIMEOUT';
104
+ suggestion = 'The payment timed out before the destination acknowledged it. ' +
105
+ 'Retry with a higher timeout value or during quieter network conditions.';
106
+ constructor(raw) {
107
+ super('Payment timed out before completion.', raw);
108
+ }
109
+ }
110
+ export class FeeInsufficientError extends FiberError {
111
+ code = 'FEE_INSUFFICIENT';
112
+ suggestion = 'The fee offered was rejected by a node on the route. Increase max_fee_amount and retry.';
113
+ constructor(raw) {
114
+ super('Fee was insufficient for the discovered payment route.', raw);
115
+ }
116
+ }
117
+ export class AmountBelowMinimumError extends FiberError {
118
+ code = 'AMOUNT_BELOW_MINIMUM';
119
+ suggestion = 'The payment amount is below the minimum TLC value enforced by ' +
120
+ 'one or more channels on the route. Try a larger amount.';
121
+ constructor(raw) {
122
+ super('Payment amount is below the channel minimum.', raw);
123
+ }
124
+ }
125
+ export class UnknownFiberError extends FiberError {
126
+ code = 'UNKNOWN';
127
+ suggestion = 'An unrecognised error was returned by the Fiber node. Inspect the raw field for the original error string.';
128
+ constructor(raw) {
129
+ super('An unknown Fiber error occurred.', raw);
130
+ }
131
+ }
132
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/client/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * FiberClient — fully typed HTTP JSON-RPC client for Fiber Network Node (FNN).
3
+ *
4
+ * Communicates with a running FNN instance via its JSON-RPC endpoint
5
+ * (default: http://127.0.0.1:8227). Every method is typed against the
6
+ * official FNN RPC specification so callers get compile-time safety and
7
+ * inline documentation instead of raw invokeCommand() calls.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const client = new FiberClient('http://127.0.0.1:8227')
12
+ * const info = await client.nodeInfo()
13
+ * console.log(info.pubkey, info.channel_count)
14
+ * ```
15
+ */
16
+ import type { Channel, Payment, SendPaymentParams, InvoiceResult, NewInvoiceParams, ListPeersResult, GraphChannelsResult, NodeInfo, RouterHop, ParseInvoiceResult, Hash256 } from './types.js';
17
+ export declare class FiberClient {
18
+ private readonly rpcUrl;
19
+ /**
20
+ * @param rpcUrl - HTTP URL of the FNN RPC endpoint. Defaults to the standard
21
+ * local node address. Override for remote nodes or custom ports.
22
+ */
23
+ constructor(rpcUrl?: string);
24
+ /** Returns identifying information and operational stats for the running FNN node. */
25
+ nodeInfo(): Promise<NodeInfo>;
26
+ /** Lists all peers currently connected to this node. */
27
+ listPeers(): Promise<ListPeersResult>;
28
+ /**
29
+ * Connects to a remote Fiber peer by multiaddr.
30
+ * @param address - Multiaddr of the remote peer
31
+ * @param save - Persist this address for reconnection on restart
32
+ */
33
+ connectPeer(address: string, save?: boolean): Promise<void>;
34
+ /**
35
+ * Lists channels on this node, optionally filtered by peer pubkey.
36
+ * @param options.pubkey - Filter to channels with a specific peer
37
+ * @param options.include_closed - Include closed channels. Defaults to false.
38
+ */
39
+ listChannels(options?: {
40
+ pubkey?: string;
41
+ include_closed?: boolean;
42
+ }): Promise<Channel[]>;
43
+ /**
44
+ * Opens a new payment channel with a connected peer.
45
+ * @param params.pubkey - Peer pubkey to open the channel with
46
+ * @param params.funding_amount - CKB to lock in the channel (hex u128, shannon)
47
+ * @param params.public - Announce to the network graph
48
+ */
49
+ openChannel(params: {
50
+ pubkey: string;
51
+ funding_amount: string;
52
+ public?: boolean;
53
+ }): Promise<{
54
+ temporary_channel_id: Hash256;
55
+ }>;
56
+ /**
57
+ * Initiates cooperative shutdown of a channel.
58
+ * @param channel_id - Channel to close
59
+ * @param force - Force-close unilaterally. Incurs a time-lock penalty.
60
+ */
61
+ shutdownChannel(channel_id: Hash256, force?: boolean): Promise<void>;
62
+ /**
63
+ * Sends a payment or performs a dry-run feasibility check.
64
+ * Set dry_run: true to validate routing and estimate fees without
65
+ * broadcasting the payment.
66
+ */
67
+ sendPayment(params: SendPaymentParams): Promise<Payment>;
68
+ /**
69
+ * Fetches current status and details of a payment by its hash.
70
+ * Poll this after sendPayment() to track Inflight → Success/Failed transitions.
71
+ */
72
+ getPayment(payment_hash: Hash256): Promise<Payment>;
73
+ /**
74
+ * Builds and validates a payment route without sending.
75
+ * Use this to inspect hop details and confirm a path exists before committing.
76
+ */
77
+ buildRouter(params: {
78
+ amount?: string;
79
+ hops_info: {
80
+ pubkey: string;
81
+ channel_outpoint?: string;
82
+ }[];
83
+ }): Promise<{
84
+ router_hops: RouterHop[];
85
+ }>;
86
+ /**
87
+ * Creates a new Fiber invoice for receiving a payment.
88
+ * @param params.amount - Amount in shannon (hex u128)
89
+ * @param params.currency - Fibb (mainnet) | Fibt (testnet) | Fibd (devnet)
90
+ */
91
+ newInvoice(params: NewInvoiceParams): Promise<InvoiceResult>;
92
+ /** Fetches an invoice and its current payment status by payment hash. */
93
+ getInvoice(payment_hash: Hash256): Promise<InvoiceResult>;
94
+ /** Cancels an Open invoice, preventing it from being paid. */
95
+ cancelInvoice(payment_hash: Hash256): Promise<InvoiceResult>;
96
+ /**
97
+ * Fetches public channels from the Fiber network graph.
98
+ * Used by PaymentChecker to score route liquidity during canPay() analysis.
99
+ * @param limit - Maximum channels to return. Defaults to 100.
100
+ */
101
+ graphChannels(limit?: number, after?: string): Promise<GraphChannelsResult>;
102
+ /**
103
+ * Parses an encoded Fiber invoice string without attempting payment.
104
+ * Extracts amount, payment_hash, and the destination pubkey (payee_public_key)
105
+ * from the attrs array. Used by PaymentChecker.canPay() to resolve the
106
+ * payment destination before running graph reachability analysis.
107
+ *
108
+ * @param invoice - The encoded invoice string (fibb.../fibt.../fibd...)
109
+ */
110
+ parseInvoice(invoice: string): Promise<ParseInvoiceResult>;
111
+ }
112
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/client/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EACV,OAAO,EACP,OAAO,EACP,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAEhB,eAAe,EACf,mBAAmB,EACnB,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,OAAO,EACR,MAAM,YAAY,CAAA;AAqCnB,qBAAa,WAAW;IAKV,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC;;;OAGG;IACH,YAA6B,MAAM,GAAE,MAAgC,EAAI;IAIzE,sFAAsF;IAChF,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,CAElC;IAID,wDAAwD;IAClD,SAAS,IAAI,OAAO,CAAC,eAAe,CAAC,CAE1C;IAED;;;;OAIG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,UAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAE7D;IAID;;;;OAIG;IACG,YAAY,CAChB,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,OAAO,CAAA;KAAO,GAC1D,OAAO,CAAC,OAAO,EAAE,CAAC,CAGpB;IAED;;;;;OAKG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,MAAM,EAAW,MAAM,CAAA;QACvB,cAAc,EAAG,MAAM,CAAA;QACvB,MAAM,CAAC,EAAU,OAAO,CAAA;KACzB,GAAG,OAAO,CAAC;QAAE,oBAAoB,EAAE,OAAO,CAAA;KAAE,CAAC,CAE7C;IAED;;;;OAIG;IACG,eAAe,CAAC,UAAU,EAAE,OAAO,EAAE,KAAK,UAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAEvE;IAID;;;;OAIG;IACG,WAAW,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,OAAO,CAAC,CAE7D;IAED;;;OAGG;IACG,UAAU,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAExD;IAED;;;OAGG;IACG,WAAW,CAAC,MAAM,EAAE;QACxB,MAAM,CAAC,EAAI,MAAM,CAAA;QACjB,SAAS,EAAE;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;KAC3D,GAAG,OAAO,CAAC;QAAE,WAAW,EAAE,SAAS,EAAE,CAAA;KAAE,CAAC,CAExC;IAID;;;;OAIG;IACG,UAAU,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,aAAa,CAAC,CAEjE;IAED,yEAAyE;IACnE,UAAU,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAE9D;IAED,8DAA8D;IACxD,aAAa,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC,CAEjE;IAID;;;;OAIG;IACG,aAAa,CAAC,KAAK,SAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAM7E;IAID;;;;;;;OAOG;IACG,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAE/D;CACF"}