@fiber-pay/react 0.2.4 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
1
  // src/index.ts
2
2
  import {
3
- ChannelState,
3
+ ChannelState as ChannelState3,
4
4
  ConfigBuilder as ConfigBuilder2,
5
5
  ckbHash,
6
- ckbToShannons,
6
+ ckbToShannons as ckbToShannons2,
7
7
  derivePublicKey,
8
8
  FiberBrowserNode as FiberBrowserNode2,
9
9
  FiberRpcError,
@@ -14,7 +14,7 @@ import {
14
14
  PasswordCredentialProvider as PasswordCredentialProvider2,
15
15
  RawKeyCredentialProvider as RawKeyCredentialProvider2,
16
16
  scriptToAddress as scriptToAddress2,
17
- shannonsToCkb,
17
+ shannonsToCkb as shannonsToCkb2,
18
18
  toHex
19
19
  } from "@fiber-pay/sdk/browser";
20
20
 
@@ -22,6 +22,7 @@ import {
22
22
  import {
23
23
  useCallback as useCallback2,
24
24
  useEffect as useEffect2,
25
+ useId,
25
26
  useRef as useRef2,
26
27
  useState as useState2
27
28
  } from "react";
@@ -58,6 +59,7 @@ function asErrorMessage(error) {
58
59
  }
59
60
  function useFiberNode(options) {
60
61
  const walletId = options.walletId ?? `wallet-${options.network}`;
62
+ const externalWallet = options.externalWallet ?? false;
61
63
  const [state, setState] = useState("idle");
62
64
  const [nodeInfo, setNodeInfo] = useState(null);
63
65
  const [error, setError] = useState(null);
@@ -78,8 +80,9 @@ function useFiberNode(options) {
78
80
  node.off("error", nodeListenersRef.current.error);
79
81
  nodeListenersRef.current = null;
80
82
  }, []);
81
- useEffect(
82
- () => () => {
83
+ useEffect(() => {
84
+ isMountedRef.current = true;
85
+ return () => {
83
86
  isMountedRef.current = false;
84
87
  const node = nodeRef.current;
85
88
  nodeRef.current = null;
@@ -88,9 +91,8 @@ function useFiberNode(options) {
88
91
  void node.stop().catch(() => {
89
92
  });
90
93
  }
91
- },
92
- [detachNodeListeners]
93
- );
94
+ };
95
+ }, [detachNodeListeners]);
94
96
  useEffect(() => {
95
97
  if (options.enabled === false) return;
96
98
  let cancelled = false;
@@ -111,12 +113,12 @@ function useFiberNode(options) {
111
113
  console.warn("[fiber-pay/react] Failed to detect passkey support:", supportError.message);
112
114
  }
113
115
  });
114
- const provider = new PasskeyCredentialProvider(walletId);
116
+ const provider = new PasskeyCredentialProvider(walletId, { skipCkbKey: externalWallet });
115
117
  setHasPasskeyConfigured(provider.isConfigured());
116
118
  return () => {
117
119
  cancelled = true;
118
120
  };
119
- }, [walletId, options.enabled]);
121
+ }, [walletId, options.enabled, externalWallet]);
120
122
  const initNode = useCallback(
121
123
  (credential) => {
122
124
  if (nodeRef.current) {
@@ -188,7 +190,9 @@ function useFiberNode(options) {
188
190
  setError(null);
189
191
  let node = null;
190
192
  try {
191
- const credential = new PasswordCredentialProvider(walletId);
193
+ const credential = new PasswordCredentialProvider(walletId, {
194
+ skipCkbKey: externalWallet
195
+ });
192
196
  node = initNode(credential);
193
197
  const info = await node.start({ unlockParams: { password } });
194
198
  if (isMountedRef.current) {
@@ -201,14 +205,16 @@ function useFiberNode(options) {
201
205
  await cleanupFailedStart(node);
202
206
  }
203
207
  },
204
- [cleanupFailedStart, initNode, walletId]
208
+ [cleanupFailedStart, initNode, walletId, externalWallet]
205
209
  );
206
210
  const createPasskeyAndStart = useCallback(
207
211
  async (username = "User") => {
208
212
  setError(null);
209
213
  let node = null;
210
214
  try {
211
- const credential = new PasskeyCredentialProvider(walletId);
215
+ const credential = new PasskeyCredentialProvider(walletId, {
216
+ skipCkbKey: externalWallet
217
+ });
212
218
  await credential.register(username);
213
219
  if (isMountedRef.current) {
214
220
  setHasPasskeyConfigured(true);
@@ -225,13 +231,15 @@ function useFiberNode(options) {
225
231
  await cleanupFailedStart(node);
226
232
  }
227
233
  },
228
- [cleanupFailedStart, initNode, walletId]
234
+ [cleanupFailedStart, initNode, walletId, externalWallet]
229
235
  );
230
236
  const startWithPasskey = useCallback(async () => {
231
237
  setError(null);
232
238
  let node = null;
233
239
  try {
234
- const credential = new PasskeyCredentialProvider(walletId);
240
+ const credential = new PasskeyCredentialProvider(walletId, {
241
+ skipCkbKey: externalWallet
242
+ });
235
243
  node = initNode(credential);
236
244
  const info = await node.start();
237
245
  if (isMountedRef.current) {
@@ -243,13 +251,17 @@ function useFiberNode(options) {
243
251
  }
244
252
  await cleanupFailedStart(node);
245
253
  }
246
- }, [cleanupFailedStart, initNode, walletId]);
254
+ }, [cleanupFailedStart, initNode, walletId, externalWallet]);
247
255
  const startWithRawKey = useCallback(
248
256
  async (fiberKey, ckbSecretKey) => {
249
257
  setError(null);
250
258
  let node = null;
251
259
  try {
252
- const credential = new RawKeyCredentialProvider(fiberKey, ckbSecretKey, walletId);
260
+ const credential = new RawKeyCredentialProvider(
261
+ fiberKey,
262
+ externalWallet ? void 0 : ckbSecretKey,
263
+ walletId
264
+ );
253
265
  node = initNode(credential);
254
266
  const info = await node.start();
255
267
  if (isMountedRef.current) {
@@ -262,7 +274,7 @@ function useFiberNode(options) {
262
274
  await cleanupFailedStart(node);
263
275
  }
264
276
  },
265
- [cleanupFailedStart, initNode, walletId]
277
+ [cleanupFailedStart, initNode, walletId, externalWallet]
266
278
  );
267
279
  const stop = useCallback(async () => {
268
280
  const node = nodeRef.current;
@@ -461,10 +473,9 @@ function ConnectButton(props) {
461
473
  const {
462
474
  network = "testnet",
463
475
  fiber: externalFiber,
464
- strategy = "auto",
476
+ strategy = "passkey",
477
+ externalWallet = false,
465
478
  password,
466
- rawKey,
467
- rawCkbKey,
468
479
  walletId,
469
480
  passkeyUsername = "User",
470
481
  wasmFactory,
@@ -482,6 +493,7 @@ function ConnectButton(props) {
482
493
  walletId,
483
494
  wasmFactory,
484
495
  nodeConfig,
496
+ externalWallet,
485
497
  enabled: !externalFiber
486
498
  });
487
499
  const fiber = externalFiber ?? internalFiber;
@@ -498,13 +510,14 @@ function ConnectButton(props) {
498
510
  createPasskeyAndStart,
499
511
  startWithPasskey,
500
512
  startWithPassword,
501
- startWithRawKey,
502
513
  stop
503
514
  } = fiber;
504
515
  const [isConnecting, setIsConnecting] = useState2(false);
505
516
  const [showDropdown, setShowDropdown] = useState2(false);
506
517
  const [localError, setLocalError] = useState2(null);
507
518
  const dropdownRef = useRef2(null);
519
+ const dropdownId = useId();
520
+ const lastReportedErrorRef = useRef2(null);
508
521
  const effectiveIsStarting = isConnecting || isStarting;
509
522
  useEffect2(() => ensureKeyframes(), []);
510
523
  useEffect2(() => {
@@ -527,23 +540,27 @@ function ConnectButton(props) {
527
540
  }
528
541
  prevRunningRef.current = isRunning;
529
542
  }, [isRunning, node, nodeInfo, onConnect, onDisconnect]);
543
+ const effectiveError = error ?? localError;
530
544
  useEffect2(() => {
531
- if (error) onError?.(error);
532
- }, [error, onError]);
533
- const resolvedStrategy = strategy === "auto" ? hasPasskeyConfigured && isPasskeySupported ? "passkey" : password ? "password" : rawKey ? "rawKey" : "passkey" : strategy;
545
+ if (!effectiveError) {
546
+ lastReportedErrorRef.current = null;
547
+ return;
548
+ }
549
+ if (lastReportedErrorRef.current === effectiveError) {
550
+ return;
551
+ }
552
+ lastReportedErrorRef.current = effectiveError;
553
+ onError?.(effectiveError);
554
+ }, [effectiveError, onError]);
534
555
  const handleConnect = useCallback2(async () => {
535
556
  setIsConnecting(true);
536
557
  setLocalError(null);
537
558
  try {
538
- switch (resolvedStrategy) {
559
+ switch (strategy) {
539
560
  case "password":
540
561
  if (!password) throw new Error('Password is required for "password" strategy');
541
562
  await startWithPassword(password);
542
563
  break;
543
- case "rawKey":
544
- if (!rawKey) throw new Error('rawKey is required for "rawKey" strategy');
545
- await startWithRawKey(rawKey, rawCkbKey);
546
- break;
547
564
  case "passkey":
548
565
  if (hasPasskeyConfigured) {
549
566
  await startWithPasskey();
@@ -555,22 +572,17 @@ function ConnectButton(props) {
555
572
  } catch (err) {
556
573
  const msg = err instanceof Error ? err.message : String(err);
557
574
  setLocalError(msg);
558
- onError?.(msg);
559
575
  } finally {
560
576
  setIsConnecting(false);
561
577
  }
562
578
  }, [
563
- resolvedStrategy,
579
+ strategy,
564
580
  password,
565
- rawKey,
566
- rawCkbKey,
567
581
  passkeyUsername,
568
582
  hasPasskeyConfigured,
569
583
  startWithPassword,
570
584
  startWithPasskey,
571
- startWithRawKey,
572
- createPasskeyAndStart,
573
- onError
585
+ createPasskeyAndStart
574
586
  ]);
575
587
  const handleDisconnect = useCallback2(async () => {
576
588
  try {
@@ -578,15 +590,14 @@ function ConnectButton(props) {
578
590
  } catch (err) {
579
591
  const msg = err instanceof Error ? err.message : String(err);
580
592
  setLocalError(msg);
581
- onError?.(msg);
582
593
  } finally {
583
594
  setShowDropdown(false);
584
595
  }
585
- }, [stop, onError]);
596
+ }, [stop]);
586
597
  const closeDropdown = useCallback2(() => {
587
598
  setShowDropdown(false);
588
599
  }, []);
589
- const hasError = !!(error || localError);
600
+ const hasError = !!effectiveError;
590
601
  let buttonLabel;
591
602
  let buttonOnClick;
592
603
  let buttonDisabled = false;
@@ -607,7 +618,7 @@ function ConnectButton(props) {
607
618
  buttonDisabled = true;
608
619
  buttonStyle = { ...styles.button, ...styles.connectButton, ...styles.disabledButton };
609
620
  } else {
610
- switch (resolvedStrategy) {
621
+ switch (strategy) {
611
622
  case "passkey":
612
623
  buttonLabel = hasPasskeyConfigured ? "Connect with Passkey" : "Connect via Passkey";
613
624
  if (!isPasskeySupported) {
@@ -618,9 +629,6 @@ function ConnectButton(props) {
618
629
  case "password":
619
630
  buttonLabel = "Connect";
620
631
  break;
621
- case "rawKey":
622
- buttonLabel = "Connect";
623
- break;
624
632
  }
625
633
  buttonOnClick = handleConnect;
626
634
  buttonStyle = {
@@ -630,177 +638,2485 @@ function ConnectButton(props) {
630
638
  };
631
639
  }
632
640
  return /* @__PURE__ */ jsxs("div", { className, style: { ...styles.root, ...style }, "data-fpay-connect-button": "", children: [
633
- (hasError || !isPasskeySupported && passkeyUnavailableReason && resolvedStrategy === "passkey") && /* @__PURE__ */ jsx("span", { style: styles.errorText, children: error || localError || passkeyUnavailableReason }),
641
+ (hasError || !isPasskeySupported && passkeyUnavailableReason && strategy === "passkey") && /* @__PURE__ */ jsx("span", { style: styles.errorText, children: error || localError || passkeyUnavailableReason }),
634
642
  isRunning ? /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, ref: dropdownRef, children: [
635
- /* @__PURE__ */ jsx("button", { type: "button", onClick: buttonOnClick, style: buttonStyle, children: buttonLabel }),
636
- showDropdown && /* @__PURE__ */ jsx("div", { style: { ...styles.dropdown, ...dropdownStyle }, children: renderConnectedDropdown ? renderConnectedDropdown({
637
- fiber,
638
- closeDropdown,
639
- disconnect: handleDisconnect
640
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [
641
- nodeInfo && /* @__PURE__ */ jsxs(Fragment, { children: [
642
- /* @__PURE__ */ jsxs("div", { style: styles.infoRow, children: [
643
- /* @__PURE__ */ jsx("span", { style: styles.infoLabel, children: "Pubkey" }),
644
- /* @__PURE__ */ jsx("span", { style: styles.infoValue, children: truncateNodeId(nodeInfo.pubkey) })
645
- ] }),
646
- /* @__PURE__ */ jsxs("div", { style: styles.infoRow, children: [
647
- /* @__PURE__ */ jsx("span", { style: styles.infoLabel, children: "State" }),
648
- /* @__PURE__ */ jsx("span", { style: styles.infoValue, children: state })
643
+ /* @__PURE__ */ jsx(
644
+ "button",
645
+ {
646
+ type: "button",
647
+ onClick: buttonOnClick,
648
+ style: buttonStyle,
649
+ "aria-haspopup": "dialog",
650
+ "aria-expanded": showDropdown,
651
+ "aria-controls": showDropdown ? dropdownId : void 0,
652
+ children: buttonLabel
653
+ }
654
+ ),
655
+ showDropdown && /* @__PURE__ */ jsx(
656
+ "div",
657
+ {
658
+ id: dropdownId,
659
+ role: "dialog",
660
+ "aria-label": "Connection panel",
661
+ style: { ...styles.dropdown, ...dropdownStyle },
662
+ children: renderConnectedDropdown ? renderConnectedDropdown({
663
+ fiber,
664
+ closeDropdown,
665
+ disconnect: handleDisconnect
666
+ }) : /* @__PURE__ */ jsxs(Fragment, { children: [
667
+ nodeInfo && /* @__PURE__ */ jsxs(Fragment, { children: [
668
+ /* @__PURE__ */ jsxs("div", { style: styles.infoRow, children: [
669
+ /* @__PURE__ */ jsx("span", { style: styles.infoLabel, children: "Pubkey" }),
670
+ /* @__PURE__ */ jsx("span", { style: styles.infoValue, children: truncateNodeId(nodeInfo.pubkey) })
671
+ ] }),
672
+ /* @__PURE__ */ jsxs("div", { style: styles.infoRow, children: [
673
+ /* @__PURE__ */ jsx("span", { style: styles.infoLabel, children: "State" }),
674
+ /* @__PURE__ */ jsx("span", { style: styles.infoValue, children: state })
675
+ ] })
676
+ ] }),
677
+ /* @__PURE__ */ jsx("div", { style: styles.separator }),
678
+ /* @__PURE__ */ jsxs(
679
+ "button",
680
+ {
681
+ type: "button",
682
+ onClick: () => void handleDisconnect(),
683
+ style: styles.disconnectButton,
684
+ children: [
685
+ /* @__PURE__ */ jsx("span", { children: "Disconnect" }),
686
+ /* @__PURE__ */ jsx(
687
+ "svg",
688
+ {
689
+ width: "14",
690
+ height: "14",
691
+ viewBox: "0 0 24 24",
692
+ fill: "none",
693
+ stroke: "currentColor",
694
+ strokeWidth: "2",
695
+ strokeLinecap: "round",
696
+ strokeLinejoin: "round",
697
+ "aria-hidden": "true",
698
+ children: /* @__PURE__ */ jsx("path", { d: "M9 18l6-6-6-6" })
699
+ }
700
+ )
701
+ ]
702
+ }
703
+ )
649
704
  ] })
650
- ] }),
651
- /* @__PURE__ */ jsx("div", { style: styles.separator }),
652
- /* @__PURE__ */ jsxs(
653
- "button",
654
- {
655
- type: "button",
656
- onClick: () => void handleDisconnect(),
657
- style: styles.disconnectButton,
658
- children: [
659
- /* @__PURE__ */ jsx("span", { children: "Disconnect" }),
660
- /* @__PURE__ */ jsx(
661
- "svg",
662
- {
663
- width: "14",
664
- height: "14",
665
- viewBox: "0 0 24 24",
666
- fill: "none",
667
- stroke: "currentColor",
668
- strokeWidth: "2",
669
- strokeLinecap: "round",
670
- strokeLinejoin: "round",
671
- "aria-hidden": "true",
672
- children: /* @__PURE__ */ jsx("path", { d: "M9 18l6-6-6-6" })
673
- }
674
- )
675
- ]
676
- }
677
- )
678
- ] }) })
705
+ }
706
+ )
679
707
  ] }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: buttonOnClick, disabled: buttonDisabled, style: buttonStyle, children: buttonLabel })
680
708
  ] });
681
709
  }
682
710
 
683
- // src/fiber-pay-quick-card.tsx
684
- import { useEffect as useEffect4, useId, useState as useState4 } from "react";
711
+ // src/fiber-node-button/index.tsx
712
+ import { useCallback as useCallback6 } from "react";
685
713
 
686
- // src/use-fiber-payment.ts
687
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
688
- function asErrorMessage2(error) {
689
- if (error instanceof Error) {
690
- return error.message;
714
+ // src/fiber-node-button/panel.tsx
715
+ import { useEffect as useEffect5, useMemo as useMemo4, useRef as useRef5 } from "react";
716
+
717
+ // src/fiber-node-button/styles.ts
718
+ var styles2 = {
719
+ shell: {
720
+ position: "relative",
721
+ display: "grid",
722
+ gridTemplateRows: "auto auto minmax(0, 1fr)",
723
+ gap: "0.7rem",
724
+ minWidth: "280px",
725
+ width: "min(460px, calc(100vw - 1rem))",
726
+ maxHeight: "72vh"
727
+ },
728
+ globalBar: {
729
+ border: "1px solid var(--fpay-border, #d8dee8)",
730
+ borderRadius: "0.72rem",
731
+ background: "linear-gradient(180deg, #ffffff 0%, #f8fafc 100%)",
732
+ padding: "0.52rem 0.56rem",
733
+ display: "grid",
734
+ gap: "0.45rem"
735
+ },
736
+ globalRow: {
737
+ display: "flex",
738
+ alignItems: "center",
739
+ justifyContent: "space-between",
740
+ gap: "0.35rem",
741
+ flexWrap: "wrap"
742
+ },
743
+ globalMetrics: {
744
+ display: "flex",
745
+ alignItems: "center",
746
+ gap: "0.5rem",
747
+ flexWrap: "wrap",
748
+ minWidth: 0
749
+ },
750
+ metricInline: {
751
+ display: "inline-flex",
752
+ alignItems: "center",
753
+ gap: "0.24rem",
754
+ whiteSpace: "nowrap"
755
+ },
756
+ metricDot: {
757
+ display: "inline-flex",
758
+ width: "0.34rem",
759
+ height: "0.34rem",
760
+ borderRadius: "999px",
761
+ flexShrink: 0,
762
+ background: "#94a3b8"
763
+ },
764
+ metricMain: {
765
+ fontSize: "0.82rem",
766
+ fontWeight: 750,
767
+ color: "var(--fpay-text-primary, #0f172a)",
768
+ lineHeight: 1.1
769
+ },
770
+ metricSub: {
771
+ fontSize: "0.72rem",
772
+ color: "var(--fpay-text-secondary, #64748b)",
773
+ lineHeight: 1.1
774
+ },
775
+ metricDivider: {
776
+ fontSize: "0.7rem",
777
+ color: "#94a3b8",
778
+ lineHeight: 1
779
+ },
780
+ globalMeta: {
781
+ display: "flex",
782
+ alignItems: "center",
783
+ justifyContent: "space-between",
784
+ gap: "0.5rem",
785
+ flexWrap: "wrap"
786
+ },
787
+ globalErrorInline: {
788
+ margin: 0,
789
+ fontSize: "0.71rem",
790
+ color: "#9f1239",
791
+ lineHeight: 1.25
792
+ },
793
+ statusDot: {
794
+ display: "inline-flex",
795
+ width: "0.45rem",
796
+ height: "0.45rem",
797
+ borderRadius: "999px",
798
+ flexShrink: 0
799
+ },
800
+ globalActions: {
801
+ display: "flex",
802
+ gap: "0.36rem",
803
+ justifyContent: "flex-end",
804
+ flexWrap: "wrap"
805
+ },
806
+ globalActionButton: {
807
+ border: "1px solid var(--fpay-border, #cbd5e1)",
808
+ borderRadius: "0.45rem",
809
+ padding: "0.28rem 0.48rem",
810
+ fontSize: "0.72rem",
811
+ fontWeight: 650,
812
+ background: "#fff",
813
+ color: "var(--fpay-text-primary, #111827)",
814
+ cursor: "pointer"
815
+ },
816
+ tabList: {
817
+ borderRadius: "0.68rem",
818
+ border: "none",
819
+ background: "#e9eef6",
820
+ padding: "0.14rem",
821
+ display: "grid",
822
+ gridTemplateColumns: "repeat(3, minmax(0, 1fr))",
823
+ gap: "0.14rem",
824
+ boxShadow: "inset 0 0 0 1px var(--fpay-border, #d8dee8)"
825
+ },
826
+ tabButton: {
827
+ border: "none",
828
+ borderRadius: "0.5rem",
829
+ background: "transparent",
830
+ color: "#334155",
831
+ fontSize: "0.74rem",
832
+ fontWeight: 700,
833
+ padding: "0.44rem 0.45rem",
834
+ cursor: "pointer"
835
+ },
836
+ tabButtonActive: {
837
+ border: "none",
838
+ borderRadius: "0.5rem",
839
+ background: "var(--fpay-accent, #1d4ed8)",
840
+ color: "#fff",
841
+ fontSize: "0.74rem",
842
+ fontWeight: 700,
843
+ padding: "0.44rem 0.45rem",
844
+ cursor: "pointer",
845
+ boxShadow: "none"
846
+ },
847
+ content: {
848
+ overflowY: "auto",
849
+ paddingRight: "0.15rem",
850
+ display: "grid",
851
+ gap: "0.7rem",
852
+ minHeight: 0
853
+ },
854
+ section: {
855
+ border: "none",
856
+ borderBottom: "1px solid var(--fpay-border, #e2e8f0)",
857
+ borderRadius: 0,
858
+ padding: "0.15rem 0 0.55rem",
859
+ background: "transparent",
860
+ display: "grid",
861
+ gap: "0.44rem"
862
+ },
863
+ sectionTitle: {
864
+ margin: 0,
865
+ fontSize: "0.8rem",
866
+ fontWeight: 750,
867
+ color: "var(--fpay-text-primary, #0f172a)",
868
+ letterSpacing: "0.01em"
869
+ },
870
+ row: {
871
+ display: "flex",
872
+ alignItems: "center",
873
+ gap: "0.45rem",
874
+ flexWrap: "wrap"
875
+ },
876
+ rowBetween: {
877
+ display: "flex",
878
+ alignItems: "center",
879
+ justifyContent: "space-between",
880
+ gap: "0.45rem",
881
+ flexWrap: "wrap"
882
+ },
883
+ compactText: {
884
+ margin: 0,
885
+ fontSize: "0.74rem",
886
+ color: "var(--fpay-text-secondary, #64748b)",
887
+ lineHeight: 1.4
888
+ },
889
+ inlineCode: {
890
+ margin: 0,
891
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace",
892
+ fontSize: "0.72rem",
893
+ color: "var(--fpay-text-primary, #111827)",
894
+ wordBreak: "break-all",
895
+ lineHeight: 1.4
896
+ },
897
+ fieldLabel: {
898
+ display: "grid",
899
+ gap: "0.25rem",
900
+ fontSize: "0.68rem",
901
+ fontWeight: 700,
902
+ color: "var(--fpay-text-secondary, #64748b)",
903
+ textTransform: "uppercase"
904
+ },
905
+ input: {
906
+ width: "100%",
907
+ border: "1px solid var(--fpay-border, #cbd5e1)",
908
+ borderRadius: "0.45rem",
909
+ padding: "0.38rem 0.48rem",
910
+ fontSize: "0.8rem",
911
+ background: "#fff",
912
+ color: "var(--fpay-text-primary, #0f172a)"
913
+ },
914
+ actionButton: {
915
+ border: "1px solid var(--fpay-border, #cbd5e1)",
916
+ borderRadius: "0.45rem",
917
+ padding: "0.35rem 0.55rem",
918
+ fontSize: "0.74rem",
919
+ fontWeight: 650,
920
+ background: "#fff",
921
+ color: "var(--fpay-text-primary, #111827)",
922
+ cursor: "pointer"
923
+ },
924
+ primaryButton: {
925
+ border: "1px solid var(--fpay-accent, #1d4ed8)",
926
+ borderRadius: "0.45rem",
927
+ padding: "0.35rem 0.55rem",
928
+ fontSize: "0.74rem",
929
+ fontWeight: 750,
930
+ background: "var(--fpay-accent, #1d4ed8)",
931
+ color: "#fff",
932
+ cursor: "pointer"
933
+ },
934
+ ghostButton: {
935
+ border: "1px solid transparent",
936
+ borderRadius: "0.45rem",
937
+ padding: "0.32rem 0.5rem",
938
+ fontSize: "0.74rem",
939
+ fontWeight: 650,
940
+ background: "transparent",
941
+ color: "var(--fpay-text-secondary, #475569)",
942
+ cursor: "pointer"
943
+ },
944
+ dangerButton: {
945
+ border: "1px solid #fecaca",
946
+ borderRadius: "0.45rem",
947
+ padding: "0.35rem 0.55rem",
948
+ fontSize: "0.74rem",
949
+ fontWeight: 750,
950
+ background: "#fff1f2",
951
+ color: "#9f1239",
952
+ cursor: "pointer"
953
+ },
954
+ badge: {
955
+ display: "inline-flex",
956
+ alignItems: "center",
957
+ width: "fit-content",
958
+ borderRadius: "999px",
959
+ border: "1px solid var(--fpay-border, #cbd5e1)",
960
+ background: "#f8fafc",
961
+ color: "var(--fpay-text-secondary, #475569)",
962
+ padding: "0.12rem 0.4rem",
963
+ fontSize: "0.66rem",
964
+ fontWeight: 700,
965
+ lineHeight: 1.1
966
+ },
967
+ notice: {
968
+ border: "1px solid #bfdbfe",
969
+ background: "#eff6ff",
970
+ color: "#1d4ed8",
971
+ borderRadius: "0.52rem",
972
+ padding: "0.46rem 0.52rem",
973
+ fontSize: "0.74rem",
974
+ lineHeight: 1.35
975
+ },
976
+ successNotice: {
977
+ borderColor: "#86efac",
978
+ background: "#f0fdf4",
979
+ color: "#166534"
980
+ },
981
+ errorNotice: {
982
+ border: "1px solid #fecaca",
983
+ background: "#fff1f2",
984
+ color: "#9f1239",
985
+ borderRadius: "0.52rem",
986
+ padding: "0.46rem 0.52rem",
987
+ fontSize: "0.74rem",
988
+ lineHeight: 1.35
989
+ },
990
+ summaryGrid: {
991
+ display: "none"
992
+ },
993
+ summaryTile: {
994
+ display: "none"
995
+ },
996
+ summaryValue: {
997
+ display: "block",
998
+ fontSize: "0.92rem",
999
+ fontWeight: 750,
1000
+ color: "var(--fpay-text-primary, #0f172a)",
1001
+ lineHeight: 1.1
1002
+ },
1003
+ summaryLabel: {
1004
+ display: "block",
1005
+ marginTop: "0.12rem",
1006
+ fontSize: "0.64rem",
1007
+ color: "var(--fpay-text-secondary, #64748b)"
1008
+ },
1009
+ summaryInline: {
1010
+ margin: 0,
1011
+ fontSize: "0.76rem",
1012
+ color: "var(--fpay-text-secondary, #475569)",
1013
+ lineHeight: 1.35
1014
+ },
1015
+ filterBar: {
1016
+ display: "flex",
1017
+ flexWrap: "wrap",
1018
+ gap: "0.28rem"
1019
+ },
1020
+ list: {
1021
+ display: "grid",
1022
+ gap: "0.34rem",
1023
+ maxHeight: "240px",
1024
+ overflowY: "auto",
1025
+ paddingRight: "0.1rem"
1026
+ },
1027
+ compactChannelRow: {
1028
+ border: "1px solid var(--fpay-border, #d8dee8)",
1029
+ borderRadius: "0.5rem",
1030
+ background: "#fff",
1031
+ padding: "0.42rem 0.46rem",
1032
+ cursor: "pointer",
1033
+ display: "grid",
1034
+ gap: "0.22rem",
1035
+ textAlign: "left"
1036
+ },
1037
+ compactChannelRowActive: {
1038
+ borderColor: "var(--fpay-accent, #1d4ed8)",
1039
+ boxShadow: "0 0 0 1px rgba(29, 78, 216, 0.12) inset",
1040
+ background: "#f8fbff"
1041
+ },
1042
+ compactChannelTop: {
1043
+ display: "grid",
1044
+ gridTemplateColumns: "1fr auto",
1045
+ alignItems: "center",
1046
+ gap: "0.35rem"
1047
+ },
1048
+ detailPanel: {
1049
+ border: "1px solid var(--fpay-border, #d8dee8)",
1050
+ borderRadius: "0.6rem",
1051
+ background: "#f8fafc",
1052
+ padding: "0.6rem",
1053
+ display: "grid",
1054
+ gap: "0.45rem"
1055
+ },
1056
+ dialogBackdrop: {
1057
+ position: "absolute",
1058
+ inset: 0,
1059
+ background: "rgba(15, 23, 42, 0.36)",
1060
+ display: "grid",
1061
+ placeItems: "center",
1062
+ padding: "0.75rem",
1063
+ zIndex: 3
1064
+ },
1065
+ dialogCard: {
1066
+ width: "min(100%, 360px)",
1067
+ borderRadius: "0.68rem",
1068
+ border: "1px solid #fecaca",
1069
+ background: "#fff",
1070
+ padding: "0.72rem",
1071
+ display: "grid",
1072
+ gap: "0.55rem"
1073
+ },
1074
+ srOnly: {
1075
+ position: "absolute",
1076
+ width: 1,
1077
+ height: 1,
1078
+ padding: 0,
1079
+ margin: -1,
1080
+ overflow: "hidden",
1081
+ clip: "rect(0, 0, 0, 0)",
1082
+ border: 0
691
1083
  }
692
- return String(error);
1084
+ };
1085
+
1086
+ // src/fiber-node-button/utils.ts
1087
+ import { ChannelState, shannonsToCkb } from "@fiber-pay/sdk/browser";
1088
+ function shorten(value, head = 10, tail = 8) {
1089
+ if (!value || value.length <= head + tail + 3) {
1090
+ return value;
1091
+ }
1092
+ return `${value.slice(0, head)}...${value.slice(-tail)}`;
693
1093
  }
694
- function useFiberPayment(node) {
695
- const [isPaying, setIsPaying] = useState3(false);
696
- const [paymentResult, setPaymentResult] = useState3(null);
697
- const [error, setError] = useState3(null);
698
- const isMountedRef = useRef3(true);
699
- useEffect3(
700
- () => () => {
701
- isMountedRef.current = false;
702
- },
703
- []
704
- );
705
- const payInvoice = useCallback3(
706
- async (invoice) => {
707
- if (!node) {
708
- if (isMountedRef.current) {
709
- setError("Node is not initialized");
710
- }
711
- return;
712
- }
713
- if (isMountedRef.current) {
714
- setIsPaying(true);
715
- setError(null);
716
- setPaymentResult(null);
717
- }
718
- try {
719
- const parsed = await node.parseInvoice({ invoice });
720
- await node.sendPayment({ invoice });
721
- const paymentHash = parsed.invoice.data.payment_hash;
722
- const result = await node.waitForPayment(paymentHash);
723
- if (result.status === "Failed") {
724
- throw new Error(result.failed_error ?? "Payment failed during routing/execution");
725
- }
726
- if (isMountedRef.current) {
727
- setPaymentResult(result);
728
- }
729
- } catch (payError) {
730
- if (isMountedRef.current) {
731
- setError(asErrorMessage2(payError));
732
- }
733
- } finally {
734
- if (isMountedRef.current) {
735
- setIsPaying(false);
736
- }
737
- }
738
- },
739
- [node]
740
- );
1094
+ function summarizeError(message, max = 72) {
1095
+ const trimmed = message.trim();
1096
+ if (trimmed.length <= max) {
1097
+ return trimmed;
1098
+ }
1099
+ return `${trimmed.slice(0, max - 3)}...`;
1100
+ }
1101
+ function toHexPrefixed(value) {
1102
+ const trimmed = value.trim();
1103
+ if (!trimmed) {
1104
+ throw new Error("Hex value is empty.");
1105
+ }
1106
+ return /^0x/i.test(trimmed) ? trimmed : `0x${trimmed}`;
1107
+ }
1108
+ function isPendingChannelState(state) {
1109
+ return state === ChannelState.NegotiatingFunding || state === ChannelState.CollaboratingFundingTx || state === ChannelState.SigningCommitment || state === ChannelState.AwaitingTxSignatures || state === ChannelState.AwaitingChannelReady;
1110
+ }
1111
+ function formatChannelBalance(shannonsHex) {
1112
+ const ckb = shannonsToCkb(shannonsHex);
1113
+ return Number.isFinite(ckb) ? ckb.toFixed(4) : "0.0000";
1114
+ }
1115
+ function isClosedChannelState(state) {
1116
+ return state === ChannelState.Closed || state === ChannelState.ShuttingDown;
1117
+ }
1118
+ function getChannelFilterState(channel) {
1119
+ const state = channel.state.state_name;
1120
+ if (isPendingChannelState(state)) return "pending";
1121
+ if (isClosedChannelState(state)) return "closed";
1122
+ return "active";
1123
+ }
1124
+ function withDisabledStyle(style, disabled) {
1125
+ if (!disabled) {
1126
+ return style;
1127
+ }
741
1128
  return {
742
- payInvoice,
743
- isPaying,
744
- paymentResult,
745
- error
1129
+ ...style,
1130
+ opacity: 0.55,
1131
+ cursor: "not-allowed"
746
1132
  };
747
1133
  }
748
1134
 
749
- // src/fiber-pay-quick-card.tsx
750
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
751
- var ONE_CKB_SHANNONS = "0x5f5e100";
752
- var cardStyle = {
753
- border: "1px solid #ddd",
754
- borderRadius: 8,
755
- padding: 16,
756
- maxWidth: 520
757
- };
758
- var rowStyle = {
759
- display: "flex",
760
- gap: 8
761
- };
762
- var rowWithMarginStyle = {
763
- ...rowStyle,
764
- marginBottom: 8
765
- };
766
- function FiberPayQuickCard(props) {
767
- const network = props.network ?? "testnet";
768
- const passkeyUsername = props.passkeyUsername ?? "User";
769
- const title = props.title ?? "FiberPay Quick Card";
770
- const onError = props.onError;
771
- const onInvoiceCreated = props.onInvoiceCreated;
772
- const onPaymentResult = props.onPaymentResult;
773
- const passwordInputId = useId();
774
- const invoiceInputId = useId();
1135
+ // src/fiber-node-button/render-action.tsx
1136
+ import { jsx as jsx2 } from "react/jsx-runtime";
1137
+ function renderPanelAction(options) {
775
1138
  const {
776
- node,
777
- nodeInfo,
1139
+ id,
1140
+ defaultProps,
1141
+ fiber,
778
1142
  state,
779
- error: nodeError,
780
- isPasskeySupported,
781
- hasPasskeyConfigured,
782
- startWithPassword,
783
- startWithPasskey,
784
- createPasskeyAndStart,
785
- stop
786
- } = useFiberNode({ network, walletId: props.walletId });
787
- const { payInvoice, isPaying, error: payError, paymentResult } = useFiberPayment(node);
788
- const [password, setPassword] = useState4("");
789
- const [invoiceInput, setInvoiceInput] = useState4("");
790
- const [createdInvoice, setCreatedInvoice] = useState4("");
791
- const [isCreatingInvoice, setIsCreatingInvoice] = useState4(false);
792
- const [invoiceError, setInvoiceError] = useState4(null);
793
- useEffect4(() => {
794
- if (nodeError) {
795
- onError?.({ scope: "node", message: nodeError });
1143
+ renderAction,
1144
+ t,
1145
+ buttonStyle = styles2.actionButton
1146
+ } = options;
1147
+ const customAction = renderAction?.({
1148
+ id,
1149
+ defaultProps,
1150
+ fiber,
1151
+ state,
1152
+ t
1153
+ });
1154
+ if (customAction !== void 0) {
1155
+ return customAction;
1156
+ }
1157
+ const loadingText = defaultProps.loadingLabel ?? t("actions.loading.default", "Processing...");
1158
+ return /* @__PURE__ */ jsx2(
1159
+ "button",
1160
+ {
1161
+ type: "button",
1162
+ style: withDisabledStyle(buttonStyle, defaultProps.disabled),
1163
+ disabled: defaultProps.disabled,
1164
+ onClick: () => {
1165
+ void defaultProps.onTrigger();
1166
+ },
1167
+ children: defaultProps.loading ? loadingText : defaultProps.label
796
1168
  }
797
- }, [nodeError, onError]);
798
- useEffect4(() => {
1169
+ );
1170
+ }
1171
+
1172
+ // src/fiber-node-button/types.ts
1173
+ var ONE_CKB_SHANNONS = "0x5f5e100";
1174
+ var TAB_ITEMS = [
1175
+ { id: "workbench", label: "Workbench" },
1176
+ { id: "channels", label: "Channels" },
1177
+ { id: "diagnostics", label: "Diagnostics" }
1178
+ ];
1179
+ var FILTER_ITEMS = ["active", "pending", "closed", "all"];
1180
+
1181
+ // src/fiber-node-button/channels-tab.tsx
1182
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
1183
+ function ChannelsTab({ state, fiber, onLog, renderAction, t }) {
1184
+ const {
1185
+ isRefreshingChannels,
1186
+ refreshChannels,
1187
+ channelCounts,
1188
+ channelFilter,
1189
+ setChannelFilter,
1190
+ visibleChannels,
1191
+ selectedChannelId,
1192
+ setSelectedChannelId,
1193
+ setForceCloseConfirmOpen,
1194
+ selectedChannel,
1195
+ selectedCanClose,
1196
+ selectedIsClosing,
1197
+ selectedPending,
1198
+ closeChannel
1199
+ } = state;
1200
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1201
+ /* @__PURE__ */ jsxs2("section", { style: styles2.section, children: [
1202
+ /* @__PURE__ */ jsxs2("div", { style: styles2.rowBetween, children: [
1203
+ /* @__PURE__ */ jsx3("h4", { style: styles2.sectionTitle, children: t("channels.summary.title", "Channel Summary") }),
1204
+ /* @__PURE__ */ jsx3(
1205
+ "button",
1206
+ {
1207
+ type: "button",
1208
+ style: withDisabledStyle(styles2.actionButton, isRefreshingChannels),
1209
+ disabled: isRefreshingChannels,
1210
+ onClick: () => {
1211
+ void refreshChannels();
1212
+ },
1213
+ children: isRefreshingChannels ? t("channels.summary.refresh.loading", "Refreshing...") : t("channels.summary.refresh", "Refresh")
1214
+ }
1215
+ )
1216
+ ] }),
1217
+ /* @__PURE__ */ jsxs2("p", { style: styles2.summaryInline, children: [
1218
+ t("channels.summary.active", "Active"),
1219
+ " ",
1220
+ channelCounts.active,
1221
+ " |",
1222
+ " ",
1223
+ t("channels.summary.pending", "Pending"),
1224
+ " ",
1225
+ channelCounts.pending,
1226
+ " |",
1227
+ " ",
1228
+ t("channels.summary.closed", "Closed"),
1229
+ " ",
1230
+ channelCounts.closed,
1231
+ " |",
1232
+ " ",
1233
+ t("channels.summary.total", "Total"),
1234
+ " ",
1235
+ channelCounts.all
1236
+ ] }),
1237
+ /* @__PURE__ */ jsx3("div", { style: styles2.filterBar, children: FILTER_ITEMS.map((filter) => /* @__PURE__ */ jsx3(
1238
+ "button",
1239
+ {
1240
+ type: "button",
1241
+ style: channelFilter === filter ? styles2.primaryButton : styles2.actionButton,
1242
+ onClick: () => setChannelFilter(filter),
1243
+ children: filter === "all" ? `${t("channels.filter.all", "All")} (${channelCounts.all})` : `${filter} (${channelCounts[filter]})`
1244
+ },
1245
+ filter
1246
+ )) }),
1247
+ /* @__PURE__ */ jsx3("div", { style: styles2.list, children: visibleChannels.length === 0 ? /* @__PURE__ */ jsx3("p", { style: styles2.compactText, children: t("channels.list.empty", "No channels found for this filter.") }) : visibleChannels.map((channel) => {
1248
+ const selected = channel.channel_id === selectedChannelId;
1249
+ return /* @__PURE__ */ jsxs2(
1250
+ "button",
1251
+ {
1252
+ type: "button",
1253
+ style: {
1254
+ ...styles2.compactChannelRow,
1255
+ ...selected ? styles2.compactChannelRowActive : {}
1256
+ },
1257
+ onClick: () => {
1258
+ setSelectedChannelId(channel.channel_id);
1259
+ setForceCloseConfirmOpen(false);
1260
+ },
1261
+ children: [
1262
+ /* @__PURE__ */ jsx3("span", { style: styles2.srOnly, children: selected ? t("channels.list.selected", "Selected channel") : t("channels.list.select", "Select channel") }),
1263
+ /* @__PURE__ */ jsxs2("span", { style: styles2.compactChannelTop, children: [
1264
+ /* @__PURE__ */ jsxs2("span", { style: styles2.inlineCode, children: [
1265
+ t("channels.list.id", "ID"),
1266
+ ": ",
1267
+ shorten(channel.channel_id, 12, 8)
1268
+ ] }),
1269
+ /* @__PURE__ */ jsx3("span", { style: styles2.badge, children: channel.state.state_name })
1270
+ ] }),
1271
+ /* @__PURE__ */ jsxs2("span", { style: styles2.compactText, children: [
1272
+ t("channels.list.peer", "Peer"),
1273
+ ": ",
1274
+ shorten(channel.pubkey, 16, 10)
1275
+ ] }),
1276
+ /* @__PURE__ */ jsxs2("span", { style: styles2.compactText, children: [
1277
+ "L ",
1278
+ formatChannelBalance(channel.local_balance),
1279
+ " / R",
1280
+ " ",
1281
+ formatChannelBalance(channel.remote_balance),
1282
+ " CKB"
1283
+ ] })
1284
+ ]
1285
+ },
1286
+ channel.channel_id
1287
+ );
1288
+ }) })
1289
+ ] }),
1290
+ selectedChannel ? /* @__PURE__ */ jsxs2("section", { style: styles2.detailPanel, children: [
1291
+ /* @__PURE__ */ jsxs2("div", { style: styles2.rowBetween, children: [
1292
+ /* @__PURE__ */ jsx3("h4", { style: styles2.sectionTitle, children: t("channels.details.title", "Channel Details") }),
1293
+ /* @__PURE__ */ jsx3("span", { style: styles2.badge, children: selectedChannel.state.state_name })
1294
+ ] }),
1295
+ /* @__PURE__ */ jsxs2("p", { style: styles2.inlineCode, children: [
1296
+ t("channels.details.channelId", "Channel ID"),
1297
+ ": ",
1298
+ selectedChannel.channel_id
1299
+ ] }),
1300
+ /* @__PURE__ */ jsxs2("p", { style: styles2.inlineCode, children: [
1301
+ t("channels.details.peer", "Peer"),
1302
+ ": ",
1303
+ selectedChannel.pubkey
1304
+ ] }),
1305
+ /* @__PURE__ */ jsxs2("div", { style: styles2.row, children: [
1306
+ /* @__PURE__ */ jsxs2("span", { style: styles2.badge, children: [
1307
+ t("channels.details.local", "Local"),
1308
+ " ",
1309
+ formatChannelBalance(selectedChannel.local_balance),
1310
+ " CKB"
1311
+ ] }),
1312
+ /* @__PURE__ */ jsxs2("span", { style: styles2.badge, children: [
1313
+ t("channels.details.remote", "Remote"),
1314
+ " ",
1315
+ formatChannelBalance(selectedChannel.remote_balance),
1316
+ " CKB"
1317
+ ] }),
1318
+ /* @__PURE__ */ jsxs2(
1319
+ "span",
1320
+ {
1321
+ style: styles2.badge,
1322
+ title: "Pending TLCs are in-flight payment locks associated with this channel.",
1323
+ children: [
1324
+ t("channels.details.tlcs", "TLCs"),
1325
+ " ",
1326
+ selectedChannel.pending_tlcs.length
1327
+ ]
1328
+ }
1329
+ )
1330
+ ] }),
1331
+ selectedChannel.failure_detail ? /* @__PURE__ */ jsxs2("p", { style: styles2.compactText, children: [
1332
+ t("channels.details.failure", "Failure"),
1333
+ ": ",
1334
+ selectedChannel.failure_detail
1335
+ ] }) : null,
1336
+ selectedChannel.shutdown_transaction_hash ? /* @__PURE__ */ jsxs2("p", { style: styles2.inlineCode, children: [
1337
+ t("channels.details.shutdownTx", "Shutdown TX"),
1338
+ ":",
1339
+ " ",
1340
+ selectedChannel.shutdown_transaction_hash
1341
+ ] }) : null,
1342
+ /* @__PURE__ */ jsxs2("div", { style: { ...styles2.row, justifyContent: "flex-end" }, children: [
1343
+ renderPanelAction({
1344
+ id: "close-channel",
1345
+ fiber,
1346
+ state,
1347
+ renderAction,
1348
+ t,
1349
+ defaultProps: {
1350
+ id: "close-channel",
1351
+ channelId: selectedChannel.channel_id,
1352
+ label: selectedPending ? t("actions.abandonPending", "Abandon Pending") : t("actions.closeChannel", "Close Channel"),
1353
+ loadingLabel: t("actions.closeChannel.loading", "Closing..."),
1354
+ disabled: !selectedCanClose || selectedIsClosing,
1355
+ loading: selectedIsClosing,
1356
+ onTrigger: async () => {
1357
+ await closeChannel(selectedChannel.channel_id, false);
1358
+ }
1359
+ }
1360
+ }),
1361
+ renderPanelAction({
1362
+ id: "force-close-channel",
1363
+ fiber,
1364
+ state,
1365
+ renderAction,
1366
+ t,
1367
+ buttonStyle: styles2.dangerButton,
1368
+ defaultProps: {
1369
+ id: "force-close-channel",
1370
+ channelId: selectedChannel.channel_id,
1371
+ label: t("actions.forceClose", "Force Close"),
1372
+ disabled: !selectedCanClose || selectedPending || selectedIsClosing,
1373
+ onTrigger: () => {
1374
+ setForceCloseConfirmOpen(true);
1375
+ onLog?.("fiber_channel_force_close_confirm_opened");
1376
+ }
1377
+ }
1378
+ })
1379
+ ] })
1380
+ ] }) : null
1381
+ ] });
1382
+ }
1383
+
1384
+ // src/fiber-node-button/diagnostics-tab.tsx
1385
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
1386
+ function DiagnosticsTab({ state, t }) {
1387
+ const {
1388
+ isRefreshingPeers,
1389
+ refreshConnectedPeers,
1390
+ connectedPeers,
1391
+ setPeerPubkey,
1392
+ switchTab,
1393
+ peerAddress,
1394
+ setPeerAddress,
1395
+ isConnectingPeer,
1396
+ connectPeerByAddress,
1397
+ isRefreshingGraph,
1398
+ refreshDiagnostics,
1399
+ refreshGraph,
1400
+ graphNodes,
1401
+ graphChannels,
1402
+ channelOpenFlow
1403
+ } = state;
1404
+ return /* @__PURE__ */ jsxs3(Fragment3, { children: [
1405
+ /* @__PURE__ */ jsxs3("section", { style: styles2.section, children: [
1406
+ /* @__PURE__ */ jsxs3("div", { style: styles2.rowBetween, children: [
1407
+ /* @__PURE__ */ jsx4("h4", { style: styles2.sectionTitle, children: t("diagnostics.peers.title", "Connected Peers") }),
1408
+ /* @__PURE__ */ jsx4(
1409
+ "button",
1410
+ {
1411
+ type: "button",
1412
+ style: withDisabledStyle(styles2.actionButton, isRefreshingPeers),
1413
+ disabled: isRefreshingPeers,
1414
+ onClick: () => {
1415
+ void refreshConnectedPeers();
1416
+ },
1417
+ children: isRefreshingPeers ? t("diagnostics.peers.refresh.loading", "Refreshing...") : t("diagnostics.peers.refresh", "Refresh Peers")
1418
+ }
1419
+ )
1420
+ ] }),
1421
+ /* @__PURE__ */ jsxs3("p", { style: styles2.compactText, children: [
1422
+ t("diagnostics.peers.count", "Peers"),
1423
+ ": ",
1424
+ connectedPeers.length
1425
+ ] }),
1426
+ /* @__PURE__ */ jsx4("div", { style: { ...styles2.list, maxHeight: "190px" }, children: connectedPeers.length === 0 ? /* @__PURE__ */ jsx4("p", { style: styles2.compactText, children: t("diagnostics.peers.empty", "No connected peers.") }) : connectedPeers.map((peer) => /* @__PURE__ */ jsxs3("article", { style: styles2.compactChannelRow, children: [
1427
+ /* @__PURE__ */ jsx4("p", { style: styles2.inlineCode, children: shorten(peer.pubkey, 18, 12) }),
1428
+ /* @__PURE__ */ jsxs3("details", { children: [
1429
+ /* @__PURE__ */ jsx4("summary", { style: { ...styles2.compactText, cursor: "pointer" }, children: t("diagnostics.peers.address", "Address") }),
1430
+ /* @__PURE__ */ jsx4("p", { style: styles2.inlineCode, children: peer.address })
1431
+ ] }),
1432
+ /* @__PURE__ */ jsx4("div", { style: styles2.row, children: /* @__PURE__ */ jsx4(
1433
+ "button",
1434
+ {
1435
+ type: "button",
1436
+ style: styles2.ghostButton,
1437
+ onClick: () => {
1438
+ setPeerPubkey(peer.pubkey);
1439
+ switchTab("workbench");
1440
+ },
1441
+ children: t("diagnostics.peers.useForOpenChannel", "Use for Open Channel")
1442
+ }
1443
+ ) })
1444
+ ] }, peer.pubkey)) }),
1445
+ /* @__PURE__ */ jsxs3("label", { style: styles2.fieldLabel, children: [
1446
+ t("diagnostics.peers.connectPeerAddress", "Connect Peer Address"),
1447
+ /* @__PURE__ */ jsx4(
1448
+ "input",
1449
+ {
1450
+ style: styles2.input,
1451
+ value: peerAddress,
1452
+ onChange: (event) => setPeerAddress(event.target.value),
1453
+ placeholder: "/dns4/.../wss/p2p/..."
1454
+ }
1455
+ )
1456
+ ] }),
1457
+ /* @__PURE__ */ jsxs3("div", { style: styles2.rowBetween, children: [
1458
+ /* @__PURE__ */ jsx4(
1459
+ "button",
1460
+ {
1461
+ type: "button",
1462
+ style: withDisabledStyle(styles2.primaryButton, isConnectingPeer || !peerAddress.trim()),
1463
+ disabled: isConnectingPeer || !peerAddress.trim(),
1464
+ onClick: () => {
1465
+ void connectPeerByAddress();
1466
+ },
1467
+ children: isConnectingPeer ? t("diagnostics.peers.connect.loading", "Connecting...") : t("diagnostics.peers.connect", "Connect Peer")
1468
+ }
1469
+ ),
1470
+ /* @__PURE__ */ jsx4(
1471
+ "button",
1472
+ {
1473
+ type: "button",
1474
+ style: withDisabledStyle(styles2.actionButton, isRefreshingPeers || isRefreshingGraph),
1475
+ disabled: isRefreshingPeers || isRefreshingGraph,
1476
+ onClick: () => {
1477
+ void refreshDiagnostics();
1478
+ },
1479
+ children: isRefreshingPeers || isRefreshingGraph ? t("diagnostics.peers.refreshAll.loading", "Refreshing...") : t("diagnostics.peers.refreshAll", "Refresh All")
1480
+ }
1481
+ )
1482
+ ] })
1483
+ ] }),
1484
+ /* @__PURE__ */ jsxs3("section", { style: styles2.section, children: [
1485
+ /* @__PURE__ */ jsxs3("div", { style: styles2.rowBetween, children: [
1486
+ /* @__PURE__ */ jsx4("h4", { style: styles2.sectionTitle, children: t("diagnostics.graph.title", "Graph Snapshot") }),
1487
+ /* @__PURE__ */ jsx4(
1488
+ "button",
1489
+ {
1490
+ type: "button",
1491
+ style: withDisabledStyle(styles2.actionButton, isRefreshingGraph),
1492
+ disabled: isRefreshingGraph,
1493
+ onClick: () => {
1494
+ void refreshGraph();
1495
+ },
1496
+ children: isRefreshingGraph ? t("diagnostics.graph.refresh.loading", "Refreshing...") : t("diagnostics.graph.refresh", "Refresh Graph")
1497
+ }
1498
+ )
1499
+ ] }),
1500
+ /* @__PURE__ */ jsxs3("p", { style: styles2.compactText, children: [
1501
+ t("diagnostics.graph.showing", "showing"),
1502
+ " ",
1503
+ Math.min(graphNodes.length, 3),
1504
+ " ",
1505
+ t("diagnostics.graph.of", "of"),
1506
+ " ",
1507
+ graphNodes.length,
1508
+ " ",
1509
+ t("diagnostics.graph.nodes", "nodes"),
1510
+ ", ",
1511
+ Math.min(graphChannels.length, 2),
1512
+ " ",
1513
+ t("diagnostics.graph.of", "of"),
1514
+ " ",
1515
+ graphChannels.length,
1516
+ " ",
1517
+ t("diagnostics.graph.channels", "channels"),
1518
+ "."
1519
+ ] }),
1520
+ graphNodes.slice(0, 3).map((node) => /* @__PURE__ */ jsxs3("p", { style: styles2.inlineCode, children: [
1521
+ "Node: ",
1522
+ node.node_name || shorten(node.pubkey, 18, 10)
1523
+ ] }, node.pubkey)),
1524
+ graphChannels.slice(0, 2).map((channel) => /* @__PURE__ */ jsxs3(
1525
+ "p",
1526
+ {
1527
+ style: styles2.inlineCode,
1528
+ children: [
1529
+ shorten(channel.node1, 10, 6),
1530
+ " to ",
1531
+ shorten(channel.node2, 10, 6),
1532
+ ";",
1533
+ " ",
1534
+ formatChannelBalance(channel.capacity),
1535
+ " CKB"
1536
+ ]
1537
+ },
1538
+ `${channel.node1}-${channel.node2}-${channel.channel_outpoint.tx_hash}`
1539
+ )),
1540
+ /* @__PURE__ */ jsxs3("details", { children: [
1541
+ /* @__PURE__ */ jsx4("summary", { style: { ...styles2.compactText, cursor: "pointer" }, children: t("diagnostics.graph.rawSnapshot", "Raw graph snapshot") }),
1542
+ /* @__PURE__ */ jsx4(
1543
+ "pre",
1544
+ {
1545
+ style: {
1546
+ ...styles2.inlineCode,
1547
+ marginTop: "0.45rem",
1548
+ maxHeight: "160px",
1549
+ overflow: "auto",
1550
+ background: "#f1f5f9",
1551
+ borderRadius: "0.45rem",
1552
+ padding: "0.45rem"
1553
+ },
1554
+ children: JSON.stringify(
1555
+ {
1556
+ nodes: graphNodes,
1557
+ channels: graphChannels
1558
+ },
1559
+ null,
1560
+ 2
1561
+ )
1562
+ }
1563
+ )
1564
+ ] })
1565
+ ] }),
1566
+ channelOpenFlow.diagnostic ? /* @__PURE__ */ jsx4("div", { style: styles2.notice, children: channelOpenFlow.diagnostic }) : null
1567
+ ] });
1568
+ }
1569
+
1570
+ // src/fiber-node-button/i18n.ts
1571
+ function applyVariables(template, vars) {
1572
+ if (!vars) {
1573
+ return template;
1574
+ }
1575
+ return template.replace(/\{(\w+)\}/g, (full, key) => {
1576
+ const value = vars[key];
1577
+ return value === void 0 ? full : String(value);
1578
+ });
1579
+ }
1580
+ var defaultFiberNodeButtonI18n = (_key, fallback, vars) => applyVariables(fallback, vars);
1581
+
1582
+ // src/fiber-node-button/use-panel-state.ts
1583
+ import { ChannelState as ChannelState2 } from "@fiber-pay/sdk/browser";
1584
+ import {
1585
+ useCallback as useCallback5,
1586
+ useEffect as useEffect4,
1587
+ useId as useId2,
1588
+ useMemo as useMemo3,
1589
+ useRef as useRef4,
1590
+ useState as useState5
1591
+ } from "react";
1592
+
1593
+ // src/use-channel-open-flow.ts
1594
+ import {
1595
+ computeSuggestedFundingAmountCkb,
1596
+ diagnoseExternalFundingFailure,
1597
+ extractRequiredCapacityCkbFromFundingError,
1598
+ openChannelWithExternalFundingFlow,
1599
+ shouldDiagnoseFundingAbortError
1600
+ } from "@fiber-pay/sdk/browser";
1601
+ import { useCallback as useCallback3, useMemo as useMemo2, useState as useState3 } from "react";
1602
+ var SHANNONS_PER_CKB = 100000000n;
1603
+ function toHexPrefixed2(value) {
1604
+ const trimmed = value.trim();
1605
+ if (!trimmed) {
1606
+ throw new Error("Hex value is empty.");
1607
+ }
1608
+ return trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
1609
+ }
1610
+ function ckbToShannons(amountCkb) {
1611
+ const normalized = amountCkb.trim();
1612
+ if (!/^\d+(\.\d+)?$/.test(normalized)) {
1613
+ throw new Error("Funding amount must be a valid CKB number.");
1614
+ }
1615
+ const [wholePart, fracPart = ""] = normalized.split(".");
1616
+ if (fracPart.length > 8 && /[1-9]/.test(fracPart.slice(8))) {
1617
+ throw new Error("Funding amount supports up to 8 decimal places.");
1618
+ }
1619
+ const fracPadded = `${fracPart}00000000`.slice(0, 8);
1620
+ const shannons = BigInt(wholePart) * SHANNONS_PER_CKB + BigInt(fracPadded || "0");
1621
+ if (shannons <= 0n) {
1622
+ throw new Error("Funding amount must be greater than 0.");
1623
+ }
1624
+ return shannons;
1625
+ }
1626
+ function toHookResultFromExternal(result) {
1627
+ return {
1628
+ mode: "external",
1629
+ channelId: result.channelId,
1630
+ fundingTxHash: result.fundingTxHash,
1631
+ unsignedFundingTx: result.unsignedFundingTx,
1632
+ signedFundingTx: result.signedFundingTx
1633
+ };
1634
+ }
1635
+ function useChannelOpenFlow(options) {
1636
+ const { node, onLog } = options;
1637
+ const [isOpening, setIsOpening] = useState3(false);
1638
+ const [error, setError] = useState3(null);
1639
+ const [diagnostic, setDiagnostic] = useState3(null);
1640
+ const [suggestedFundingAmountCkb, setSuggestedFundingAmountCkb] = useState3(null);
1641
+ const [lastResult, setLastResult] = useState3(null);
1642
+ const reset = useCallback3(() => {
1643
+ setError(null);
1644
+ setDiagnostic(null);
1645
+ setSuggestedFundingAmountCkb(null);
1646
+ setLastResult(null);
1647
+ }, []);
1648
+ const openChannel = useCallback3(
1649
+ async (params) => {
1650
+ if (!node) {
1651
+ setError("Node is not connected.");
1652
+ return null;
1653
+ }
1654
+ setIsOpening(true);
1655
+ setError(null);
1656
+ setDiagnostic(null);
1657
+ setSuggestedFundingAmountCkb(null);
1658
+ let requestedFundingShannons;
1659
+ let effectiveFundingLockScript = params.fundingLockScript;
1660
+ try {
1661
+ requestedFundingShannons = ckbToShannons(params.fundingAmountCkb);
1662
+ const pubkey = toHexPrefixed2(params.pubkey);
1663
+ if (!params.externalWallet) {
1664
+ const openResult = await node.openChannel({
1665
+ pubkey,
1666
+ funding_amount: `0x${requestedFundingShannons.toString(16)}`
1667
+ });
1668
+ const result2 = {
1669
+ mode: "internal",
1670
+ channelId: openResult.temporary_channel_id
1671
+ };
1672
+ setLastResult(result2);
1673
+ onLog?.(`Internal channel open requested: ${openResult.temporary_channel_id}`);
1674
+ return result2;
1675
+ }
1676
+ if (!params.signFundingTx) {
1677
+ throw new Error("Missing signFundingTx callback for external wallet funding flow.");
1678
+ }
1679
+ const nodeInfo = await node.nodeInfo();
1680
+ const defaultScript = nodeInfo.default_funding_lock_script;
1681
+ const shutdownScript = params.shutdownScript ?? defaultScript;
1682
+ effectiveFundingLockScript = params.fundingLockScript ?? defaultScript;
1683
+ const flowResult = await openChannelWithExternalFundingFlow({
1684
+ node,
1685
+ params: {
1686
+ pubkey,
1687
+ funding_amount: `0x${requestedFundingShannons.toString(16)}`,
1688
+ shutdown_script: shutdownScript,
1689
+ funding_lock_script: effectiveFundingLockScript,
1690
+ funding_lock_script_cell_deps: params.fundingLockScriptCellDeps
1691
+ },
1692
+ signFundingTx: params.signFundingTx
1693
+ });
1694
+ const result = toHookResultFromExternal(flowResult);
1695
+ setLastResult(result);
1696
+ onLog?.(
1697
+ `External funding completed: channel=${flowResult.channelId}, tx=${flowResult.fundingTxHash}`
1698
+ );
1699
+ return result;
1700
+ } catch (unknownError) {
1701
+ const message = unknownError instanceof Error ? unknownError.message : String(unknownError);
1702
+ let displayMessage = message;
1703
+ const requiredCapacity = extractRequiredCapacityCkbFromFundingError(message);
1704
+ if (requiredCapacity) {
1705
+ try {
1706
+ const suggested = computeSuggestedFundingAmountCkb(
1707
+ params.fundingAmountCkb,
1708
+ requiredCapacity
1709
+ );
1710
+ if (suggested) {
1711
+ setSuggestedFundingAmountCkb(suggested);
1712
+ displayMessage = `Insufficient capacity: current amount ${params.fundingAmountCkb} CKB may not cover fee. Suggested amount: ${suggested} CKB. Original error: ${message}`;
1713
+ }
1714
+ } catch {
1715
+ }
1716
+ }
1717
+ setError(displayMessage);
1718
+ if (params.externalWallet) {
1719
+ const hasKnownAbortPattern = shouldDiagnoseFundingAbortError(message);
1720
+ if (!hasKnownAbortPattern) {
1721
+ onLog?.(
1722
+ "External funding error did not match known abort patterns; running best-effort diagnostics."
1723
+ );
1724
+ }
1725
+ const targetPubkey = (() => {
1726
+ try {
1727
+ return toHexPrefixed2(params.pubkey);
1728
+ } catch {
1729
+ return void 0;
1730
+ }
1731
+ })();
1732
+ try {
1733
+ const diagnoseResult = await diagnoseExternalFundingFailure({
1734
+ node,
1735
+ rawError: message,
1736
+ targetPubkey,
1737
+ fundingLockScript: effectiveFundingLockScript,
1738
+ requestedFundingShannons,
1739
+ ckbRpcUrl: params.ckbRpcUrl
1740
+ });
1741
+ if (diagnoseResult.summary) {
1742
+ setDiagnostic(diagnoseResult.summary);
1743
+ onLog?.(`External funding diagnostic: ${diagnoseResult.summary}`);
1744
+ } else {
1745
+ const fallbackDiagnostic = "No additional channel diagnostics were available for this external funding error.";
1746
+ setDiagnostic(fallbackDiagnostic);
1747
+ onLog?.(fallbackDiagnostic);
1748
+ }
1749
+ } catch (diagnosticError) {
1750
+ const diagnosticMessage = diagnosticError instanceof Error ? diagnosticError.message : String(diagnosticError);
1751
+ const fallbackDiagnostic = "External funding diagnostics failed; no additional diagnostic details are available.";
1752
+ setDiagnostic(fallbackDiagnostic);
1753
+ onLog?.(`External funding diagnostics failed: ${diagnosticMessage}`);
1754
+ }
1755
+ }
1756
+ onLog?.(`Channel open flow failed: ${message}`);
1757
+ return null;
1758
+ } finally {
1759
+ setIsOpening(false);
1760
+ }
1761
+ },
1762
+ [node, onLog]
1763
+ );
1764
+ return useMemo2(
1765
+ () => ({
1766
+ openChannel,
1767
+ reset,
1768
+ isOpening,
1769
+ error,
1770
+ diagnostic,
1771
+ suggestedFundingAmountCkb,
1772
+ lastResult
1773
+ }),
1774
+ [openChannel, reset, isOpening, error, diagnostic, suggestedFundingAmountCkb, lastResult]
1775
+ );
1776
+ }
1777
+
1778
+ // src/use-fiber-payment.ts
1779
+ import { useCallback as useCallback4, useEffect as useEffect3, useRef as useRef3, useState as useState4 } from "react";
1780
+ function asErrorMessage2(error) {
1781
+ if (error instanceof Error) {
1782
+ return error.message;
1783
+ }
1784
+ return String(error);
1785
+ }
1786
+ function useFiberPayment(node) {
1787
+ const [isPaying, setIsPaying] = useState4(false);
1788
+ const [paymentResult, setPaymentResult] = useState4(null);
1789
+ const [error, setError] = useState4(null);
1790
+ const isMountedRef = useRef3(true);
1791
+ useEffect3(
1792
+ () => () => {
1793
+ isMountedRef.current = false;
1794
+ },
1795
+ []
1796
+ );
1797
+ const ensureNode = useCallback4(() => {
1798
+ if (!node) {
1799
+ throw new Error("Node is not initialized");
1800
+ }
1801
+ return node;
1802
+ }, [node]);
1803
+ const parseInvoiceInternal = useCallback4(
1804
+ async (invoice) => {
1805
+ const activeNode = ensureNode();
1806
+ return activeNode.parseInvoice({ invoice });
1807
+ },
1808
+ [ensureNode]
1809
+ );
1810
+ const sendPaymentInternal = useCallback4(
1811
+ async (invoice) => {
1812
+ const activeNode = ensureNode();
1813
+ return activeNode.sendPayment({ invoice });
1814
+ },
1815
+ [ensureNode]
1816
+ );
1817
+ const waitForPaymentInternal = useCallback4(
1818
+ async (paymentHash) => {
1819
+ const activeNode = ensureNode();
1820
+ return activeNode.waitForPayment(paymentHash);
1821
+ },
1822
+ [ensureNode]
1823
+ );
1824
+ const parseInvoice = useCallback4(
1825
+ async (invoice) => {
1826
+ if (isMountedRef.current) {
1827
+ setError(null);
1828
+ }
1829
+ try {
1830
+ return await parseInvoiceInternal(invoice);
1831
+ } catch (parseError) {
1832
+ if (isMountedRef.current) {
1833
+ setError(asErrorMessage2(parseError));
1834
+ }
1835
+ throw parseError;
1836
+ }
1837
+ },
1838
+ [parseInvoiceInternal]
1839
+ );
1840
+ const sendPayment = useCallback4(
1841
+ async (invoice) => {
1842
+ if (isMountedRef.current) {
1843
+ setIsPaying(true);
1844
+ setError(null);
1845
+ setPaymentResult(null);
1846
+ }
1847
+ try {
1848
+ return await sendPaymentInternal(invoice);
1849
+ } catch (sendError) {
1850
+ if (isMountedRef.current) {
1851
+ setError(asErrorMessage2(sendError));
1852
+ }
1853
+ throw sendError;
1854
+ } finally {
1855
+ if (isMountedRef.current) {
1856
+ setIsPaying(false);
1857
+ }
1858
+ }
1859
+ },
1860
+ [sendPaymentInternal]
1861
+ );
1862
+ const waitForPayment = useCallback4(
1863
+ async (paymentHash) => {
1864
+ if (isMountedRef.current) {
1865
+ setIsPaying(true);
1866
+ setError(null);
1867
+ setPaymentResult(null);
1868
+ }
1869
+ try {
1870
+ const result = await waitForPaymentInternal(paymentHash);
1871
+ if (isMountedRef.current) {
1872
+ setPaymentResult(result);
1873
+ }
1874
+ return result;
1875
+ } catch (waitError) {
1876
+ if (isMountedRef.current) {
1877
+ setError(asErrorMessage2(waitError));
1878
+ }
1879
+ throw waitError;
1880
+ } finally {
1881
+ if (isMountedRef.current) {
1882
+ setIsPaying(false);
1883
+ }
1884
+ }
1885
+ },
1886
+ [waitForPaymentInternal]
1887
+ );
1888
+ const payInvoice = useCallback4(
1889
+ async (invoice) => {
1890
+ if (isMountedRef.current) {
1891
+ setIsPaying(true);
1892
+ setError(null);
1893
+ setPaymentResult(null);
1894
+ }
1895
+ try {
1896
+ const parsed = await parseInvoiceInternal(invoice);
1897
+ await sendPaymentInternal(invoice);
1898
+ const paymentHash = parsed.invoice.data.payment_hash;
1899
+ const result = await waitForPaymentInternal(paymentHash);
1900
+ if (result.status === "Failed") {
1901
+ throw new Error(result.failed_error ?? "Payment failed during routing/execution");
1902
+ }
1903
+ if (isMountedRef.current) {
1904
+ setPaymentResult(result);
1905
+ }
1906
+ } catch (payError) {
1907
+ if (isMountedRef.current) {
1908
+ setError(asErrorMessage2(payError));
1909
+ }
1910
+ } finally {
1911
+ if (isMountedRef.current) {
1912
+ setIsPaying(false);
1913
+ }
1914
+ }
1915
+ },
1916
+ [parseInvoiceInternal, sendPaymentInternal, waitForPaymentInternal]
1917
+ );
1918
+ return {
1919
+ parseInvoice,
1920
+ sendPayment,
1921
+ waitForPayment,
1922
+ payInvoice,
1923
+ isPaying,
1924
+ paymentResult,
1925
+ error
1926
+ };
1927
+ }
1928
+
1929
+ // src/fiber-node-button/use-panel-state.ts
1930
+ function useFiberNodeButtonPanelState(props) {
1931
+ const {
1932
+ network,
1933
+ fiber,
1934
+ onLog,
1935
+ onError,
1936
+ initialPeerPubkey,
1937
+ initialPeerAddress,
1938
+ initialFundingAmountCkb,
1939
+ externalFunding
1940
+ } = props;
1941
+ const [activeTab, setActiveTab] = useState5("workbench");
1942
+ const [peerPubkey, setPeerPubkey] = useState5(initialPeerPubkey);
1943
+ const [peerAddress, setPeerAddress] = useState5(initialPeerAddress);
1944
+ const [fundingAmountCkb, setFundingAmountCkb] = useState5(initialFundingAmountCkb);
1945
+ const [connectedPeers, setConnectedPeers] = useState5([]);
1946
+ const [isRefreshingPeers, setIsRefreshingPeers] = useState5(false);
1947
+ const [isConnectingPeer, setIsConnectingPeer] = useState5(false);
1948
+ const [graphNodes, setGraphNodes] = useState5([]);
1949
+ const [graphChannels, setGraphChannels] = useState5([]);
1950
+ const [isRefreshingGraph, setIsRefreshingGraph] = useState5(false);
1951
+ const [channels, setChannels] = useState5([]);
1952
+ const [channelFilter, setChannelFilter] = useState5("active");
1953
+ const [isRefreshingChannels, setIsRefreshingChannels] = useState5(false);
1954
+ const [closingChannelId, setClosingChannelId] = useState5(null);
1955
+ const [selectedChannelId, setSelectedChannelId] = useState5(null);
1956
+ const [forceCloseConfirmOpen, setForceCloseConfirmOpen] = useState5(false);
1957
+ const [invoiceInput, setInvoiceInput] = useState5("");
1958
+ const [createdInvoice, setCreatedInvoice] = useState5("");
1959
+ const [isCreatingInvoice, setIsCreatingInvoice] = useState5(false);
1960
+ const [latestError, setLatestError] = useState5(null);
1961
+ const [statusNotice, setStatusNotice] = useState5(null);
1962
+ const statusTimerRef = useRef4(null);
1963
+ const mountedRef = useRef4(true);
1964
+ const peerListId = useId2();
1965
+ const tabPanelId = useId2();
1966
+ const channelOpenFlow = useChannelOpenFlow({
1967
+ node: fiber.node,
1968
+ onLog
1969
+ });
1970
+ const { payInvoice, isPaying, paymentResult, error: paymentError } = useFiberPayment(fiber.node);
1971
+ const isNodeReady = fiber.isRunning && !!fiber.node;
1972
+ useEffect4(() => {
1973
+ return () => {
1974
+ mountedRef.current = false;
1975
+ if (statusTimerRef.current !== null) {
1976
+ window.clearTimeout(statusTimerRef.current);
1977
+ }
1978
+ };
1979
+ }, []);
1980
+ const flashStatus = useCallback5((text, tone = "info") => {
1981
+ setStatusNotice({ tone, text });
1982
+ if (statusTimerRef.current !== null) {
1983
+ window.clearTimeout(statusTimerRef.current);
1984
+ }
1985
+ statusTimerRef.current = window.setTimeout(() => {
1986
+ setStatusNotice(null);
1987
+ statusTimerRef.current = null;
1988
+ }, 3200);
1989
+ }, []);
1990
+ const reportError = useCallback5(
1991
+ (message) => {
1992
+ setLatestError(message);
1993
+ onError?.(message);
1994
+ onLog?.(`fiber_panel_error_shown: ${summarizeError(message, 120)}`);
1995
+ },
1996
+ [onError, onLog]
1997
+ );
1998
+ const refreshConnectedPeers = useCallback5(async () => {
1999
+ if (!fiber.node) {
2000
+ if (mountedRef.current) {
2001
+ setConnectedPeers([]);
2002
+ }
2003
+ return;
2004
+ }
2005
+ if (!mountedRef.current) {
2006
+ return;
2007
+ }
2008
+ setIsRefreshingPeers(true);
2009
+ try {
2010
+ const peers = await fiber.node.listPeers();
2011
+ if (!mountedRef.current) {
2012
+ return;
2013
+ }
2014
+ setConnectedPeers(peers.peers);
2015
+ setPeerPubkey((prev) => prev.trim() ? prev : peers.peers[0]?.pubkey ?? prev);
2016
+ onLog?.(`Loaded connected peers: ${peers.peers.length}.`);
2017
+ } catch (error) {
2018
+ if (!mountedRef.current) {
2019
+ return;
2020
+ }
2021
+ const message = error instanceof Error ? error.message : String(error);
2022
+ reportError(message);
2023
+ onLog?.(`Refresh peers failed: ${message}`);
2024
+ } finally {
2025
+ if (mountedRef.current) {
2026
+ setIsRefreshingPeers(false);
2027
+ }
2028
+ }
2029
+ }, [fiber.node, onLog, reportError]);
2030
+ const refreshGraph = useCallback5(async () => {
2031
+ if (!fiber.node) {
2032
+ if (mountedRef.current) {
2033
+ setGraphNodes([]);
2034
+ setGraphChannels([]);
2035
+ }
2036
+ return;
2037
+ }
2038
+ if (!mountedRef.current) {
2039
+ return;
2040
+ }
2041
+ setIsRefreshingGraph(true);
2042
+ try {
2043
+ const [nodesResult, channelsResult] = await Promise.all([
2044
+ fiber.node.graphNodes({ limit: "0x8" }),
2045
+ fiber.node.graphChannels({ limit: "0x8" })
2046
+ ]);
2047
+ if (!mountedRef.current) {
2048
+ return;
2049
+ }
2050
+ setGraphNodes(nodesResult.nodes);
2051
+ setGraphChannels(channelsResult.channels);
2052
+ onLog?.(
2053
+ `Loaded graph: ${nodesResult.nodes.length} nodes, ${channelsResult.channels.length} channels.`
2054
+ );
2055
+ } catch (error) {
2056
+ if (!mountedRef.current) {
2057
+ return;
2058
+ }
2059
+ const message = error instanceof Error ? error.message : String(error);
2060
+ reportError(message);
2061
+ onLog?.(`Refresh graph failed: ${message}`);
2062
+ } finally {
2063
+ if (mountedRef.current) {
2064
+ setIsRefreshingGraph(false);
2065
+ }
2066
+ }
2067
+ }, [fiber.node, onLog, reportError]);
2068
+ const refreshChannels = useCallback5(async () => {
2069
+ if (!fiber.node) {
2070
+ if (mountedRef.current) {
2071
+ setChannels([]);
2072
+ }
2073
+ return;
2074
+ }
2075
+ if (!mountedRef.current) {
2076
+ return;
2077
+ }
2078
+ setIsRefreshingChannels(true);
2079
+ try {
2080
+ const result = await fiber.node.listChannels({ include_closed: true });
2081
+ if (!mountedRef.current) {
2082
+ return;
2083
+ }
2084
+ setChannels(result.channels);
2085
+ onLog?.(`Loaded channels: ${result.channels.length}.`);
2086
+ } catch (error) {
2087
+ if (!mountedRef.current) {
2088
+ return;
2089
+ }
2090
+ const message = error instanceof Error ? error.message : String(error);
2091
+ reportError(message);
2092
+ onLog?.(`Refresh channels failed: ${message}`);
2093
+ } finally {
2094
+ if (mountedRef.current) {
2095
+ setIsRefreshingChannels(false);
2096
+ }
2097
+ }
2098
+ }, [fiber.node, onLog, reportError]);
2099
+ const closeChannel = useCallback5(
2100
+ async (channelId, force = false) => {
2101
+ if (!fiber.node) {
2102
+ reportError("Node is not connected.");
2103
+ return;
2104
+ }
2105
+ setClosingChannelId(channelId);
2106
+ try {
2107
+ const latest = await fiber.node.listChannels({ include_closed: true });
2108
+ const target = latest.channels.find((item) => item.channel_id === channelId);
2109
+ if (!target) {
2110
+ throw new Error(`Channel not found in latest snapshot: ${channelId}.`);
2111
+ }
2112
+ if (target.state.state_name === ChannelState2.Closed) {
2113
+ setChannels(latest.channels);
2114
+ flashStatus("Channel is already closed.", "info");
2115
+ onLog?.(`Channel already closed: ${channelId}`);
2116
+ return;
2117
+ }
2118
+ if (target.state.state_name === ChannelState2.ShuttingDown) {
2119
+ setChannels(latest.channels);
2120
+ flashStatus("Channel is already shutting down.", "info");
2121
+ onLog?.(`Channel is already shutting down: ${channelId}`);
2122
+ return;
2123
+ }
2124
+ if (isPendingChannelState(target.state.state_name)) {
2125
+ const params = {
2126
+ channel_id: channelId
2127
+ };
2128
+ await fiber.node.abandonChannel(params);
2129
+ flashStatus("Pending channel abandoned.", "success");
2130
+ onLog?.(`Pending channel abandoned: ${channelId}`);
2131
+ } else {
2132
+ const params = {
2133
+ channel_id: channelId,
2134
+ force
2135
+ };
2136
+ await fiber.node.shutdownChannel(params);
2137
+ flashStatus(force ? "Force close requested." : "Close requested.", "success");
2138
+ onLog?.(`${force ? "Force close" : "Close"} channel requested: ${channelId}`);
2139
+ }
2140
+ await refreshChannels();
2141
+ } catch (error) {
2142
+ const message = error instanceof Error ? error.message : String(error);
2143
+ reportError(message);
2144
+ } finally {
2145
+ setClosingChannelId(null);
2146
+ }
2147
+ },
2148
+ [fiber.node, flashStatus, onLog, refreshChannels, reportError]
2149
+ );
2150
+ const connectPeerByAddress = useCallback5(async () => {
2151
+ if (!fiber.node) {
2152
+ reportError("Node is not connected.");
2153
+ return;
2154
+ }
2155
+ if (!peerAddress.trim()) {
2156
+ reportError("Peer address is empty.");
2157
+ return;
2158
+ }
2159
+ setIsConnectingPeer(true);
2160
+ try {
2161
+ await fiber.node.connectPeer({
2162
+ address: peerAddress.trim(),
2163
+ save: true
2164
+ });
2165
+ flashStatus("Peer connected.", "success");
2166
+ onLog?.("Peer connected from address input.");
2167
+ await refreshConnectedPeers();
2168
+ } catch (error) {
2169
+ const message = error instanceof Error ? error.message : String(error);
2170
+ reportError(message);
2171
+ onLog?.(`Connect peer failed: ${message}`);
2172
+ } finally {
2173
+ setIsConnectingPeer(false);
2174
+ }
2175
+ }, [fiber.node, flashStatus, onLog, peerAddress, refreshConnectedPeers, reportError]);
2176
+ const openChannel = useCallback5(async () => {
2177
+ if (!fiber.node) {
2178
+ reportError("Node is not connected.");
2179
+ return;
2180
+ }
2181
+ if (!peerPubkey.trim()) {
2182
+ reportError("Target peer pubkey is empty.");
2183
+ return;
2184
+ }
2185
+ channelOpenFlow.reset();
2186
+ try {
2187
+ const pubkey = toHexPrefixed(peerPubkey);
2188
+ if (!externalFunding?.enabled) {
2189
+ const openResult2 = await channelOpenFlow.openChannel({
2190
+ pubkey,
2191
+ fundingAmountCkb,
2192
+ externalWallet: false
2193
+ });
2194
+ if (!openResult2) {
2195
+ return;
2196
+ }
2197
+ flashStatus("Open channel submitted.", "success");
2198
+ onLog?.("fiber_panel_primary_action_clicked: open_channel");
2199
+ await refreshChannels();
2200
+ return;
2201
+ }
2202
+ const resolved = await externalFunding.resolve({
2203
+ node: fiber.node,
2204
+ pubkey,
2205
+ fundingAmountCkb
2206
+ });
2207
+ const openResult = await channelOpenFlow.openChannel({
2208
+ pubkey,
2209
+ fundingAmountCkb,
2210
+ externalWallet: true,
2211
+ shutdownScript: resolved.shutdownScript,
2212
+ fundingLockScript: resolved.fundingLockScript,
2213
+ fundingLockScriptCellDeps: resolved.fundingLockScriptCellDeps,
2214
+ signFundingTx: resolved.signFundingTx,
2215
+ ckbRpcUrl: resolved.ckbRpcUrl
2216
+ });
2217
+ if (!openResult) {
2218
+ return;
2219
+ }
2220
+ flashStatus("Open channel submitted.", "success");
2221
+ onLog?.("fiber_panel_primary_action_clicked: open_channel");
2222
+ await refreshChannels();
2223
+ } catch (error) {
2224
+ const message = error instanceof Error ? error.message : String(error);
2225
+ reportError(message);
2226
+ onLog?.(`Open channel failed: ${message}`);
2227
+ }
2228
+ }, [
2229
+ channelOpenFlow,
2230
+ externalFunding,
2231
+ fiber.node,
2232
+ flashStatus,
2233
+ fundingAmountCkb,
2234
+ onLog,
2235
+ peerPubkey,
2236
+ refreshChannels,
2237
+ reportError
2238
+ ]);
2239
+ const createInvoice = useCallback5(async () => {
2240
+ if (!fiber.node) {
2241
+ reportError("Node is not connected.");
2242
+ return;
2243
+ }
2244
+ setIsCreatingInvoice(true);
2245
+ try {
2246
+ const created = await fiber.node.newInvoice({
2247
+ amount: ONE_CKB_SHANNONS,
2248
+ currency: network === "mainnet" ? "Fibb" : "Fibt",
2249
+ description: "FiberNodeButton invoice"
2250
+ });
2251
+ setCreatedInvoice(created.invoice_address);
2252
+ flashStatus("Invoice created.", "success");
2253
+ onLog?.("fiber_panel_primary_action_clicked: create_invoice");
2254
+ onLog?.(`Invoice created: ${shorten(created.invoice_address, 20, 8)}`);
2255
+ } catch (error) {
2256
+ const message = error instanceof Error ? error.message : String(error);
2257
+ reportError(message);
2258
+ } finally {
2259
+ setIsCreatingInvoice(false);
2260
+ }
2261
+ }, [fiber.node, flashStatus, network, onLog, reportError]);
2262
+ const submitPayment = useCallback5(async () => {
2263
+ if (!invoiceInput.trim()) {
2264
+ reportError("Invoice is empty.");
2265
+ return;
2266
+ }
2267
+ onLog?.("fiber_panel_primary_action_clicked: pay_invoice");
2268
+ await payInvoice(invoiceInput);
2269
+ }, [invoiceInput, onLog, payInvoice, reportError]);
2270
+ useEffect4(() => {
2271
+ if (!fiber.isRunning || !fiber.node) {
2272
+ setConnectedPeers([]);
2273
+ setGraphNodes([]);
2274
+ setGraphChannels([]);
2275
+ setChannels([]);
2276
+ setSelectedChannelId(null);
2277
+ return;
2278
+ }
2279
+ void refreshConnectedPeers();
2280
+ void refreshGraph();
2281
+ void refreshChannels();
2282
+ }, [fiber.isRunning, fiber.node, refreshChannels, refreshConnectedPeers, refreshGraph]);
2283
+ useEffect4(() => {
2284
+ if (paymentError) {
2285
+ reportError(paymentError);
2286
+ }
2287
+ }, [paymentError, reportError]);
2288
+ useEffect4(() => {
2289
+ if (channelOpenFlow.error) {
2290
+ reportError(channelOpenFlow.error);
2291
+ }
2292
+ }, [channelOpenFlow.error, reportError]);
2293
+ useEffect4(() => {
2294
+ if (!paymentResult) {
2295
+ return;
2296
+ }
2297
+ flashStatus(`Payment ${paymentResult.status}.`, "success");
2298
+ onLog?.(`Payment status: ${paymentResult.status}`);
2299
+ }, [flashStatus, onLog, paymentResult]);
2300
+ const channelCounts = useMemo3(() => {
2301
+ const counts = { active: 0, pending: 0, closed: 0, all: channels.length };
2302
+ for (const channel of channels) {
2303
+ counts[getChannelFilterState(channel)] += 1;
2304
+ }
2305
+ return counts;
2306
+ }, [channels]);
2307
+ const visibleChannels = useMemo3(
2308
+ () => channelFilter === "all" ? channels : channels.filter((channel) => getChannelFilterState(channel) === channelFilter),
2309
+ [channelFilter, channels]
2310
+ );
2311
+ const activeChannelCount = channelCounts.active;
2312
+ const selectedChannel = useMemo3(
2313
+ () => channels.find((channel) => channel.channel_id === selectedChannelId) ?? null,
2314
+ [channels, selectedChannelId]
2315
+ );
2316
+ useEffect4(() => {
2317
+ if (!selectedChannelId) {
2318
+ return;
2319
+ }
2320
+ if (!channels.some((channel) => channel.channel_id === selectedChannelId)) {
2321
+ setSelectedChannelId(null);
2322
+ setForceCloseConfirmOpen(false);
2323
+ }
2324
+ }, [channels, selectedChannelId]);
2325
+ useEffect4(() => {
2326
+ const isSelectedVisible = selectedChannelId ? visibleChannels.some((channel) => channel.channel_id === selectedChannelId) : false;
2327
+ if (!isSelectedVisible) {
2328
+ setSelectedChannelId(visibleChannels[0]?.channel_id ?? null);
2329
+ setForceCloseConfirmOpen(false);
2330
+ }
2331
+ }, [selectedChannelId, visibleChannels]);
2332
+ const selectedState = selectedChannel?.state.state_name;
2333
+ const selectedPending = selectedChannel ? isPendingChannelState(selectedState) : false;
2334
+ const selectedCanClose = !!selectedChannel && selectedState !== ChannelState2.Closed && selectedState !== ChannelState2.ShuttingDown;
2335
+ const selectedIsClosing = !!selectedChannel && closingChannelId === selectedChannel.channel_id;
2336
+ const connectorContext = useMemo3(
2337
+ () => ({
2338
+ fiber,
2339
+ externalFundingEnabled: !!externalFunding?.enabled,
2340
+ isOpeningChannel: channelOpenFlow.isOpening
2341
+ }),
2342
+ [channelOpenFlow.isOpening, externalFunding?.enabled, fiber]
2343
+ );
2344
+ const switchTab = useCallback5(
2345
+ (next) => {
2346
+ setActiveTab(next);
2347
+ onLog?.(`fiber_panel_tab_switched: ${next}`);
2348
+ },
2349
+ [onLog]
2350
+ );
2351
+ const refreshDiagnostics = useCallback5(async () => {
2352
+ await Promise.all([refreshConnectedPeers(), refreshGraph()]);
2353
+ flashStatus("Diagnostics refreshed.", "info");
2354
+ }, [flashStatus, refreshConnectedPeers, refreshGraph]);
2355
+ return {
2356
+ // tab
2357
+ activeTab,
2358
+ switchTab,
2359
+ tabPanelId,
2360
+ // inputs
2361
+ peerPubkey,
2362
+ setPeerPubkey,
2363
+ peerAddress,
2364
+ setPeerAddress,
2365
+ fundingAmountCkb,
2366
+ setFundingAmountCkb,
2367
+ peerListId,
2368
+ // peers
2369
+ connectedPeers,
2370
+ isRefreshingPeers,
2371
+ isConnectingPeer,
2372
+ refreshConnectedPeers,
2373
+ connectPeerByAddress,
2374
+ // graph
2375
+ graphNodes,
2376
+ graphChannels,
2377
+ isRefreshingGraph,
2378
+ refreshGraph,
2379
+ // channels
2380
+ channels,
2381
+ channelFilter,
2382
+ setChannelFilter,
2383
+ isRefreshingChannels,
2384
+ refreshChannels,
2385
+ closeChannel,
2386
+ selectedChannelId,
2387
+ setSelectedChannelId,
2388
+ selectedChannel,
2389
+ selectedPending,
2390
+ selectedCanClose,
2391
+ selectedIsClosing,
2392
+ forceCloseConfirmOpen,
2393
+ setForceCloseConfirmOpen,
2394
+ channelCounts,
2395
+ visibleChannels,
2396
+ activeChannelCount,
2397
+ // payments
2398
+ invoiceInput,
2399
+ setInvoiceInput,
2400
+ createdInvoice,
2401
+ isCreatingInvoice,
2402
+ createInvoice,
2403
+ submitPayment,
2404
+ isPaying,
2405
+ paymentResult,
2406
+ // flow
2407
+ channelOpenFlow,
2408
+ openChannel,
2409
+ // notices
2410
+ latestError,
2411
+ statusNotice,
2412
+ // diagnostics aggregate
2413
+ refreshDiagnostics,
2414
+ // misc
2415
+ isNodeReady,
2416
+ connectorContext
2417
+ };
2418
+ }
2419
+
2420
+ // src/fiber-node-button/workbench-tab.tsx
2421
+ import { Fragment as Fragment4, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
2422
+ function WorkbenchTab({
2423
+ state,
2424
+ fiber,
2425
+ externalFunding,
2426
+ renderConnectorSection,
2427
+ renderAction,
2428
+ t
2429
+ }) {
2430
+ const {
2431
+ isNodeReady,
2432
+ connectorContext,
2433
+ channelOpenFlow,
2434
+ peerListId,
2435
+ peerPubkey,
2436
+ setPeerPubkey,
2437
+ connectedPeers,
2438
+ fundingAmountCkb,
2439
+ setFundingAmountCkb,
2440
+ openChannel,
2441
+ isCreatingInvoice,
2442
+ createInvoice,
2443
+ createdInvoice,
2444
+ invoiceInput,
2445
+ setInvoiceInput,
2446
+ isPaying,
2447
+ submitPayment,
2448
+ paymentResult
2449
+ } = state;
2450
+ return /* @__PURE__ */ jsxs4(Fragment4, { children: [
2451
+ /* @__PURE__ */ jsxs4("section", { style: styles2.section, children: [
2452
+ /* @__PURE__ */ jsxs4("div", { style: styles2.rowBetween, children: [
2453
+ /* @__PURE__ */ jsx5("h4", { style: styles2.sectionTitle, children: t("workbench.connectionPrep.title", "Connection Prep") }),
2454
+ /* @__PURE__ */ jsx5("span", { style: styles2.badge, children: isNodeReady ? t("workbench.connectionPrep.connected", "Connected") : t("workbench.connectionPrep.disconnected", "Disconnected") })
2455
+ ] }),
2456
+ /* @__PURE__ */ jsxs4("p", { style: styles2.compactText, children: [
2457
+ t("workbench.connectionPrep.node", "Node"),
2458
+ ":",
2459
+ " ",
2460
+ fiber.nodeInfo?.pubkey ? shorten(fiber.nodeInfo.pubkey, 18, 12) : t("meta.na", "N/A")
2461
+ ] }),
2462
+ /* @__PURE__ */ jsxs4("p", { style: styles2.compactText, children: [
2463
+ t("workbench.connectionPrep.externalWallet", "External wallet"),
2464
+ ":",
2465
+ " ",
2466
+ externalFunding?.enabled ? t("workbench.connectionPrep.enabled", "Enabled") : t("workbench.connectionPrep.disabled", "Disabled")
2467
+ ] }),
2468
+ renderConnectorSection ? /* @__PURE__ */ jsx5("div", { style: { borderTop: "1px solid #e2e8f0", paddingTop: "0.55rem" }, children: renderConnectorSection(connectorContext) }) : null
2469
+ ] }),
2470
+ /* @__PURE__ */ jsxs4("section", { style: styles2.section, children: [
2471
+ /* @__PURE__ */ jsxs4("div", { style: styles2.rowBetween, children: [
2472
+ /* @__PURE__ */ jsx5("h4", { style: styles2.sectionTitle, children: t("workbench.openChannel.title", "Open Channel") }),
2473
+ channelOpenFlow.lastResult ? /* @__PURE__ */ jsx5("span", { style: styles2.badge, children: t("workbench.openChannel.recentSuccess", "Recent Success") }) : null
2474
+ ] }),
2475
+ /* @__PURE__ */ jsxs4("label", { style: styles2.fieldLabel, children: [
2476
+ t("workbench.openChannel.targetPeerPubkey", "Target Peer Pubkey"),
2477
+ /* @__PURE__ */ jsx5(
2478
+ "input",
2479
+ {
2480
+ style: styles2.input,
2481
+ list: peerListId,
2482
+ value: peerPubkey,
2483
+ onChange: (event) => setPeerPubkey(event.target.value),
2484
+ placeholder: connectedPeers[0]?.pubkey ?? "0x..."
2485
+ }
2486
+ ),
2487
+ /* @__PURE__ */ jsx5("datalist", { id: peerListId, children: connectedPeers.map((peer) => /* @__PURE__ */ jsx5("option", { value: peer.pubkey }, peer.pubkey)) })
2488
+ ] }),
2489
+ /* @__PURE__ */ jsxs4("label", { style: styles2.fieldLabel, children: [
2490
+ t("workbench.openChannel.fundingAmount", "Funding Amount (CKB)"),
2491
+ /* @__PURE__ */ jsx5(
2492
+ "input",
2493
+ {
2494
+ style: styles2.input,
2495
+ value: fundingAmountCkb,
2496
+ onChange: (event) => setFundingAmountCkb(event.target.value),
2497
+ placeholder: "1000"
2498
+ }
2499
+ )
2500
+ ] }),
2501
+ /* @__PURE__ */ jsx5("div", { style: styles2.row, children: renderPanelAction({
2502
+ id: "open-channel",
2503
+ fiber,
2504
+ state,
2505
+ renderAction,
2506
+ t,
2507
+ buttonStyle: styles2.primaryButton,
2508
+ defaultProps: {
2509
+ id: "open-channel",
2510
+ label: t("actions.openChannel", "Open Channel"),
2511
+ loadingLabel: t("actions.openChannel.loading", "Opening..."),
2512
+ disabled: !isNodeReady || channelOpenFlow.isOpening || !peerPubkey.trim(),
2513
+ loading: channelOpenFlow.isOpening,
2514
+ onTrigger: openChannel
2515
+ }
2516
+ }) }),
2517
+ channelOpenFlow.lastResult ? /* @__PURE__ */ jsxs4("p", { style: styles2.compactText, children: [
2518
+ t("workbench.openChannel.lastChannel", "Last channel"),
2519
+ ":",
2520
+ " ",
2521
+ shorten(channelOpenFlow.lastResult.channelId, 14, 8)
2522
+ ] }) : null,
2523
+ channelOpenFlow.suggestedFundingAmountCkb ? /* @__PURE__ */ jsxs4("p", { style: styles2.compactText, children: [
2524
+ t("workbench.openChannel.suggestedAmount", "Suggested amount"),
2525
+ ":",
2526
+ " ",
2527
+ channelOpenFlow.suggestedFundingAmountCkb,
2528
+ " CKB"
2529
+ ] }) : null
2530
+ ] }),
2531
+ /* @__PURE__ */ jsxs4("section", { style: styles2.section, children: [
2532
+ /* @__PURE__ */ jsx5("h4", { style: styles2.sectionTitle, children: t("workbench.payments.title", "Payments") }),
2533
+ /* @__PURE__ */ jsxs4("div", { style: styles2.row, children: [
2534
+ renderPanelAction({
2535
+ id: "create-invoice",
2536
+ fiber,
2537
+ state,
2538
+ renderAction,
2539
+ t,
2540
+ defaultProps: {
2541
+ id: "create-invoice",
2542
+ label: t("actions.createInvoice", "Create Invoice (1 CKB)"),
2543
+ loadingLabel: t("actions.createInvoice.loading", "Creating..."),
2544
+ disabled: isCreatingInvoice || !isNodeReady,
2545
+ loading: isCreatingInvoice,
2546
+ onTrigger: createInvoice
2547
+ }
2548
+ }),
2549
+ createdInvoice ? /* @__PURE__ */ jsx5("span", { style: styles2.compactText, children: shorten(createdInvoice, 20, 10) }) : null
2550
+ ] }),
2551
+ /* @__PURE__ */ jsxs4("label", { style: styles2.fieldLabel, children: [
2552
+ t("workbench.payments.invoice", "Invoice"),
2553
+ /* @__PURE__ */ jsx5(
2554
+ "input",
2555
+ {
2556
+ style: styles2.input,
2557
+ value: invoiceInput,
2558
+ onChange: (event) => setInvoiceInput(event.target.value),
2559
+ placeholder: t("workbench.payments.invoicePlaceholder", "Paste invoice to pay")
2560
+ }
2561
+ )
2562
+ ] }),
2563
+ /* @__PURE__ */ jsxs4("div", { style: styles2.rowBetween, children: [
2564
+ renderPanelAction({
2565
+ id: "pay-invoice",
2566
+ fiber,
2567
+ state,
2568
+ renderAction,
2569
+ t,
2570
+ buttonStyle: styles2.primaryButton,
2571
+ defaultProps: {
2572
+ id: "pay-invoice",
2573
+ label: t("actions.payInvoice", "Pay Invoice"),
2574
+ loadingLabel: t("actions.payInvoice.loading", "Paying..."),
2575
+ disabled: isPaying || !isNodeReady || !invoiceInput.trim(),
2576
+ loading: isPaying,
2577
+ onTrigger: submitPayment
2578
+ }
2579
+ }),
2580
+ /* @__PURE__ */ jsxs4("span", { style: styles2.compactText, children: [
2581
+ t("workbench.payments.status", "Status"),
2582
+ ":",
2583
+ " ",
2584
+ paymentResult?.status ?? t("workbench.payments.idle", "Idle")
2585
+ ] })
2586
+ ] })
2587
+ ] })
2588
+ ] });
2589
+ }
2590
+
2591
+ // src/fiber-node-button/panel.tsx
2592
+ import { Fragment as Fragment5, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
2593
+ function toDomSafeTabId(tabId, index) {
2594
+ const normalized = tabId.trim().replace(/[^A-Za-z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
2595
+ const safeBase = normalized.length > 0 ? normalized : "tab";
2596
+ return `${safeBase}-${index + 1}`;
2597
+ }
2598
+ function resolveTabLabel(tabId, t) {
2599
+ const builtIn = TAB_ITEMS.find((item) => item.id === tabId);
2600
+ if (!builtIn) {
2601
+ return tabId;
2602
+ }
2603
+ if (builtIn.id === "workbench") {
2604
+ return t("tabs.workbench", builtIn.label);
2605
+ }
2606
+ if (builtIn.id === "channels") {
2607
+ return t("tabs.channels", builtIn.label);
2608
+ }
2609
+ return t("tabs.diagnostics", builtIn.label);
2610
+ }
2611
+ function resolveTabs(tabs, t) {
2612
+ const defaultIds = TAB_ITEMS.map((item) => item.id);
2613
+ if (!tabs || tabs.length === 0) {
2614
+ return defaultIds.map((id, index) => ({
2615
+ id,
2616
+ domId: toDomSafeTabId(id, index),
2617
+ label: resolveTabLabel(id, t)
2618
+ }));
2619
+ }
2620
+ const tabConfigById = /* @__PURE__ */ new Map();
2621
+ const orderedIds = [];
2622
+ for (const tabConfig of tabs) {
2623
+ tabConfigById.set(tabConfig.id, tabConfig);
2624
+ if (!orderedIds.includes(tabConfig.id)) {
2625
+ orderedIds.push(tabConfig.id);
2626
+ }
2627
+ }
2628
+ for (const id of defaultIds) {
2629
+ if (!orderedIds.includes(id)) {
2630
+ orderedIds.push(id);
2631
+ }
2632
+ }
2633
+ const resolvedTabs = [];
2634
+ for (const [index, id] of orderedIds.entries()) {
2635
+ const config = tabConfigById.get(id);
2636
+ if (config?.hidden) {
2637
+ continue;
2638
+ }
2639
+ const label = typeof config?.label === "function" ? config.label(t) : config?.label ?? resolveTabLabel(id, t);
2640
+ const tabItem = {
2641
+ id,
2642
+ domId: toDomSafeTabId(id, index),
2643
+ label
2644
+ };
2645
+ if (config?.render) {
2646
+ tabItem.render = config.render;
2647
+ }
2648
+ resolvedTabs.push(tabItem);
2649
+ }
2650
+ return resolvedTabs;
2651
+ }
2652
+ function FiberNodeButtonPanel(props) {
2653
+ const {
2654
+ dropdownContext,
2655
+ fiber,
2656
+ onLog,
2657
+ externalFunding,
2658
+ renderConnectorSection,
2659
+ tabs,
2660
+ renderTabContent,
2661
+ renderAction
2662
+ } = props;
2663
+ const t = props.t ?? defaultFiberNodeButtonI18n;
2664
+ const state = useFiberNodeButtonPanelState(props);
2665
+ const forceCloseDialogRef = useRef5(null);
2666
+ const {
2667
+ activeTab,
2668
+ switchTab,
2669
+ tabPanelId,
2670
+ statusNotice,
2671
+ latestError,
2672
+ activeChannelCount,
2673
+ connectedPeers,
2674
+ forceCloseConfirmOpen,
2675
+ setForceCloseConfirmOpen,
2676
+ selectedChannel,
2677
+ closeChannel,
2678
+ openChannel,
2679
+ createInvoice,
2680
+ submitPayment
2681
+ } = state;
2682
+ const tabActions = useMemo4(
2683
+ () => ({
2684
+ openChannel: async () => {
2685
+ await openChannel();
2686
+ },
2687
+ createInvoice: async () => {
2688
+ await createInvoice();
2689
+ },
2690
+ payInvoice: async () => {
2691
+ await submitPayment();
2692
+ },
2693
+ closeChannel: async (channelId) => {
2694
+ await closeChannel(channelId, false);
2695
+ },
2696
+ forceCloseChannel: async (channelId) => {
2697
+ await closeChannel(channelId, true);
2698
+ }
2699
+ }),
2700
+ [closeChannel, createInvoice, openChannel, submitPayment]
2701
+ );
2702
+ const tabContext = useMemo4(
2703
+ () => ({
2704
+ fiber,
2705
+ state,
2706
+ externalFundingEnabled: !!externalFunding?.enabled,
2707
+ t,
2708
+ actions: tabActions
2709
+ }),
2710
+ [externalFunding?.enabled, fiber, state, t, tabActions]
2711
+ );
2712
+ const resolvedTabs = useMemo4(() => resolveTabs(tabs, t), [t, tabs]);
2713
+ const effectiveActiveTab = useMemo4(
2714
+ () => resolvedTabs.some((tab) => tab.id === activeTab) ? activeTab : resolvedTabs[0]?.id ?? null,
2715
+ [activeTab, resolvedTabs]
2716
+ );
2717
+ const selectedResolvedTab = useMemo4(
2718
+ () => resolvedTabs.find((tab) => tab.id === effectiveActiveTab) ?? null,
2719
+ [effectiveActiveTab, resolvedTabs]
2720
+ );
2721
+ const tabListStyle = useMemo4(
2722
+ () => ({
2723
+ ...styles2.tabList,
2724
+ gridTemplateColumns: `repeat(${Math.max(1, resolvedTabs.length)}, minmax(112px, 1fr))`,
2725
+ overflowX: "auto"
2726
+ }),
2727
+ [resolvedTabs.length]
2728
+ );
2729
+ const overriddenTabContent = effectiveActiveTab ? renderTabContent?.(effectiveActiveTab, tabContext) : void 0;
2730
+ let tabContent = overriddenTabContent;
2731
+ if (tabContent === void 0) {
2732
+ if (selectedResolvedTab?.render) {
2733
+ tabContent = selectedResolvedTab.render(tabContext);
2734
+ } else if (effectiveActiveTab === "workbench") {
2735
+ tabContent = /* @__PURE__ */ jsx6(
2736
+ WorkbenchTab,
2737
+ {
2738
+ state,
2739
+ fiber,
2740
+ externalFunding,
2741
+ renderConnectorSection,
2742
+ renderAction,
2743
+ t
2744
+ }
2745
+ );
2746
+ } else if (effectiveActiveTab === "channels") {
2747
+ tabContent = /* @__PURE__ */ jsx6(ChannelsTab, { state, onLog, renderAction, t, fiber });
2748
+ } else if (effectiveActiveTab === "diagnostics") {
2749
+ tabContent = /* @__PURE__ */ jsx6(DiagnosticsTab, { state, t });
2750
+ } else if (effectiveActiveTab === null) {
2751
+ tabContent = /* @__PURE__ */ jsx6("div", { style: styles2.notice, children: t("tabs.empty", "No visible tabs are configured.") });
2752
+ } else {
2753
+ tabContent = /* @__PURE__ */ jsx6("div", { style: styles2.notice, children: t("tabs.unimplemented", "Tab content is not implemented.") });
2754
+ }
2755
+ }
2756
+ useEffect5(() => {
2757
+ if (forceCloseConfirmOpen) {
2758
+ forceCloseDialogRef.current?.focus();
2759
+ }
2760
+ }, [forceCloseConfirmOpen]);
2761
+ useEffect5(() => {
2762
+ if (resolvedTabs.length === 0) {
2763
+ return;
2764
+ }
2765
+ if (!resolvedTabs.some((tab) => tab.id === activeTab)) {
2766
+ switchTab(resolvedTabs[0].id);
2767
+ }
2768
+ }, [activeTab, resolvedTabs, switchTab]);
2769
+ return /* @__PURE__ */ jsxs5("div", { style: styles2.shell, children: [
2770
+ /* @__PURE__ */ jsxs5("header", { style: styles2.globalBar, children: [
2771
+ /* @__PURE__ */ jsxs5("div", { style: styles2.globalRow, children: [
2772
+ /* @__PURE__ */ jsxs5("div", { style: styles2.globalMetrics, children: [
2773
+ /* @__PURE__ */ jsxs5("span", { style: styles2.metricInline, children: [
2774
+ /* @__PURE__ */ jsx6(
2775
+ "span",
2776
+ {
2777
+ style: {
2778
+ ...styles2.metricDot,
2779
+ background: fiber.state === "running" ? "#16a34a" : fiber.state === "error" ? "#dc2626" : "#64748b"
2780
+ },
2781
+ "aria-hidden": "true"
2782
+ }
2783
+ ),
2784
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricMain, children: fiber.state }),
2785
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricSub, children: t("metrics.node", "Node") })
2786
+ ] }),
2787
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricDivider, "aria-hidden": "true", children: "|" }),
2788
+ /* @__PURE__ */ jsxs5("span", { style: styles2.metricInline, children: [
2789
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricMain, children: externalFunding?.enabled ? t("metrics.funding.external", "External") : t("metrics.funding.internal", "Internal") }),
2790
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricSub, children: t("metrics.funding", "Funding") })
2791
+ ] }),
2792
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricDivider, "aria-hidden": "true", children: "|" }),
2793
+ /* @__PURE__ */ jsxs5("span", { style: styles2.metricInline, children: [
2794
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricMain, children: activeChannelCount }),
2795
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricSub, children: t("metrics.active", "Active") })
2796
+ ] }),
2797
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricDivider, "aria-hidden": "true", children: "|" }),
2798
+ /* @__PURE__ */ jsxs5("span", { style: styles2.metricInline, children: [
2799
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricMain, children: connectedPeers.length }),
2800
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricSub, children: t("metrics.peers", "Peers") })
2801
+ ] }),
2802
+ latestError ? /* @__PURE__ */ jsxs5(Fragment5, { children: [
2803
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricDivider, "aria-hidden": "true", children: "|" }),
2804
+ /* @__PURE__ */ jsxs5("span", { style: styles2.metricInline, children: [
2805
+ /* @__PURE__ */ jsx6(
2806
+ "span",
2807
+ {
2808
+ style: {
2809
+ ...styles2.metricDot,
2810
+ background: "#dc2626"
2811
+ },
2812
+ "aria-hidden": "true"
2813
+ }
2814
+ ),
2815
+ /* @__PURE__ */ jsx6("span", { style: styles2.metricMain, children: t("metrics.error", "Error") })
2816
+ ] })
2817
+ ] }) : null
2818
+ ] }),
2819
+ /* @__PURE__ */ jsxs5("div", { style: styles2.globalActions, children: [
2820
+ /* @__PURE__ */ jsx6(
2821
+ "button",
2822
+ {
2823
+ type: "button",
2824
+ style: styles2.globalActionButton,
2825
+ onClick: () => {
2826
+ void dropdownContext.disconnect();
2827
+ },
2828
+ "aria-label": t("actions.disconnect.aria", "Disconnect node"),
2829
+ children: t("actions.disconnect", "Disconnect")
2830
+ }
2831
+ ),
2832
+ /* @__PURE__ */ jsx6(
2833
+ "button",
2834
+ {
2835
+ type: "button",
2836
+ style: styles2.globalActionButton,
2837
+ onClick: () => {
2838
+ dropdownContext.closeDropdown();
2839
+ },
2840
+ "aria-label": t("actions.closePanel.aria", "Close panel"),
2841
+ children: t("actions.closePanel", "Close Panel")
2842
+ }
2843
+ )
2844
+ ] })
2845
+ ] }),
2846
+ /* @__PURE__ */ jsxs5("div", { style: styles2.globalMeta, children: [
2847
+ /* @__PURE__ */ jsxs5("p", { style: styles2.inlineCode, children: [
2848
+ t("meta.node", "Node"),
2849
+ ":",
2850
+ " ",
2851
+ fiber.nodeInfo?.pubkey ? shorten(fiber.nodeInfo.pubkey, 18, 12) : t("meta.na", "N/A")
2852
+ ] }),
2853
+ latestError ? /* @__PURE__ */ jsxs5("p", { style: styles2.globalErrorInline, children: [
2854
+ t("meta.recentError", "Recent error"),
2855
+ ": ",
2856
+ summarizeError(latestError, 92)
2857
+ ] }) : null
2858
+ ] })
2859
+ ] }),
2860
+ /* @__PURE__ */ jsx6("div", { role: "tablist", "aria-label": t("tabs.aria", "Fiber panel tabs"), style: tabListStyle, children: resolvedTabs.map((tab) => {
2861
+ const selected = effectiveActiveTab === tab.id;
2862
+ return /* @__PURE__ */ jsx6(
2863
+ "button",
2864
+ {
2865
+ id: `${tabPanelId}-tab-${tab.domId}`,
2866
+ type: "button",
2867
+ role: "tab",
2868
+ "aria-selected": selected,
2869
+ "aria-controls": selected ? `${tabPanelId}-panel-${tab.domId}` : void 0,
2870
+ style: selected ? styles2.tabButtonActive : styles2.tabButton,
2871
+ onClick: () => switchTab(tab.id),
2872
+ children: tab.label
2873
+ },
2874
+ tab.id
2875
+ );
2876
+ }) }),
2877
+ /* @__PURE__ */ jsxs5(
2878
+ "div",
2879
+ {
2880
+ id: `${tabPanelId}-panel-${selectedResolvedTab?.domId ?? "empty"}`,
2881
+ role: "tabpanel",
2882
+ "aria-labelledby": selectedResolvedTab ? `${tabPanelId}-tab-${selectedResolvedTab.domId}` : void 0,
2883
+ style: styles2.content,
2884
+ children: [
2885
+ statusNotice ? /* @__PURE__ */ jsx6(
2886
+ "div",
2887
+ {
2888
+ style: {
2889
+ ...styles2.notice,
2890
+ ...statusNotice.tone === "success" ? styles2.successNotice : {}
2891
+ },
2892
+ children: statusNotice.text
2893
+ }
2894
+ ) : null,
2895
+ tabContent,
2896
+ latestError ? /* @__PURE__ */ jsx6("div", { style: styles2.errorNotice, children: latestError }) : null
2897
+ ]
2898
+ }
2899
+ ),
2900
+ forceCloseConfirmOpen && selectedChannel ? /* @__PURE__ */ jsx6("div", { style: styles2.dialogBackdrop, children: /* @__PURE__ */ jsxs5(
2901
+ "div",
2902
+ {
2903
+ ref: forceCloseDialogRef,
2904
+ role: "dialog",
2905
+ "aria-modal": "true",
2906
+ "aria-label": t("dialog.forceClose.aria", "Force close confirmation"),
2907
+ tabIndex: -1,
2908
+ style: styles2.dialogCard,
2909
+ onKeyDown: (event) => {
2910
+ if (event.key === "Escape") {
2911
+ setForceCloseConfirmOpen(false);
2912
+ }
2913
+ },
2914
+ children: [
2915
+ /* @__PURE__ */ jsx6("h4", { style: styles2.sectionTitle, children: t("dialog.forceClose.title", "Force close this channel?") }),
2916
+ /* @__PURE__ */ jsx6("p", { style: styles2.compactText, children: t(
2917
+ "dialog.forceClose.description",
2918
+ "This action may immediately broadcast a unilateral close transaction, can lock liquidity until settlement, and may produce additional fees. Continue only if normal close cannot proceed."
2919
+ ) }),
2920
+ /* @__PURE__ */ jsxs5("p", { style: styles2.inlineCode, children: [
2921
+ t("dialog.forceClose.channel", "Channel"),
2922
+ ":",
2923
+ " ",
2924
+ shorten(selectedChannel.channel_id, 20, 12)
2925
+ ] }),
2926
+ /* @__PURE__ */ jsxs5("div", { style: { ...styles2.row, justifyContent: "flex-end" }, children: [
2927
+ /* @__PURE__ */ jsx6(
2928
+ "button",
2929
+ {
2930
+ type: "button",
2931
+ style: styles2.actionButton,
2932
+ onClick: () => {
2933
+ setForceCloseConfirmOpen(false);
2934
+ },
2935
+ children: t("dialog.forceClose.cancel", "Cancel")
2936
+ }
2937
+ ),
2938
+ /* @__PURE__ */ jsx6(
2939
+ "button",
2940
+ {
2941
+ type: "button",
2942
+ style: styles2.dangerButton,
2943
+ onClick: () => {
2944
+ setForceCloseConfirmOpen(false);
2945
+ onLog?.("fiber_channel_force_close_confirmed");
2946
+ void closeChannel(selectedChannel.channel_id, true);
2947
+ },
2948
+ children: t("dialog.forceClose.confirm", "Confirm Force Close")
2949
+ }
2950
+ )
2951
+ ] })
2952
+ ]
2953
+ }
2954
+ ) }) : null
2955
+ ] });
2956
+ }
2957
+
2958
+ // src/fiber-node-button/index.tsx
2959
+ import { jsx as jsx7 } from "react/jsx-runtime";
2960
+ function FiberNodeButton(props) {
2961
+ const {
2962
+ network = "testnet",
2963
+ fiber: externalFiber,
2964
+ strategy = "passkey",
2965
+ externalWallet = false,
2966
+ password,
2967
+ walletId,
2968
+ passkeyUsername = "User",
2969
+ wasmFactory,
2970
+ nodeConfig,
2971
+ className,
2972
+ style,
2973
+ dropdownStyle,
2974
+ onConnect,
2975
+ onDisconnect,
2976
+ onError,
2977
+ onLog,
2978
+ initialPeerPubkey = "",
2979
+ initialPeerAddress = "",
2980
+ initialFundingAmountCkb = "1000",
2981
+ externalFunding,
2982
+ renderConnectorSection,
2983
+ tabs,
2984
+ renderTabContent,
2985
+ renderAction,
2986
+ t
2987
+ } = props;
2988
+ const managedFiber = useFiberNode({
2989
+ network,
2990
+ walletId,
2991
+ wasmFactory,
2992
+ nodeConfig,
2993
+ externalWallet,
2994
+ enabled: !externalFiber
2995
+ });
2996
+ const fiber = externalFiber ?? managedFiber;
2997
+ const handleConnectButtonError = useCallback6(
2998
+ (error) => {
2999
+ onError?.(error);
3000
+ },
3001
+ [onError]
3002
+ );
3003
+ const renderDropdown = useCallback6(
3004
+ (dropdownContext) => /* @__PURE__ */ jsx7(
3005
+ FiberNodeButtonPanel,
3006
+ {
3007
+ dropdownContext,
3008
+ network,
3009
+ fiber,
3010
+ onLog,
3011
+ onError,
3012
+ initialPeerPubkey,
3013
+ initialPeerAddress,
3014
+ initialFundingAmountCkb,
3015
+ externalFunding,
3016
+ renderConnectorSection,
3017
+ tabs,
3018
+ renderTabContent,
3019
+ renderAction,
3020
+ t
3021
+ }
3022
+ ),
3023
+ [
3024
+ externalFunding,
3025
+ fiber,
3026
+ initialFundingAmountCkb,
3027
+ initialPeerAddress,
3028
+ initialPeerPubkey,
3029
+ network,
3030
+ onError,
3031
+ onLog,
3032
+ renderAction,
3033
+ renderConnectorSection,
3034
+ renderTabContent,
3035
+ t,
3036
+ tabs
3037
+ ]
3038
+ );
3039
+ return /* @__PURE__ */ jsx7(
3040
+ ConnectButton,
3041
+ {
3042
+ fiber,
3043
+ strategy,
3044
+ password,
3045
+ passkeyUsername,
3046
+ onConnect,
3047
+ onDisconnect,
3048
+ onError: handleConnectButtonError,
3049
+ className,
3050
+ style,
3051
+ dropdownStyle: { maxWidth: 460, width: "calc(100vw - 1rem)", ...dropdownStyle },
3052
+ renderConnectedDropdown: renderDropdown
3053
+ }
3054
+ );
3055
+ }
3056
+
3057
+ // src/fiber-pay-quick-card.tsx
3058
+ import { useEffect as useEffect6, useId as useId3, useState as useState6 } from "react";
3059
+ import { Fragment as Fragment6, jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
3060
+ var ONE_CKB_SHANNONS2 = "0x5f5e100";
3061
+ var cardStyle = {
3062
+ border: "1px solid #ddd",
3063
+ borderRadius: 8,
3064
+ padding: 16,
3065
+ maxWidth: 520
3066
+ };
3067
+ var rowStyle = {
3068
+ display: "flex",
3069
+ gap: 8
3070
+ };
3071
+ var rowWithMarginStyle = {
3072
+ ...rowStyle,
3073
+ marginBottom: 8
3074
+ };
3075
+ function FiberPayQuickCard(props) {
3076
+ const network = props.network ?? "testnet";
3077
+ const passkeyUsername = props.passkeyUsername ?? "User";
3078
+ const title = props.title ?? "FiberPay Quick Card";
3079
+ const usesExternalFiber = !!props.fiber;
3080
+ const onError = props.onError;
3081
+ const onInvoiceCreated = props.onInvoiceCreated;
3082
+ const onPaymentResult = props.onPaymentResult;
3083
+ const passwordInputId = useId3();
3084
+ const invoiceInputId = useId3();
3085
+ const managedFiber = useFiberNode({
3086
+ network,
3087
+ walletId: props.walletId,
3088
+ enabled: !usesExternalFiber
3089
+ });
3090
+ const fiber = props.fiber ?? managedFiber;
3091
+ const {
3092
+ node,
3093
+ nodeInfo,
3094
+ state,
3095
+ error: nodeError,
3096
+ isPasskeySupported,
3097
+ hasPasskeyConfigured,
3098
+ startWithPassword,
3099
+ startWithPasskey,
3100
+ createPasskeyAndStart,
3101
+ stop
3102
+ } = fiber;
3103
+ const { payInvoice, isPaying, error: payError, paymentResult } = useFiberPayment(node);
3104
+ const [password, setPassword] = useState6("");
3105
+ const [invoiceInput, setInvoiceInput] = useState6("");
3106
+ const [createdInvoice, setCreatedInvoice] = useState6("");
3107
+ const [isCreatingInvoice, setIsCreatingInvoice] = useState6(false);
3108
+ const [invoiceError, setInvoiceError] = useState6(null);
3109
+ useEffect6(() => {
3110
+ if (nodeError) {
3111
+ onError?.({ scope: "node", message: nodeError });
3112
+ }
3113
+ }, [nodeError, onError]);
3114
+ useEffect6(() => {
799
3115
  if (payError) {
800
3116
  onError?.({ scope: "payment", message: payError });
801
3117
  }
802
3118
  }, [onError, payError]);
803
- useEffect4(() => {
3119
+ useEffect6(() => {
804
3120
  if (paymentResult) {
805
3121
  onPaymentResult?.(paymentResult);
806
3122
  }
@@ -813,7 +3129,7 @@ function FiberPayQuickCard(props) {
813
3129
  setInvoiceError(null);
814
3130
  try {
815
3131
  const created = await node.newInvoice({
816
- amount: ONE_CKB_SHANNONS,
3132
+ amount: ONE_CKB_SHANNONS2,
817
3133
  currency: network === "mainnet" ? "Fibb" : "Fibt",
818
3134
  description: "FiberPay QuickCard invoice"
819
3135
  });
@@ -827,18 +3143,21 @@ function FiberPayQuickCard(props) {
827
3143
  setIsCreatingInvoice(false);
828
3144
  }
829
3145
  };
830
- return /* @__PURE__ */ jsxs2("div", { style: { ...cardStyle, ...props.style }, className: props.className, children: [
831
- /* @__PURE__ */ jsxs2("h3", { children: [
3146
+ return /* @__PURE__ */ jsxs6("div", { style: { ...cardStyle, ...props.style }, className: props.className, children: [
3147
+ /* @__PURE__ */ jsxs6("h3", { children: [
832
3148
  title,
833
3149
  " (",
834
3150
  network,
835
3151
  ")"
836
3152
  ] }),
837
- !nodeInfo ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
838
- isPasskeySupported ? /* @__PURE__ */ jsx2("div", { style: rowWithMarginStyle, children: hasPasskeyConfigured ? /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void startWithPasskey(), children: "Login with Passkey" }) : /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void createPasskeyAndStart(passkeyUsername), children: "Register Passkey" }) }) : null,
839
- /* @__PURE__ */ jsx2("label", { htmlFor: passwordInputId, children: "Password" }),
840
- /* @__PURE__ */ jsxs2("div", { style: rowStyle, children: [
841
- /* @__PURE__ */ jsx2(
3153
+ !nodeInfo ? usesExternalFiber ? /* @__PURE__ */ jsxs6("p", { children: [
3154
+ /* @__PURE__ */ jsx8("strong", { children: "Connection required:" }),
3155
+ " connect the shared node first, then return here to create or pay invoices."
3156
+ ] }) : /* @__PURE__ */ jsxs6(Fragment6, { children: [
3157
+ isPasskeySupported ? /* @__PURE__ */ jsx8("div", { style: rowWithMarginStyle, children: hasPasskeyConfigured ? /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void startWithPasskey(), children: "Login with Passkey" }) : /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void createPasskeyAndStart(passkeyUsername), children: "Register Passkey" }) }) : null,
3158
+ /* @__PURE__ */ jsx8("label", { htmlFor: passwordInputId, children: "Password" }),
3159
+ /* @__PURE__ */ jsxs6("div", { style: rowStyle, children: [
3160
+ /* @__PURE__ */ jsx8(
842
3161
  "input",
843
3162
  {
844
3163
  id: passwordInputId,
@@ -850,31 +3169,31 @@ function FiberPayQuickCard(props) {
850
3169
  placeholder: "Password"
851
3170
  }
852
3171
  ),
853
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void startWithPassword(password), children: "Start with Password" })
3172
+ /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void startWithPassword(password), children: "Start with Password" })
854
3173
  ] })
855
- ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
856
- /* @__PURE__ */ jsxs2("p", { children: [
857
- /* @__PURE__ */ jsx2("strong", { children: "State:" }),
3174
+ ] }) : /* @__PURE__ */ jsxs6(Fragment6, { children: [
3175
+ /* @__PURE__ */ jsxs6("p", { children: [
3176
+ /* @__PURE__ */ jsx8("strong", { children: "State:" }),
858
3177
  " ",
859
3178
  state
860
3179
  ] }),
861
- /* @__PURE__ */ jsxs2("p", { children: [
862
- /* @__PURE__ */ jsx2("strong", { children: "Pubkey:" }),
3180
+ /* @__PURE__ */ jsxs6("p", { children: [
3181
+ /* @__PURE__ */ jsx8("strong", { children: "Pubkey:" }),
863
3182
  " ",
864
3183
  nodeInfo.pubkey
865
3184
  ] }),
866
- /* @__PURE__ */ jsxs2("div", { style: rowWithMarginStyle, children: [
867
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void createInvoice(), disabled: isCreatingInvoice, children: isCreatingInvoice ? "Creating..." : "Create Invoice (1 CKB)" }),
868
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void stop(), children: "Stop Node" })
3185
+ /* @__PURE__ */ jsxs6("div", { style: rowWithMarginStyle, children: [
3186
+ /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void createInvoice(), disabled: isCreatingInvoice, children: isCreatingInvoice ? "Creating..." : "Create Invoice (1 CKB)" }),
3187
+ /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void stop(), children: "Stop Node" })
869
3188
  ] }),
870
- createdInvoice ? /* @__PURE__ */ jsxs2("p", { children: [
871
- /* @__PURE__ */ jsx2("strong", { children: "Created invoice:" }),
3189
+ createdInvoice ? /* @__PURE__ */ jsxs6("p", { children: [
3190
+ /* @__PURE__ */ jsx8("strong", { children: "Created invoice:" }),
872
3191
  " ",
873
3192
  createdInvoice
874
3193
  ] }) : null,
875
- /* @__PURE__ */ jsx2("label", { htmlFor: invoiceInputId, children: "Invoice" }),
876
- /* @__PURE__ */ jsxs2("div", { style: rowStyle, children: [
877
- /* @__PURE__ */ jsx2(
3194
+ /* @__PURE__ */ jsx8("label", { htmlFor: invoiceInputId, children: "Invoice" }),
3195
+ /* @__PURE__ */ jsxs6("div", { style: rowStyle, children: [
3196
+ /* @__PURE__ */ jsx8(
878
3197
  "input",
879
3198
  {
880
3199
  id: invoiceInputId,
@@ -884,26 +3203,26 @@ function FiberPayQuickCard(props) {
884
3203
  placeholder: "Paste invoice to pay"
885
3204
  }
886
3205
  ),
887
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void payInvoice(invoiceInput), disabled: isPaying, children: isPaying ? "Paying..." : "Pay" })
3206
+ /* @__PURE__ */ jsx8("button", { type: "button", onClick: () => void payInvoice(invoiceInput), disabled: isPaying, children: isPaying ? "Paying..." : "Pay" })
888
3207
  ] }),
889
- paymentResult ? /* @__PURE__ */ jsxs2("p", { children: [
890
- /* @__PURE__ */ jsx2("strong", { children: "Payment:" }),
3208
+ paymentResult ? /* @__PURE__ */ jsxs6("p", { children: [
3209
+ /* @__PURE__ */ jsx8("strong", { children: "Payment:" }),
891
3210
  " ",
892
3211
  paymentResult.status
893
3212
  ] }) : null
894
3213
  ] }),
895
- nodeError ? /* @__PURE__ */ jsxs2("p", { style: { color: "#b91c1c" }, children: [
896
- /* @__PURE__ */ jsx2("strong", { children: "Node error:" }),
3214
+ nodeError ? /* @__PURE__ */ jsxs6("p", { style: { color: "#b91c1c" }, children: [
3215
+ /* @__PURE__ */ jsx8("strong", { children: "Node error:" }),
897
3216
  " ",
898
3217
  nodeError
899
3218
  ] }) : null,
900
- payError ? /* @__PURE__ */ jsxs2("p", { style: { color: "#b91c1c" }, children: [
901
- /* @__PURE__ */ jsx2("strong", { children: "Payment error:" }),
3219
+ payError ? /* @__PURE__ */ jsxs6("p", { style: { color: "#b91c1c" }, children: [
3220
+ /* @__PURE__ */ jsx8("strong", { children: "Payment error:" }),
902
3221
  " ",
903
3222
  payError
904
3223
  ] }) : null,
905
- invoiceError ? /* @__PURE__ */ jsxs2("p", { style: { color: "#b91c1c" }, children: [
906
- /* @__PURE__ */ jsx2("strong", { children: "Invoice error:" }),
3224
+ invoiceError ? /* @__PURE__ */ jsxs6("p", { style: { color: "#b91c1c" }, children: [
3225
+ /* @__PURE__ */ jsx8("strong", { children: "Invoice error:" }),
907
3226
  " ",
908
3227
  invoiceError
909
3228
  ] }) : null
@@ -918,12 +3237,12 @@ import {
918
3237
  scriptToAddress
919
3238
  } from "@fiber-pay/sdk/browser";
920
3239
  import {
921
- useCallback as useCallback4,
922
- useEffect as useEffect5,
923
- useRef as useRef4,
924
- useState as useState5
3240
+ useCallback as useCallback7,
3241
+ useEffect as useEffect7,
3242
+ useRef as useRef6,
3243
+ useState as useState7
925
3244
  } from "react";
926
- import { Fragment as Fragment3, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
3245
+ import { Fragment as Fragment7, jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
927
3246
  function truncateMiddle(str, left = 8, right = 8) {
928
3247
  if (str.length <= left + right + 3) return str;
929
3248
  return `${str.slice(0, left)}\u2026${str.slice(-right)}`;
@@ -961,7 +3280,7 @@ async function fetchNodeStats(node, network) {
961
3280
  externalFunding: false
962
3281
  };
963
3282
  }
964
- var styles2 = {
3283
+ var styles3 = {
965
3284
  root: {
966
3285
  fontFamily: "system-ui, -apple-system, sans-serif",
967
3286
  fontSize: "0.875rem",
@@ -1090,7 +3409,7 @@ var STATUS_COLORS = {
1090
3409
  error: { bg: "rgba(239,68,68,0.1)", fg: "#dc2626", dot: "#ef4444" }
1091
3410
  };
1092
3411
  function CopyIcon() {
1093
- return /* @__PURE__ */ jsxs3(
3412
+ return /* @__PURE__ */ jsxs7(
1094
3413
  "svg",
1095
3414
  {
1096
3415
  width: "12",
@@ -1103,26 +3422,26 @@ function CopyIcon() {
1103
3422
  strokeLinejoin: "round",
1104
3423
  "aria-hidden": "true",
1105
3424
  children: [
1106
- /* @__PURE__ */ jsx3("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
1107
- /* @__PURE__ */ jsx3("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
3425
+ /* @__PURE__ */ jsx9("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
3426
+ /* @__PURE__ */ jsx9("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
1108
3427
  ]
1109
3428
  }
1110
3429
  );
1111
3430
  }
1112
3431
  function InfoRow({ label, value, copyable }) {
1113
- return /* @__PURE__ */ jsxs3("div", { style: styles2.infoRow, children: [
1114
- /* @__PURE__ */ jsx3("span", { style: styles2.infoLabel, children: label }),
1115
- /* @__PURE__ */ jsxs3("div", { style: styles2.infoValueWrapper, children: [
1116
- /* @__PURE__ */ jsx3("span", { style: styles2.infoValue, children: truncateMiddle(value, 6, 6) }),
1117
- copyable && /* @__PURE__ */ jsx3(
3432
+ return /* @__PURE__ */ jsxs7("div", { style: styles3.infoRow, children: [
3433
+ /* @__PURE__ */ jsx9("span", { style: styles3.infoLabel, children: label }),
3434
+ /* @__PURE__ */ jsxs7("div", { style: styles3.infoValueWrapper, children: [
3435
+ /* @__PURE__ */ jsx9("span", { style: styles3.infoValue, children: truncateMiddle(value, 6, 6) }),
3436
+ copyable && /* @__PURE__ */ jsx9(
1118
3437
  "button",
1119
3438
  {
1120
3439
  type: "button",
1121
3440
  onClick: () => copyToClipboard(value),
1122
- style: styles2.copyButton,
3441
+ style: styles3.copyButton,
1123
3442
  title: "Copy to clipboard",
1124
3443
  "aria-label": `Copy ${label}`,
1125
- children: /* @__PURE__ */ jsx3(CopyIcon, {})
3444
+ children: /* @__PURE__ */ jsx9(CopyIcon, {})
1126
3445
  }
1127
3446
  )
1128
3447
  ] })
@@ -1138,12 +3457,12 @@ function NodeInfoPanel(props) {
1138
3457
  className,
1139
3458
  style
1140
3459
  } = props;
1141
- const [stats, setStats] = useState5(null);
1142
- const [statsError, setStatsError] = useState5(null);
1143
- const [statsLoading, setStatsLoading] = useState5(false);
1144
- const cancelledRef = useRef4(false);
1145
- const [QRComponent, setQRComponent] = useState5(null);
1146
- useEffect5(() => {
3460
+ const [stats, setStats] = useState7(null);
3461
+ const [statsError, setStatsError] = useState7(null);
3462
+ const [statsLoading, setStatsLoading] = useState7(false);
3463
+ const cancelledRef = useRef6(false);
3464
+ const [QRComponent, setQRComponent] = useState7(null);
3465
+ useEffect7(() => {
1147
3466
  let cancelled = false;
1148
3467
  if (!showQrCode || renderQrCode) return;
1149
3468
  import("qrcode.react").then((mod) => {
@@ -1156,8 +3475,8 @@ function NodeInfoPanel(props) {
1156
3475
  cancelled = true;
1157
3476
  };
1158
3477
  }, [showQrCode, renderQrCode]);
1159
- const loadingRef = useRef4(false);
1160
- const loadStats = useCallback4(async () => {
3478
+ const loadingRef = useRef6(false);
3479
+ const loadStats = useCallback7(async () => {
1161
3480
  if (!node || node.state !== "running" || loadingRef.current) return;
1162
3481
  loadingRef.current = true;
1163
3482
  setStatsLoading(true);
@@ -1174,7 +3493,7 @@ function NodeInfoPanel(props) {
1174
3493
  if (!cancelledRef.current) setStatsLoading(false);
1175
3494
  }
1176
3495
  }, [node, network]);
1177
- useEffect5(() => {
3496
+ useEffect7(() => {
1178
3497
  cancelledRef.current = false;
1179
3498
  if (!node || node.state !== "running") {
1180
3499
  setStats(null);
@@ -1189,21 +3508,21 @@ function NodeInfoPanel(props) {
1189
3508
  };
1190
3509
  }, [node, node?.state, pollInterval, loadStats]);
1191
3510
  if (!node) {
1192
- return /* @__PURE__ */ jsx3("div", { className, style: { ...styles2.root, ...style }, "data-fpay-node-info": "", children: /* @__PURE__ */ jsx3("div", { style: styles2.idle, children: "No node connected" }) });
3511
+ return /* @__PURE__ */ jsx9("div", { className, style: { ...styles3.root, ...style }, "data-fpay-node-info": "", children: /* @__PURE__ */ jsx9("div", { style: styles3.idle, children: "No node connected" }) });
1193
3512
  }
1194
3513
  const nodeState = node.state;
1195
3514
  const statusColor = STATUS_COLORS[nodeState] ?? STATUS_COLORS.idle;
1196
- return /* @__PURE__ */ jsxs3("div", { className, style: { ...styles2.root, ...style }, "data-fpay-node-info": "", children: [
1197
- /* @__PURE__ */ jsxs3(
3515
+ return /* @__PURE__ */ jsxs7("div", { className, style: { ...styles3.root, ...style }, "data-fpay-node-info": "", children: [
3516
+ /* @__PURE__ */ jsxs7(
1198
3517
  "div",
1199
3518
  {
1200
3519
  style: {
1201
- ...styles2.statusBadge,
3520
+ ...styles3.statusBadge,
1202
3521
  backgroundColor: statusColor.bg,
1203
3522
  color: statusColor.fg
1204
3523
  },
1205
3524
  children: [
1206
- /* @__PURE__ */ jsx3(
3525
+ /* @__PURE__ */ jsx9(
1207
3526
  "span",
1208
3527
  {
1209
3528
  style: {
@@ -1219,8 +3538,8 @@ function NodeInfoPanel(props) {
1219
3538
  ]
1220
3539
  }
1221
3540
  ),
1222
- statsLoading && !stats && /* @__PURE__ */ jsxs3("div", { style: styles2.loading, children: [
1223
- /* @__PURE__ */ jsxs3(
3541
+ statsLoading && !stats && /* @__PURE__ */ jsxs7("div", { style: styles3.loading, children: [
3542
+ /* @__PURE__ */ jsxs7(
1224
3543
  "svg",
1225
3544
  {
1226
3545
  width: "12",
@@ -1234,8 +3553,8 @@ function NodeInfoPanel(props) {
1234
3553
  role: "img",
1235
3554
  "aria-label": "Loading",
1236
3555
  children: [
1237
- /* @__PURE__ */ jsx3("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }),
1238
- /* @__PURE__ */ jsx3(
3556
+ /* @__PURE__ */ jsx9("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }),
3557
+ /* @__PURE__ */ jsx9(
1239
3558
  "animateTransform",
1240
3559
  {
1241
3560
  attributeName: "transform",
@@ -1251,22 +3570,22 @@ function NodeInfoPanel(props) {
1251
3570
  ),
1252
3571
  "Loading\u2026"
1253
3572
  ] }),
1254
- statsError && /* @__PURE__ */ jsx3("div", { style: styles2.errorBox, children: statsError }),
1255
- stats && /* @__PURE__ */ jsxs3(Fragment3, { children: [
1256
- /* @__PURE__ */ jsx3(InfoRow, { label: "Pubkey", value: stats.pubkey, copyable: true }),
1257
- stats.externalFunding ? /* @__PURE__ */ jsx3("div", { style: { padding: "0.25rem 0", fontSize: "0.75rem", color: "#6b7280" }, children: "External funding mode" }) : stats.ckbAddress ? /* @__PURE__ */ jsx3(InfoRow, { label: "CKB Address", value: stats.ckbAddress, copyable: true }) : null,
1258
- /* @__PURE__ */ jsxs3("div", { style: styles2.statsGrid, children: [
1259
- /* @__PURE__ */ jsxs3("div", { style: styles2.statCard, children: [
1260
- /* @__PURE__ */ jsx3("div", { style: styles2.statLabel, children: "Peers" }),
1261
- /* @__PURE__ */ jsx3("div", { style: styles2.statValue, children: stats.peers })
3573
+ statsError && /* @__PURE__ */ jsx9("div", { style: styles3.errorBox, children: statsError }),
3574
+ stats && /* @__PURE__ */ jsxs7(Fragment7, { children: [
3575
+ /* @__PURE__ */ jsx9(InfoRow, { label: "Pubkey", value: stats.pubkey, copyable: true }),
3576
+ stats.externalFunding ? /* @__PURE__ */ jsx9("div", { style: { padding: "0.25rem 0", fontSize: "0.75rem", color: "#6b7280" }, children: "External funding mode" }) : stats.ckbAddress ? /* @__PURE__ */ jsx9(InfoRow, { label: "CKB Address", value: stats.ckbAddress, copyable: true }) : null,
3577
+ /* @__PURE__ */ jsxs7("div", { style: styles3.statsGrid, children: [
3578
+ /* @__PURE__ */ jsxs7("div", { style: styles3.statCard, children: [
3579
+ /* @__PURE__ */ jsx9("div", { style: styles3.statLabel, children: "Peers" }),
3580
+ /* @__PURE__ */ jsx9("div", { style: styles3.statValue, children: stats.peers })
1262
3581
  ] }),
1263
- /* @__PURE__ */ jsxs3("div", { style: styles2.statCard, children: [
1264
- /* @__PURE__ */ jsx3("div", { style: styles2.statLabel, children: "Channels" }),
1265
- /* @__PURE__ */ jsx3("div", { style: styles2.statValue, children: stats.channels })
3582
+ /* @__PURE__ */ jsxs7("div", { style: styles3.statCard, children: [
3583
+ /* @__PURE__ */ jsx9("div", { style: styles3.statLabel, children: "Channels" }),
3584
+ /* @__PURE__ */ jsx9("div", { style: styles3.statValue, children: stats.channels })
1266
3585
  ] })
1267
3586
  ] }),
1268
- showQrCode && stats.ckbAddress && /* @__PURE__ */ jsxs3("div", { style: styles2.qrContainer, children: [
1269
- renderQrCode ? renderQrCode(stats.ckbAddress) : QRComponent ? /* @__PURE__ */ jsx3(
3587
+ showQrCode && stats.ckbAddress && /* @__PURE__ */ jsxs7("div", { style: styles3.qrContainer, children: [
3588
+ renderQrCode ? renderQrCode(stats.ckbAddress) : QRComponent ? /* @__PURE__ */ jsx9(
1270
3589
  QRComponent,
1271
3590
  {
1272
3591
  value: stats.ckbAddress,
@@ -1274,11 +3593,11 @@ function NodeInfoPanel(props) {
1274
3593
  bgColor: "transparent",
1275
3594
  fgColor: "currentColor"
1276
3595
  }
1277
- ) : /* @__PURE__ */ jsx3("div", { style: { fontSize: "0.625rem", color: "#9ca3af" }, children: "Install qrcode.react for QR code" }),
1278
- /* @__PURE__ */ jsx3("span", { style: styles2.qrCaption, children: "Scan to deposit CKB" }),
1279
- /* @__PURE__ */ jsxs3("div", { style: styles2.balanceRow, children: [
1280
- /* @__PURE__ */ jsx3("span", { style: { fontSize: "0.75rem", color: "#6b7280" }, children: "Balance" }),
1281
- /* @__PURE__ */ jsxs3(
3596
+ ) : /* @__PURE__ */ jsx9("div", { style: { fontSize: "0.625rem", color: "#9ca3af" }, children: "Install qrcode.react for QR code" }),
3597
+ /* @__PURE__ */ jsx9("span", { style: styles3.qrCaption, children: "Scan to deposit CKB" }),
3598
+ /* @__PURE__ */ jsxs7("div", { style: styles3.balanceRow, children: [
3599
+ /* @__PURE__ */ jsx9("span", { style: { fontSize: "0.75rem", color: "#6b7280" }, children: "Balance" }),
3600
+ /* @__PURE__ */ jsxs7(
1282
3601
  "span",
1283
3602
  {
1284
3603
  style: {
@@ -1298,10 +3617,11 @@ function NodeInfoPanel(props) {
1298
3617
  ] });
1299
3618
  }
1300
3619
  export {
1301
- ChannelState,
3620
+ ChannelState3 as ChannelState,
1302
3621
  ConfigBuilder2 as ConfigBuilder,
1303
3622
  ConnectButton,
1304
3623
  FiberBrowserNode2 as FiberBrowserNode,
3624
+ FiberNodeButton,
1305
3625
  FiberPayQuickCard,
1306
3626
  FiberRpcError,
1307
3627
  NodeInfoPanel,
@@ -1309,14 +3629,15 @@ export {
1309
3629
  PasswordCredentialProvider2 as PasswordCredentialProvider,
1310
3630
  RawKeyCredentialProvider2 as RawKeyCredentialProvider,
1311
3631
  ckbHash,
1312
- ckbToShannons,
3632
+ ckbToShannons2 as ckbToShannons,
1313
3633
  derivePublicKey,
1314
3634
  formatShannonsAsCkb2 as formatShannonsAsCkb,
1315
3635
  fromHex,
1316
3636
  getLockBalanceShannons2 as getLockBalanceShannons,
1317
3637
  scriptToAddress2 as scriptToAddress,
1318
- shannonsToCkb,
3638
+ shannonsToCkb2 as shannonsToCkb,
1319
3639
  toHex,
3640
+ useChannelOpenFlow,
1320
3641
  useFiberNode,
1321
3642
  useFiberPayment
1322
3643
  };