@fiber-dev-kit/core 0.1.0-rc.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.
@@ -0,0 +1,39 @@
1
+ # Contributing to @fiber-dev-kit/core
2
+
3
+ Thanks for helping improve the Fiber Dev Kit core package.
4
+
5
+ ## Scope
6
+
7
+ This package contains the shared TypeScript foundation for the devkit:
8
+
9
+ - Fiber JSON-RPC client helpers.
10
+ - Fiber node, peer, channel, invoice, and payment types.
11
+ - Amount conversion utilities.
12
+ - Payment diagnostics.
13
+ - Node alert rules.
14
+ - Route confidence helpers.
15
+
16
+ Keep changes focused on shared behavior that belongs below the inspector and test client packages.
17
+
18
+ ## Local Development
19
+
20
+ From the workspace root:
21
+
22
+ ```bash
23
+ npm install
24
+ npm run build --workspace @fiber-dev-kit/core
25
+ npm run typecheck --workspace @fiber-dev-kit/core
26
+ npm test --workspace @fiber-dev-kit/core
27
+ ```
28
+
29
+ ## Guidelines
30
+
31
+ - Preserve the public API unless the version is being intentionally bumped for a breaking change.
32
+ - Add or update tests for diagnostics, amount conversion, RPC wrapping, alert rules, and route confidence behavior.
33
+ - Keep Fiber RPC types close to the node API shape. Add ergonomic wrapper types separately when needed.
34
+ - Avoid introducing browser-only dependencies. This package targets Node.js 18 and newer.
35
+ - Prefer small, explicit helpers over broad abstractions.
36
+
37
+ ## Release Notes
38
+
39
+ When changing behavior, document the user-facing impact in the pull request or release notes. Include migration notes for renamed exports, changed error codes, or changed amount handling.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fiber Dev Kit contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @fiber-dev-kit/core
2
+
3
+ Network-aware TypeScript helpers for working with Fiber Network Node JSON-RPC APIs.
4
+
5
+ This package is not a replacement for official Fiber SDKs. It is support infrastructure for local node diagnostics, test automation, and hackathon-grade developer tooling.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install @fiber-dev-kit/core
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import {
17
+ FiberClient,
18
+ diagnosePaymentFailure,
19
+ evaluateNodeAlerts,
20
+ routeConfidence,
21
+ } from "@fiber-dev-kit/core";
22
+
23
+ const node = new FiberClient({
24
+ name: "a",
25
+ url: "http://127.0.0.1:8227",
26
+ });
27
+
28
+ const info = await node.getInfo();
29
+ const peers = await node.listPeers();
30
+ const channels = await node.listChannels();
31
+
32
+ const alerts = evaluateNodeAlerts({
33
+ name: "a",
34
+ info,
35
+ peers,
36
+ channels,
37
+ });
38
+
39
+ const confidence = await routeConfidence({
40
+ from: node,
41
+ to: "0372...",
42
+ amount: "1",
43
+ });
44
+
45
+ try {
46
+ await node.sendPayment({ invoice: "..." });
47
+ } catch (error) {
48
+ console.error(diagnosePaymentFailure(error));
49
+ }
50
+ ```
51
+
52
+ ## What it provides
53
+
54
+ - A small Fiber JSON-RPC client.
55
+ - Amount conversion helpers for CKB/shannons.
56
+ - Payment failure diagnosis helpers.
57
+ - Node and channel alert rules.
58
+ - Route confidence and `canPay` style reports for local testing.
59
+ - Typed Fiber node, channel, invoice, and payment shapes used by the devkit packages.
60
+
61
+ ## Requirements
62
+
63
+ - Node.js 18 or newer.
64
+ - A reachable Fiber node JSON-RPC endpoint.
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,37 @@
1
+ import { type Diagnosis } from "./diagnostics";
2
+ import type { Channel } from "./types/channel";
3
+ import type { HexString } from "./types/common";
4
+ import type { NodeInfo, PeerInfo } from "./types/node";
5
+ import type { PaymentResult } from "./types/payment";
6
+ export type AlertSeverity = "info" | "warning" | "critical";
7
+ export type AlertCode = "NODE_UNREACHABLE" | "ZERO_PEERS" | "NO_READY_CHANNELS" | "LOW_LOCAL_BALANCE" | "PAYMENT_FAILED";
8
+ export interface Alert {
9
+ code: AlertCode;
10
+ severity: AlertSeverity;
11
+ summary: string;
12
+ suggestion: string;
13
+ nodeId?: string;
14
+ channelId?: HexString;
15
+ paymentHash?: HexString;
16
+ diagnosis?: Diagnosis | null;
17
+ }
18
+ export interface AlertRules {
19
+ /** Emit ZERO_PEERS when true. Default: true. */
20
+ requirePeers?: boolean;
21
+ /** Emit NO_READY_CHANNELS when true. Default: true. */
22
+ requireReadyChannels?: boolean;
23
+ /** Emit LOW_LOCAL_BALANCE below this ready-channel outbound balance. Default: 1 CKB. */
24
+ minLocalBalanceCkb?: number;
25
+ /** Emit PAYMENT_FAILED for recent failed payments when true. Default: true. */
26
+ includeFailedPayments?: boolean;
27
+ }
28
+ export interface AlertSnapshot {
29
+ nodeId?: string;
30
+ node?: NodeInfo;
31
+ nodeError?: unknown;
32
+ peers?: PeerInfo[];
33
+ channels?: Channel[];
34
+ payments?: PaymentResult[];
35
+ }
36
+ /** Evaluates a node snapshot into actionable operational alerts for CLIs, UIs, or tests. */
37
+ export declare function evaluateAlerts(snapshot: AlertSnapshot, rules?: AlertRules): Alert[];
@@ -0,0 +1,86 @@
1
+ import type { HexString } from "./types/common";
2
+ import type { ConnectPeerParams, DisconnectPeerParams, NodeInfo, PeerInfo } from "./types/node";
3
+ import type { AbandonChannelParams, AcceptChannelParams, AcceptChannelResult, Channel, ListChannelsParams, OpenChannelParams, OpenChannelResult, ShutdownChannelParams, UpdateChannelParams } from "./types/channel";
4
+ import type { CancelInvoiceParams, CkbInvoice, GetInvoiceResult, InvoiceCurrency, NewInvoiceParams, NewInvoiceResult, SettleInvoiceParams } from "./types/invoice";
5
+ import type { GetPaymentParams, ListPaymentsParams, PaymentResult, SendPaymentParams } from "./types/payment";
6
+ import type { NetworkMode } from "./types/network";
7
+ export interface FiberClientConfig {
8
+ /** RPC endpoint of the node, e.g. `http://127.0.0.1:8227`. */
9
+ nodeUrl: string;
10
+ /** Bearer token, if the node's RPC has auth enabled. */
11
+ authToken?: string;
12
+ /** Request timeout in ms. Default: 30000. */
13
+ timeoutMs?: number;
14
+ /** Currency used by `createInvoice`/`sendPayment` when not given explicitly. Default: `Fibt` (testnet). */
15
+ defaultCurrency?: InvoiceCurrency;
16
+ /**
17
+ * Which network `nodeUrl` points at. Unset means "unknown" and no guard is applied — set
18
+ * this to `"mainnet"` to get the write-guard below for free; it costs nothing on
19
+ * devnet/testnet.
20
+ */
21
+ network?: NetworkMode;
22
+ /**
23
+ * Explicit opt-in to run fund-moving calls (`openChannel`, `acceptChannel`,
24
+ * `abandonChannel`, `shutdownChannel`, `sendPayment`/`payInvoice`) when `network` is
25
+ * `"mainnet"`. Default: false. This exists to catch the mistake that actually loses
26
+ * money — a test/demo script pointed at the wrong `nodeUrl` — not to stop legitimate
27
+ * mainnet wallets or merchant tooling from using this client; set it once, deliberately,
28
+ * wherever real production traffic is intended.
29
+ */
30
+ allowMainnetWrites?: boolean;
31
+ }
32
+ /** Amount accepted by ergonomic methods: a human CKB amount, or an already-hex shannon amount. */
33
+ export type AmountLike = number | HexString;
34
+ /**
35
+ * Ergonomic TypeScript client for the Fiber Network Node (FNN) JSON-RPC.
36
+ *
37
+ * ```ts
38
+ * const fiber = new FiberClient("http://127.0.0.1:8227");
39
+ * await fiber.info();
40
+ * await fiber.connectPeer({ address: "/ip4/.../p2p/..." });
41
+ * await fiber.openChannel({ pubkey, fundingAmount: 500 });
42
+ * const { invoice_address } = await fiber.createInvoice({ amount: 10, description: "coffee" });
43
+ * await fiber.payInvoice(invoice_address);
44
+ * await fiber.listChannels();
45
+ * ```
46
+ */
47
+ export declare class FiberClient {
48
+ private readonly nodeUrl;
49
+ private readonly authToken?;
50
+ private readonly timeoutMs;
51
+ private readonly defaultCurrency;
52
+ private readonly network?;
53
+ private readonly allowMainnetWrites;
54
+ constructor(config: string | FiberClientConfig);
55
+ info(): Promise<NodeInfo>;
56
+ listPeers(): Promise<PeerInfo[]>;
57
+ connectPeer(params: ConnectPeerParams): Promise<void>;
58
+ disconnectPeer(params: DisconnectPeerParams | HexString): Promise<void>;
59
+ openChannel(params: Omit<OpenChannelParams, "fundingAmount"> & {
60
+ fundingAmount: AmountLike;
61
+ }): Promise<OpenChannelResult>;
62
+ acceptChannel(params: Omit<AcceptChannelParams, "fundingAmount"> & {
63
+ fundingAmount: AmountLike;
64
+ }): Promise<AcceptChannelResult>;
65
+ abandonChannel(params: AbandonChannelParams | HexString): Promise<void>;
66
+ listChannels(params?: ListChannelsParams): Promise<Channel[]>;
67
+ shutdownChannel(params: ShutdownChannelParams): Promise<void>;
68
+ updateChannel(params: UpdateChannelParams): Promise<void>;
69
+ createInvoice(params: Omit<NewInvoiceParams, "amount" | "currency"> & {
70
+ amount: AmountLike;
71
+ currency?: InvoiceCurrency;
72
+ }): Promise<NewInvoiceResult>;
73
+ parseInvoice(invoice: string): Promise<CkbInvoice>;
74
+ getInvoice(paymentHash: HexString): Promise<GetInvoiceResult>;
75
+ cancelInvoice(params: CancelInvoiceParams | HexString): Promise<void>;
76
+ settleInvoice(params: SettleInvoiceParams): Promise<void>;
77
+ sendPayment(params: Omit<SendPaymentParams, "amount"> & {
78
+ amount?: AmountLike;
79
+ }): Promise<PaymentResult>;
80
+ /** Convenience wrapper: pay an encoded invoice (as returned by `createInvoice`'s `invoice_address`). */
81
+ payInvoice(invoice: string, opts?: Omit<SendPaymentParams, "invoice" | "targetPubkey">): Promise<PaymentResult>;
82
+ getPayment(params: GetPaymentParams | HexString): Promise<PaymentResult>;
83
+ listPayments(params?: ListPaymentsParams): Promise<PaymentResult[]>;
84
+ private guardWriteOp;
85
+ private call;
86
+ }
@@ -0,0 +1,14 @@
1
+ import { FiberError } from "./errors";
2
+ import type { PaymentResult } from "./types/payment";
3
+ export type DiagnosisCode = "INSUFFICIENT_LIQUIDITY" | "ROUTE_NOT_FOUND" | "PEER_NOT_CONNECTED" | "PEER_UNREACHABLE" | "INVOICE_EXPIRED" | "INVOICE_ALREADY_PAID" | "INVOICE_CANCELLED" | "INVALID_PARAMS" | "TIMEOUT" | "UNKNOWN";
4
+ export interface Diagnosis {
5
+ code: DiagnosisCode;
6
+ /** Plain-English explanation of what likely happened. */
7
+ summary: string;
8
+ /** A concrete next step to try. */
9
+ suggestion: string;
10
+ }
11
+ /** Turns a `FiberError` into a structured, actionable diagnosis for logs, CLIs, or UIs. */
12
+ export declare function diagnose(error: FiberError): Diagnosis;
13
+ /** Diagnoses a `Failed` payment's `failed_error` field. Returns `null` for non-failed payments. */
14
+ export declare function diagnosePayment(payment: PaymentResult): Diagnosis | null;
@@ -0,0 +1,7 @@
1
+ export type FiberErrorCode = "NETWORK_ERROR" | "RPC_ERROR" | "INVALID_RESPONSE" | "REQUEST_TIMEOUT" | "MAINNET_WRITE_BLOCKED";
2
+ export declare class FiberError extends Error {
3
+ readonly code: FiberErrorCode;
4
+ readonly context?: Record<string, unknown> | undefined;
5
+ constructor(code: FiberErrorCode, message: string, context?: Record<string, unknown> | undefined);
6
+ static is(err: unknown): err is FiberError;
7
+ }
@@ -0,0 +1,64 @@
1
+ import { type Diagnosis } from "./diagnostics";
2
+ import type { FiberClient } from "./client";
3
+ import type { Channel } from "./types/channel";
4
+ import type { PaymentResult } from "./types/payment";
5
+ export type FiberEvent = {
6
+ type: "channel.opened";
7
+ channel: Channel;
8
+ } | {
9
+ type: "channel.updated";
10
+ channel: Channel;
11
+ } | {
12
+ type: "channel.closed";
13
+ channel: Channel;
14
+ } | {
15
+ type: "payment.created";
16
+ payment: PaymentResult;
17
+ } | {
18
+ type: "payment.updated";
19
+ payment: PaymentResult;
20
+ } | {
21
+ type: "payment.succeeded";
22
+ payment: PaymentResult;
23
+ } | {
24
+ type: "payment.failed";
25
+ payment: PaymentResult;
26
+ diagnosis: Diagnosis | null;
27
+ };
28
+ export interface FiberEventClientConfig {
29
+ client: FiberClient;
30
+ /** How often to poll `listChannels`/`listPayments` for changes, in ms. Default: 1000. */
31
+ pollIntervalMs?: number;
32
+ /**
33
+ * Called when a poll cycle fails (transient RPC/network error). Polling continues on the
34
+ * next interval regardless — a single flaky call must never take down a long-running
35
+ * consumer (the inspector server, a CI test run). Default: swallowed silently.
36
+ */
37
+ onError?: (error: unknown) => void;
38
+ }
39
+ /**
40
+ * FNN's RPC exposes no server-push subscription channel for clients (the node's WebSocket
41
+ * support is for the Fiber peer-to-peer transport, not for RPC event delivery). This client
42
+ * approximates live events by polling `listChannels`/`listPayments` on an interval and
43
+ * diffing snapshots, so downstream consumers (the inspector UI, test-client assertions) can
44
+ * still subscribe to a typed event stream.
45
+ */
46
+ export declare class FiberEventClient {
47
+ private readonly client;
48
+ private readonly pollIntervalMs;
49
+ private readonly onError;
50
+ private readonly handlers;
51
+ private timer;
52
+ private lastChannels;
53
+ private lastPayments;
54
+ private polling;
55
+ constructor(config: FiberEventClientConfig);
56
+ start(): void;
57
+ stop(): void;
58
+ /** Subscribes to an event type. Returns an unsubscribe function. */
59
+ on(type: FiberEvent["type"], handler: (event: FiberEvent) => void): () => void;
60
+ private emit;
61
+ private poll;
62
+ private pollChannels;
63
+ private pollPayments;
64
+ }
@@ -0,0 +1,17 @@
1
+ export { FiberClient } from "./client";
2
+ export type { FiberClientConfig, AmountLike } from "./client";
3
+ export { FiberEventClient } from "./events";
4
+ export type { FiberEvent, FiberEventClientConfig } from "./events";
5
+ export { FiberError } from "./errors";
6
+ export type { FiberErrorCode } from "./errors";
7
+ export { diagnose, diagnosePayment } from "./diagnostics";
8
+ export type { Diagnosis, DiagnosisCode } from "./diagnostics";
9
+ export { evaluateAlerts } from "./alerts";
10
+ export type { Alert, AlertCode, AlertRules, AlertSeverity, AlertSnapshot } from "./alerts";
11
+ export { ckbToShannonHex, shannonHexToCkb, formatAmount, toHex, fromHex } from "./utils";
12
+ export type { HexString, Script, Ckb } from "./types/common";
13
+ export type { NetworkMode } from "./types/network";
14
+ export type { NodeInfo, PeerInfo, ConnectPeerParams, DisconnectPeerParams, PeerAddressType, UdtCfgInfo, UdtCellDep } from "./types/node";
15
+ export type { Channel, ChannelState, OpenChannelParams, OpenChannelResult, AcceptChannelParams, AcceptChannelResult, ListChannelsParams, ListChannelsResult, AbandonChannelParams, ShutdownChannelParams, UpdateChannelParams, } from "./types/channel";
16
+ export type { CkbInvoice, CkbInvoiceData, InvoiceCurrency, InvoiceStatus, HashAlgorithm, NewInvoiceParams, NewInvoiceResult, GetInvoiceResult, ParseInvoiceParams, ParseInvoiceResult, CancelInvoiceParams, SettleInvoiceParams, } from "./types/invoice";
17
+ export type { PaymentStatus, PaymentResult, SendPaymentParams, HopHint, GetPaymentParams, ListPaymentsParams, ListPaymentsResult, } from "./types/payment";
package/dist/index.js ADDED
@@ -0,0 +1,490 @@
1
+ // src/errors.ts
2
+ var FiberError = class _FiberError extends Error {
3
+ constructor(code, message, context) {
4
+ super(message);
5
+ this.code = code;
6
+ this.context = context;
7
+ this.name = "FiberError";
8
+ }
9
+ code;
10
+ context;
11
+ static is(err) {
12
+ return err instanceof _FiberError;
13
+ }
14
+ };
15
+
16
+ // src/utils.ts
17
+ var SHANNONS_PER_CKB = 100000000n;
18
+ function ckbToShannonHex(ckb) {
19
+ if (!Number.isFinite(ckb) || ckb < 0) {
20
+ throw new RangeError(`ckbToShannonHex: amount must be a non-negative finite number, got ${ckb}`);
21
+ }
22
+ const shannons = BigInt(Math.round(ckb * Number(SHANNONS_PER_CKB)));
23
+ return toHex(shannons);
24
+ }
25
+ function shannonHexToCkb(hex) {
26
+ const shannons = fromHex(hex);
27
+ return Number(shannons) / Number(SHANNONS_PER_CKB);
28
+ }
29
+ function formatAmount(hex, unit = "CKB") {
30
+ if (unit === "shannon") {
31
+ return `${fromHex(hex).toString()} shannon`;
32
+ }
33
+ return `${shannonHexToCkb(hex)} CKB`;
34
+ }
35
+ function toHex(value) {
36
+ const big = typeof value === "bigint" ? value : BigInt(value);
37
+ if (big < 0n) {
38
+ throw new RangeError(`toHex: value must be non-negative, got ${big}`);
39
+ }
40
+ return `0x${big.toString(16)}`;
41
+ }
42
+ function fromHex(hex) {
43
+ return BigInt(hex);
44
+ }
45
+
46
+ // src/client.ts
47
+ function resolveAmount(amount) {
48
+ return typeof amount === "number" ? ckbToShannonHex(amount) : amount;
49
+ }
50
+ function isPlainObject(value) {
51
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52
+ }
53
+ function camelToSnakeKey(key) {
54
+ return key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
55
+ }
56
+ function toWireParams(value) {
57
+ if (Array.isArray(value)) {
58
+ return value.map(toWireParams);
59
+ }
60
+ if (isPlainObject(value)) {
61
+ const result = {};
62
+ for (const [key, val] of Object.entries(value)) {
63
+ if (val === void 0) continue;
64
+ result[camelToSnakeKey(key)] = toWireParams(val);
65
+ }
66
+ return result;
67
+ }
68
+ if (typeof value === "number") {
69
+ return toHex(value);
70
+ }
71
+ return value;
72
+ }
73
+ var FiberClient = class {
74
+ nodeUrl;
75
+ authToken;
76
+ timeoutMs;
77
+ defaultCurrency;
78
+ network;
79
+ allowMainnetWrites;
80
+ constructor(config) {
81
+ const resolved = typeof config === "string" ? { nodeUrl: config } : config;
82
+ this.nodeUrl = resolved.nodeUrl;
83
+ this.authToken = resolved.authToken;
84
+ this.timeoutMs = resolved.timeoutMs ?? 3e4;
85
+ this.defaultCurrency = resolved.defaultCurrency ?? "Fibt";
86
+ this.network = resolved.network;
87
+ this.allowMainnetWrites = resolved.allowMainnetWrites ?? false;
88
+ }
89
+ // ── Node ──────────────────────────────────────────────────
90
+ info() {
91
+ return this.call("node_info", []);
92
+ }
93
+ async listPeers() {
94
+ const result = await this.call("list_peers", []);
95
+ return result.peers;
96
+ }
97
+ async connectPeer(params) {
98
+ await this.call("connect_peer", [toWireParams(params)]);
99
+ }
100
+ async disconnectPeer(params) {
101
+ const wire = typeof params === "string" ? { pubkey: params } : params;
102
+ await this.call("disconnect_peer", [toWireParams(wire)]);
103
+ }
104
+ // ── Channels ──────────────────────────────────────────────
105
+ openChannel(params) {
106
+ this.guardWriteOp("openChannel");
107
+ return this.call("open_channel", [
108
+ toWireParams({ ...params, fundingAmount: resolveAmount(params.fundingAmount) })
109
+ ]);
110
+ }
111
+ acceptChannel(params) {
112
+ this.guardWriteOp("acceptChannel");
113
+ return this.call("accept_channel", [
114
+ toWireParams({ ...params, fundingAmount: resolveAmount(params.fundingAmount) })
115
+ ]);
116
+ }
117
+ async abandonChannel(params) {
118
+ this.guardWriteOp("abandonChannel");
119
+ const wire = typeof params === "string" ? { channelId: params } : params;
120
+ await this.call("abandon_channel", [toWireParams(wire)]);
121
+ }
122
+ async listChannels(params = {}) {
123
+ const result = await this.call("list_channels", [toWireParams(params)]);
124
+ return result.channels;
125
+ }
126
+ async shutdownChannel(params) {
127
+ this.guardWriteOp("shutdownChannel");
128
+ await this.call("shutdown_channel", [toWireParams(params)]);
129
+ }
130
+ async updateChannel(params) {
131
+ await this.call("update_channel", [toWireParams(params)]);
132
+ }
133
+ // ── Invoices ──────────────────────────────────────────────
134
+ createInvoice(params) {
135
+ return this.call("new_invoice", [
136
+ toWireParams({
137
+ ...params,
138
+ amount: resolveAmount(params.amount),
139
+ currency: params.currency ?? this.defaultCurrency
140
+ })
141
+ ]);
142
+ }
143
+ async parseInvoice(invoice) {
144
+ const result = await this.call("parse_invoice", [{ invoice }]);
145
+ return result.invoice;
146
+ }
147
+ getInvoice(paymentHash) {
148
+ return this.call("get_invoice", [{ payment_hash: paymentHash }]);
149
+ }
150
+ async cancelInvoice(params) {
151
+ const wire = typeof params === "string" ? { paymentHash: params } : params;
152
+ await this.call("cancel_invoice", [toWireParams(wire)]);
153
+ }
154
+ async settleInvoice(params) {
155
+ await this.call("settle_invoice", [toWireParams(params)]);
156
+ }
157
+ // ── Payments ──────────────────────────────────────────────
158
+ sendPayment(params) {
159
+ this.guardWriteOp("sendPayment");
160
+ return this.call("send_payment", [
161
+ toWireParams({
162
+ ...params,
163
+ amount: params.amount === void 0 ? void 0 : resolveAmount(params.amount)
164
+ })
165
+ ]);
166
+ }
167
+ /** Convenience wrapper: pay an encoded invoice (as returned by `createInvoice`'s `invoice_address`). */
168
+ payInvoice(invoice, opts = {}) {
169
+ return this.sendPayment({ ...opts, invoice });
170
+ }
171
+ getPayment(params) {
172
+ const wire = typeof params === "string" ? { paymentHash: params } : params;
173
+ return this.call("get_payment", [toWireParams(wire)]);
174
+ }
175
+ async listPayments(params = {}) {
176
+ const result = await this.call("list_payments", [toWireParams(params)]);
177
+ return result.payments;
178
+ }
179
+ // ── Internal ──────────────────────────────────────────────
180
+ guardWriteOp(method) {
181
+ if (this.network === "mainnet" && !this.allowMainnetWrites) {
182
+ throw new FiberError(
183
+ "MAINNET_WRITE_BLOCKED",
184
+ `${method}() is blocked because this client is configured for network: "mainnet". If this is intentional production traffic, set allowMainnetWrites: true in the FiberClient config.`,
185
+ { method }
186
+ );
187
+ }
188
+ }
189
+ async call(method, params) {
190
+ let response;
191
+ try {
192
+ response = await fetch(this.nodeUrl, {
193
+ method: "POST",
194
+ headers: {
195
+ "Content-Type": "application/json",
196
+ ...this.authToken ? { Authorization: `Bearer ${this.authToken}` } : {}
197
+ },
198
+ body: JSON.stringify({ jsonrpc: "2.0", id: Date.now(), method, params }),
199
+ signal: AbortSignal.timeout(this.timeoutMs)
200
+ });
201
+ } catch (err) {
202
+ if (err instanceof Error && err.name === "TimeoutError") {
203
+ throw new FiberError("REQUEST_TIMEOUT", `RPC call to ${method} exceeded ${this.timeoutMs}ms`, { method });
204
+ }
205
+ throw new FiberError("NETWORK_ERROR", `Failed to reach ${this.nodeUrl}: ${err.message}`, {
206
+ method,
207
+ cause: err
208
+ });
209
+ }
210
+ if (!response.ok) {
211
+ throw new FiberError("NETWORK_ERROR", `HTTP ${response.status} from ${this.nodeUrl}`, {
212
+ method,
213
+ status: response.status
214
+ });
215
+ }
216
+ let data;
217
+ try {
218
+ data = await response.json();
219
+ } catch (err) {
220
+ throw new FiberError("INVALID_RESPONSE", `Non-JSON response from ${this.nodeUrl}`, { method, cause: err });
221
+ }
222
+ if (data.error) {
223
+ throw new FiberError("RPC_ERROR", data.error.message, {
224
+ method,
225
+ rpcCode: data.error.code,
226
+ rpcData: data.error.data
227
+ });
228
+ }
229
+ return data.result;
230
+ }
231
+ };
232
+
233
+ // src/diagnostics.ts
234
+ var PATTERNS = [
235
+ {
236
+ code: "INSUFFICIENT_LIQUIDITY",
237
+ test: /insufficient|liquidity|not enough balance|exceeds.*balance/i,
238
+ summary: "The route has capacity in total, but not enough usable balance in the direction this payment needs to flow.",
239
+ suggestion: "Reduce the payment amount, open a larger channel, or rebalance existing channels toward the recipient."
240
+ },
241
+ {
242
+ code: "ROUTE_NOT_FOUND",
243
+ test: /no route|route.*not found|routing.*fail|unable to find a route/i,
244
+ summary: "No path could be found from this node to the target through the currently known network graph.",
245
+ suggestion: "Confirm the target node has at least one public channel, or connect directly and open a channel to it."
246
+ },
247
+ {
248
+ code: "PEER_NOT_CONNECTED",
249
+ test: /peer.*not (found|connected)|not connected to peer/i,
250
+ summary: "The RPC call targets a peer this node hasn't connected to yet.",
251
+ suggestion: "Call connectPeer() with the peer's multiaddr or pubkey before retrying this operation."
252
+ },
253
+ {
254
+ code: "PEER_UNREACHABLE",
255
+ test: /connection refused|dial.*fail|unreachable|timed out.*connect/i,
256
+ summary: "The node could not establish a network connection to the peer's address.",
257
+ suggestion: "Check the peer's multiaddr/port and confirm it's online and reachable from this node."
258
+ },
259
+ {
260
+ code: "INVOICE_EXPIRED",
261
+ test: /invoice.*expired|expired.*invoice/i,
262
+ summary: "The invoice's expiry window has passed.",
263
+ suggestion: "Ask the recipient to generate a new invoice with createInvoice()."
264
+ },
265
+ {
266
+ code: "INVOICE_ALREADY_PAID",
267
+ test: /already paid|invoice.*paid/i,
268
+ summary: "This invoice has already been settled.",
269
+ suggestion: "No action needed \u2014 check getPayment()/getInvoice() for the existing settlement."
270
+ },
271
+ {
272
+ code: "INVOICE_CANCELLED",
273
+ test: /invoice.*cancel/i,
274
+ summary: "This invoice was cancelled and can no longer be paid.",
275
+ suggestion: "Ask the recipient to generate a new invoice."
276
+ },
277
+ {
278
+ code: "INVALID_PARAMS",
279
+ // "invalid pubkey '...': malformed public key" is a confirmed live FNN error string.
280
+ test: /missing field|invalid params|invalid type|invalid \w+|malformed|expected/i,
281
+ summary: "The RPC call was rejected because a parameter was missing, malformed, or the wrong type.",
282
+ suggestion: "Inspect the error's context.rpcData for the exact field FNN rejected."
283
+ }
284
+ ];
285
+ function diagnose(error) {
286
+ if (error.code === "REQUEST_TIMEOUT") {
287
+ return {
288
+ code: "TIMEOUT",
289
+ summary: "The RPC call exceeded its timeout before the node responded.",
290
+ suggestion: "Retry with a longer timeoutMs, or check whether the node is under heavy load."
291
+ };
292
+ }
293
+ const haystack = `${error.message} ${JSON.stringify(error.context?.rpcData ?? "")}`;
294
+ const match = PATTERNS.find((pattern) => pattern.test.test(haystack));
295
+ if (match) {
296
+ return { code: match.code, summary: match.summary, suggestion: match.suggestion };
297
+ }
298
+ return {
299
+ code: "UNKNOWN",
300
+ summary: error.message,
301
+ suggestion: "No known pattern matched this error; inspect error.context for the raw RPC response."
302
+ };
303
+ }
304
+ function diagnosePayment(payment) {
305
+ if (payment.status !== "Failed" || !payment.failed_error) return null;
306
+ return diagnose(new FiberError("RPC_ERROR", payment.failed_error));
307
+ }
308
+
309
+ // src/events.ts
310
+ var FiberEventClient = class {
311
+ client;
312
+ pollIntervalMs;
313
+ onError;
314
+ handlers = /* @__PURE__ */ new Map();
315
+ timer = null;
316
+ lastChannels = /* @__PURE__ */ new Map();
317
+ lastPayments = /* @__PURE__ */ new Map();
318
+ polling = false;
319
+ constructor(config) {
320
+ this.client = config.client;
321
+ this.pollIntervalMs = config.pollIntervalMs ?? 1e3;
322
+ this.onError = config.onError ?? (() => {
323
+ });
324
+ }
325
+ start() {
326
+ if (this.timer) return;
327
+ this.timer = setInterval(() => void this.poll(), this.pollIntervalMs);
328
+ void this.poll();
329
+ }
330
+ stop() {
331
+ if (this.timer) {
332
+ clearInterval(this.timer);
333
+ this.timer = null;
334
+ }
335
+ }
336
+ /** Subscribes to an event type. Returns an unsubscribe function. */
337
+ on(type, handler) {
338
+ if (!this.handlers.has(type)) {
339
+ this.handlers.set(type, /* @__PURE__ */ new Set());
340
+ }
341
+ this.handlers.get(type).add(handler);
342
+ return () => this.handlers.get(type)?.delete(handler);
343
+ }
344
+ emit(event) {
345
+ for (const handler of this.handlers.get(event.type) ?? []) {
346
+ handler(event);
347
+ }
348
+ }
349
+ async poll() {
350
+ if (this.polling) return;
351
+ this.polling = true;
352
+ try {
353
+ await Promise.all([this.pollChannels(), this.pollPayments()]);
354
+ } catch (err) {
355
+ this.onError(err);
356
+ } finally {
357
+ this.polling = false;
358
+ }
359
+ }
360
+ async pollChannels() {
361
+ const channels = await this.client.listChannels({ includeClosed: true });
362
+ const seen = /* @__PURE__ */ new Set();
363
+ for (const channel of channels) {
364
+ seen.add(channel.channel_id);
365
+ const previous = this.lastChannels.get(channel.channel_id);
366
+ const state = channelStateName(channel);
367
+ if (!previous) {
368
+ this.emit({ type: "channel.opened", channel });
369
+ } else if (channelStateName(previous) !== state) {
370
+ this.emit({ type: state === "Closed" ? "channel.closed" : "channel.updated", channel });
371
+ }
372
+ this.lastChannels.set(channel.channel_id, channel);
373
+ }
374
+ for (const [id, channel] of this.lastChannels) {
375
+ if (!seen.has(id)) {
376
+ this.emit({ type: "channel.closed", channel });
377
+ this.lastChannels.delete(id);
378
+ }
379
+ }
380
+ }
381
+ async pollPayments() {
382
+ const payments = await this.client.listPayments({ limit: 50 });
383
+ for (const payment of payments) {
384
+ const previous = this.lastPayments.get(payment.payment_hash);
385
+ if (!previous) {
386
+ this.emit({ type: "payment.created", payment });
387
+ } else if (previous.status !== payment.status) {
388
+ this.emit({ type: "payment.updated", payment });
389
+ if (payment.status === "Success") this.emit({ type: "payment.succeeded", payment });
390
+ if (payment.status === "Failed") {
391
+ this.emit({ type: "payment.failed", payment, diagnosis: diagnosePayment(payment) });
392
+ }
393
+ }
394
+ this.lastPayments.set(payment.payment_hash, payment);
395
+ }
396
+ }
397
+ };
398
+ function channelStateName(channel) {
399
+ return channel.state.state_name;
400
+ }
401
+
402
+ // src/alerts.ts
403
+ var DEFAULT_ALERT_RULES = {
404
+ requirePeers: true,
405
+ requireReadyChannels: true,
406
+ minLocalBalanceCkb: 1,
407
+ includeFailedPayments: true
408
+ };
409
+ function evaluateAlerts(snapshot, rules = {}) {
410
+ const resolved = { ...DEFAULT_ALERT_RULES, ...rules };
411
+ const alerts = [];
412
+ const nodeId = snapshot.nodeId;
413
+ if (snapshot.nodeError || !snapshot.node) {
414
+ return [
415
+ {
416
+ code: "NODE_UNREACHABLE",
417
+ severity: "critical",
418
+ nodeId,
419
+ summary: `Node${nodeId ? ` "${nodeId}"` : ""} is not reachable over RPC.`,
420
+ suggestion: "Confirm the FNN process is running and that the RPC URL, port, and auth settings are correct."
421
+ }
422
+ ];
423
+ }
424
+ const peers = snapshot.peers ?? [];
425
+ if (resolved.requirePeers && peers.length === 0 && fromHex(snapshot.node.peers_count) === 0n) {
426
+ alerts.push({
427
+ code: "ZERO_PEERS",
428
+ severity: "warning",
429
+ nodeId,
430
+ summary: `Node${nodeId ? ` "${nodeId}"` : ""} has no connected peers.`,
431
+ suggestion: "Connect at least one reachable Fiber peer before opening channels or testing routed payments."
432
+ });
433
+ }
434
+ const channels = snapshot.channels ?? [];
435
+ const readyChannels = channels.filter((channel) => channel.state.state_name === "ChannelReady");
436
+ if (resolved.requireReadyChannels && readyChannels.length === 0) {
437
+ alerts.push({
438
+ code: "NO_READY_CHANNELS",
439
+ severity: "warning",
440
+ nodeId,
441
+ summary: `Node${nodeId ? ` "${nodeId}"` : ""} has no ready channels.`,
442
+ suggestion: "Open a channel and wait until it reaches ChannelReady before testing payments."
443
+ });
444
+ }
445
+ for (const channel of readyChannels) {
446
+ const localBalance = shannonHexToCkb(channel.local_balance);
447
+ if (localBalance < resolved.minLocalBalanceCkb) {
448
+ alerts.push({
449
+ code: "LOW_LOCAL_BALANCE",
450
+ severity: "warning",
451
+ nodeId,
452
+ channelId: channel.channel_id,
453
+ summary: `Channel ${shortId(channel.channel_id)} has only ${localBalance} CKB of outbound balance.`,
454
+ suggestion: "Rebalance the channel, reduce payment size, or open a channel with more local liquidity."
455
+ });
456
+ }
457
+ }
458
+ if (resolved.includeFailedPayments) {
459
+ for (const payment of snapshot.payments ?? []) {
460
+ if (payment.status !== "Failed") continue;
461
+ const diagnosis = diagnosePayment(payment);
462
+ alerts.push({
463
+ code: "PAYMENT_FAILED",
464
+ severity: "critical",
465
+ nodeId,
466
+ paymentHash: payment.payment_hash,
467
+ diagnosis,
468
+ summary: diagnosis?.summary ?? `Payment ${shortId(payment.payment_hash)} failed.`,
469
+ suggestion: diagnosis?.suggestion ?? "Inspect the raw failed_error field and retry after correcting the route or invoice."
470
+ });
471
+ }
472
+ }
473
+ return alerts;
474
+ }
475
+ function shortId(value) {
476
+ return value.length > 14 ? `${value.slice(0, 12)}...` : value;
477
+ }
478
+ export {
479
+ FiberClient,
480
+ FiberError,
481
+ FiberEventClient,
482
+ ckbToShannonHex,
483
+ diagnose,
484
+ diagnosePayment,
485
+ evaluateAlerts,
486
+ formatAmount,
487
+ fromHex,
488
+ shannonHexToCkb,
489
+ toHex
490
+ };
@@ -0,0 +1,86 @@
1
+ import type { HexString, Script } from "./common";
2
+ export interface OpenChannelParams {
3
+ /** Pubkey of the peer to open a channel with. The peer must already be connected via `connectPeer`. */
4
+ pubkey: HexString;
5
+ /** Funding amount in shannons (hex) or CKB (number) — ergonomic methods accept CKB and convert. */
6
+ fundingAmount: HexString;
7
+ /** Broadcast to the network so it can forward TLCs. Default: true. */
8
+ public?: boolean;
9
+ /** One-way channel: not broadcast, can only send payments in one direction. Default: false. */
10
+ oneWay?: boolean;
11
+ fundingUdtTypeScript?: Script;
12
+ shutdownScript?: Script;
13
+ commitmentDelayEpoch?: HexString;
14
+ commitmentFeeRate?: HexString;
15
+ fundingFeeRate?: HexString;
16
+ tlcExpiryDelta?: HexString;
17
+ tlcMinValue?: HexString;
18
+ tlcFeeProportionalMillionths?: HexString;
19
+ maxTlcValueInFlight?: HexString;
20
+ maxTlcNumberInFlight?: HexString;
21
+ }
22
+ export interface OpenChannelResult {
23
+ temporary_channel_id: HexString;
24
+ }
25
+ export interface AcceptChannelParams {
26
+ temporaryChannelId: HexString;
27
+ fundingAmount: HexString;
28
+ shutdownScript?: Script;
29
+ maxTlcValueInFlight?: HexString;
30
+ }
31
+ export interface AcceptChannelResult {
32
+ channel_id: HexString;
33
+ }
34
+ export interface ListChannelsParams {
35
+ pubkey?: HexString;
36
+ includeClosed?: boolean;
37
+ onlyPending?: boolean;
38
+ }
39
+ /**
40
+ * PascalCase per FNN's serde convention, confirmed live via `ChannelReady`. The pending
41
+ * states are named per `list_channels --only-pending`'s help text ("negotiating,
42
+ * collaborating on funding tx, signing, awaiting tx signatures, awaiting channel ready").
43
+ */
44
+ export type ChannelState = "NegotiatingFunding" | "CollaboratingFundingTx" | "SigningCommitment" | "AwaitingTxSignatures" | "AwaitingChannelReady" | "ChannelReady" | "ShuttingDown" | "Closed";
45
+ /**
46
+ * `list_channels` entry. `channel_id`, `channel_outpoint`, `state.state_name`,
47
+ * `local_balance`, and `remote_balance` are confirmed against a real funded testnet
48
+ * channel; the rest mirror `open_channel`'s snake_case parameter surface and should be
49
+ * spot-checked as they come up.
50
+ */
51
+ export interface Channel {
52
+ channel_id: HexString;
53
+ channel_outpoint: HexString;
54
+ latest_commitment_transaction_hash?: HexString;
55
+ peer_id?: HexString;
56
+ state: {
57
+ state_name: ChannelState;
58
+ state_flags?: string[];
59
+ };
60
+ local_balance: HexString;
61
+ remote_balance: HexString;
62
+ offered_tlc_balance?: HexString;
63
+ received_tlc_balance?: HexString;
64
+ is_public?: boolean;
65
+ funding_udt_type_script?: Script;
66
+ created_at?: HexString;
67
+ }
68
+ export interface ListChannelsResult {
69
+ channels: Channel[];
70
+ }
71
+ export interface AbandonChannelParams {
72
+ channelId: HexString;
73
+ }
74
+ export interface ShutdownChannelParams {
75
+ channelId: HexString;
76
+ closeScript?: Script;
77
+ feeRate?: HexString;
78
+ force?: boolean;
79
+ }
80
+ export interface UpdateChannelParams {
81
+ channelId: HexString;
82
+ enabled?: boolean;
83
+ tlcExpiryDelta?: HexString;
84
+ tlcMinimumValue?: HexString;
85
+ tlcFeeProportionalMillionths?: HexString;
86
+ }
@@ -0,0 +1,10 @@
1
+ /** A `0x`-prefixed hex string, as returned by FNN's JSON-RPC for hashes, pubkeys, and amounts. */
2
+ export type HexString = `0x${string}`;
3
+ /** A CKB script (lock or type). */
4
+ export interface Script {
5
+ code_hash: HexString;
6
+ hash_type: "type" | "data" | "data1" | "data2";
7
+ args: HexString;
8
+ }
9
+ /** Human-readable CKB amount, as accepted by the ergonomic client methods (converted to shannon hex on the wire). */
10
+ export type Ckb = number;
@@ -0,0 +1,69 @@
1
+ import type { HexString, Script } from "./common";
2
+ /** Fiber Bitcoin-style bech32m prefixes: mainnet, testnet, devnet. */
3
+ export type InvoiceCurrency = "Fibb" | "Fibt" | "Fibd";
4
+ export type HashAlgorithm = "Sha256" | "Ckbhash";
5
+ export interface NewInvoiceParams {
6
+ /** Amount in shannons (hex) or CKB (number) — ergonomic methods accept CKB and convert. */
7
+ amount: HexString;
8
+ currency: InvoiceCurrency;
9
+ description?: string;
10
+ /** If set, `paymentHash` must be absent. If both are absent, a random preimage is generated. */
11
+ paymentPreimage?: HexString;
12
+ /** Hold invoice: hash is known, preimage is revealed later via `settleInvoice`. */
13
+ paymentHash?: HexString;
14
+ /** Expiry in seconds. */
15
+ expiry?: number;
16
+ fallbackAddress?: string;
17
+ /** Final HTLC timeout in ms. Min 16 hours, max 14 days. */
18
+ finalExpiryDelta?: number;
19
+ udtTypeScript?: Script;
20
+ hashAlgorithm?: HashAlgorithm;
21
+ allowMpp?: boolean;
22
+ allowTrampolineRouting?: boolean;
23
+ }
24
+ interface InvoiceAttrDescription {
25
+ description: string;
26
+ }
27
+ interface InvoiceAttrExpiry {
28
+ final_htlc_minimum_expiry_delta: HexString;
29
+ }
30
+ interface InvoiceAttrPayee {
31
+ payee_public_key: HexString;
32
+ }
33
+ type InvoiceAttr = InvoiceAttrDescription | InvoiceAttrExpiry | InvoiceAttrPayee | Record<string, unknown>;
34
+ export interface CkbInvoiceData {
35
+ attrs: InvoiceAttr[];
36
+ payment_hash: HexString;
37
+ timestamp: HexString;
38
+ }
39
+ export interface CkbInvoice {
40
+ amount: HexString;
41
+ currency: InvoiceCurrency;
42
+ data: CkbInvoiceData;
43
+ signature: string;
44
+ }
45
+ export interface NewInvoiceResult {
46
+ invoice: CkbInvoice;
47
+ invoice_address: string;
48
+ }
49
+ /** `Open` confirmed live; `Cancelled`/`Paid`/`Expired` per the RPC's documented invoice lifecycle. */
50
+ export type InvoiceStatus = "Open" | "Cancelled" | "Paid" | "Expired";
51
+ export interface GetInvoiceResult {
52
+ invoice: CkbInvoice;
53
+ invoice_address: string;
54
+ status: InvoiceStatus;
55
+ }
56
+ export interface ParseInvoiceParams {
57
+ invoice: string;
58
+ }
59
+ export interface ParseInvoiceResult {
60
+ invoice: CkbInvoice;
61
+ }
62
+ export interface CancelInvoiceParams {
63
+ paymentHash: HexString;
64
+ }
65
+ export interface SettleInvoiceParams {
66
+ paymentHash: HexString;
67
+ paymentPreimage: HexString;
68
+ }
69
+ export {};
@@ -0,0 +1,2 @@
1
+ /** Which Fiber network a client is pointed at. Drives `FiberClient`'s mainnet write-guard. */
2
+ export type NetworkMode = "devnet" | "testnet" | "mainnet";
@@ -0,0 +1,55 @@
1
+ import type { HexString, Script } from "./common";
2
+ export interface UdtCellDep {
3
+ type_id?: Script;
4
+ cell_dep?: {
5
+ out_point: {
6
+ tx_hash: HexString;
7
+ index: HexString;
8
+ };
9
+ dep_type: "code" | "dep_group";
10
+ };
11
+ }
12
+ export interface UdtCfgInfo {
13
+ name: string;
14
+ script: Script;
15
+ auto_accept_amount?: HexString;
16
+ cell_deps: UdtCellDep[];
17
+ }
18
+ /** Raw `node_info` RPC result. All amount fields are hex-encoded shannons. */
19
+ export interface NodeInfo {
20
+ version: string;
21
+ commit_hash: string;
22
+ pubkey: HexString;
23
+ features: string[];
24
+ node_name: string | null;
25
+ addresses: string[];
26
+ chain_hash: HexString;
27
+ open_channel_auto_accept_min_ckb_funding_amount: HexString;
28
+ auto_accept_channel_ckb_funding_amount: HexString;
29
+ default_funding_lock_script: Script;
30
+ tlc_expiry_delta: HexString;
31
+ tlc_min_value: HexString;
32
+ tlc_fee_proportional_millionths: HexString;
33
+ channel_count: HexString;
34
+ pending_channel_count: HexString;
35
+ peers_count: HexString;
36
+ udt_cfg_infos: UdtCfgInfo[];
37
+ }
38
+ /** Raw `list_peers` RPC result entry. */
39
+ export interface PeerInfo {
40
+ pubkey: HexString;
41
+ address: string;
42
+ }
43
+ export type PeerAddressType = "tcp" | "ws" | "wss";
44
+ export interface ConnectPeerParams {
45
+ /** Multiaddr string of the peer. Either `address` or `pubkey` must be provided. */
46
+ address?: string;
47
+ /** Peer pubkey; the node resolves the address from locally synced graph data. */
48
+ pubkey?: HexString;
49
+ /** Whether to persist the peer address to the peer store. */
50
+ save?: boolean;
51
+ addrType?: PeerAddressType;
52
+ }
53
+ export interface DisconnectPeerParams {
54
+ pubkey: HexString;
55
+ }
@@ -0,0 +1,60 @@
1
+ import type { HexString, Script } from "./common";
2
+ export interface HopHint {
3
+ pubkey: HexString;
4
+ channelOutpoint: string;
5
+ feeRate: HexString;
6
+ tlcExpiryDelta: HexString;
7
+ }
8
+ export interface SendPaymentParams {
9
+ /** Pubkey of the payment target node. Obtainable via `info()` or `graph_nodes`. */
10
+ targetPubkey?: HexString;
11
+ /** Amount in shannons (hex) or CKB (number) for non-UDT payments. Defaults to the invoice amount if unset. */
12
+ amount?: HexString;
13
+ /** Hash used in the payment's HTLC. Required unless `keysend` or an invoice supplies one. */
14
+ paymentHash?: HexString;
15
+ finalTlcExpiryDelta?: number;
16
+ tlcExpiryLimit?: number;
17
+ /** Encoded invoice string (`invoice_address` from `createInvoice`). */
18
+ invoice?: string;
19
+ /** Payment timeout in seconds. */
20
+ timeout?: number;
21
+ maxFeeAmount?: HexString;
22
+ maxFeeRate?: number;
23
+ maxParts?: number;
24
+ trampolineHops?: HexString[];
25
+ keysend?: boolean;
26
+ udtTypeScript?: Script;
27
+ /** Allow a circular self-payment, used for channel rebalancing. */
28
+ allowSelfPayment?: boolean;
29
+ customRecords?: Record<string, HexString>;
30
+ hopHints?: HopHint[];
31
+ /** Dry-run: validate routing/fees without sending. */
32
+ dryRun?: boolean;
33
+ }
34
+ /**
35
+ * Payment lifecycle status. `Created` and `Success` confirmed against a real end-to-end
36
+ * testnet payment; `Inflight`/`Failed` per the RPC's `--status` filter on `list_payments`
37
+ * and standard Lightning-style payment lifecycles.
38
+ */
39
+ export type PaymentStatus = "Created" | "Inflight" | "Success" | "Failed";
40
+ /** Confirmed live: `payment_hash`, `status`, `created_at`, `fee`. */
41
+ export interface PaymentResult {
42
+ payment_hash: HexString;
43
+ status: PaymentStatus;
44
+ created_at: HexString;
45
+ fee: HexString;
46
+ last_updated_at?: HexString;
47
+ failed_error?: string;
48
+ }
49
+ export interface GetPaymentParams {
50
+ paymentHash: HexString;
51
+ }
52
+ export interface ListPaymentsParams {
53
+ status?: PaymentStatus;
54
+ limit?: number;
55
+ after?: HexString;
56
+ }
57
+ export interface ListPaymentsResult {
58
+ payments: PaymentResult[];
59
+ last_cursor: HexString | null;
60
+ }
@@ -0,0 +1,9 @@
1
+ import type { HexString } from "./types/common";
2
+ /** Converts a human-readable CKB amount to a hex-encoded shannon amount, as FNN's RPC expects. */
3
+ export declare function ckbToShannonHex(ckb: number): HexString;
4
+ /** Converts a hex-encoded shannon amount from the RPC into a human-readable CKB amount. */
5
+ export declare function shannonHexToCkb(hex: HexString): number;
6
+ /** Formats a hex-encoded shannon amount for display, e.g. `formatAmount("0x5F5E10000", "CKB")` → `"100 CKB"`. */
7
+ export declare function formatAmount(hex: HexString, unit?: "CKB" | "shannon"): string;
8
+ export declare function toHex(value: bigint | number): HexString;
9
+ export declare function fromHex(hex: HexString): bigint;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@fiber-dev-kit/core",
3
+ "version": "0.1.0-rc.0",
4
+ "description": "Network-aware TypeScript RPC client for the Fiber Network Node (FNN)",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+ssh://git@github.com/scisamir/fiber-dev-kit.git",
10
+ "directory": "core"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/scisamir/fiber-dev-kit/issues"
14
+ },
15
+ "homepage": "https://github.com/scisamir/fiber-dev-kit#readme",
16
+ "keywords": [
17
+ "ckb",
18
+ "fiber-network",
19
+ "nervos",
20
+ "payments",
21
+ "rpc"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public",
25
+ "tag": "rc"
26
+ },
27
+ "main": "./dist/index.js",
28
+ "types": "./dist/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "import": "./dist/index.js"
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "CONTRIBUTION.md"
38
+ ],
39
+ "scripts": {
40
+ "build": "tsup src/index.ts --format esm --clean && tsc --emitDeclarationOnly --declaration",
41
+ "dev": "tsup src/index.ts --format esm --watch",
42
+ "typecheck": "tsc --noEmit",
43
+ "test": "vitest run"
44
+ },
45
+ "devDependencies": {
46
+ "tsup": "^8.0.1",
47
+ "typescript": "^5.4.0",
48
+ "vitest": "^1.4.0"
49
+ },
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ }
53
+ }