@fiber-pay/react 0.2.5 → 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);
@@ -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,7 +473,8 @@ function ConnectButton(props) {
461
473
  const {
462
474
  network = "testnet",
463
475
  fiber: externalFiber,
464
- strategy,
476
+ strategy = "passkey",
477
+ externalWallet = false,
465
478
  password,
466
479
  walletId,
467
480
  passkeyUsername = "User",
@@ -480,6 +493,7 @@ function ConnectButton(props) {
480
493
  walletId,
481
494
  wasmFactory,
482
495
  nodeConfig,
496
+ externalWallet,
483
497
  enabled: !externalFiber
484
498
  });
485
499
  const fiber = externalFiber ?? internalFiber;
@@ -502,6 +516,8 @@ function ConnectButton(props) {
502
516
  const [showDropdown, setShowDropdown] = useState2(false);
503
517
  const [localError, setLocalError] = useState2(null);
504
518
  const dropdownRef = useRef2(null);
519
+ const dropdownId = useId();
520
+ const lastReportedErrorRef = useRef2(null);
505
521
  const effectiveIsStarting = isConnecting || isStarting;
506
522
  useEffect2(() => ensureKeyframes(), []);
507
523
  useEffect2(() => {
@@ -524,9 +540,18 @@ function ConnectButton(props) {
524
540
  }
525
541
  prevRunningRef.current = isRunning;
526
542
  }, [isRunning, node, nodeInfo, onConnect, onDisconnect]);
543
+ const effectiveError = error ?? localError;
527
544
  useEffect2(() => {
528
- if (error) onError?.(error);
529
- }, [error, onError]);
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]);
530
555
  const handleConnect = useCallback2(async () => {
531
556
  setIsConnecting(true);
532
557
  setLocalError(null);
@@ -547,7 +572,6 @@ function ConnectButton(props) {
547
572
  } catch (err) {
548
573
  const msg = err instanceof Error ? err.message : String(err);
549
574
  setLocalError(msg);
550
- onError?.(msg);
551
575
  } finally {
552
576
  setIsConnecting(false);
553
577
  }
@@ -558,8 +582,7 @@ function ConnectButton(props) {
558
582
  hasPasskeyConfigured,
559
583
  startWithPassword,
560
584
  startWithPasskey,
561
- createPasskeyAndStart,
562
- onError
585
+ createPasskeyAndStart
563
586
  ]);
564
587
  const handleDisconnect = useCallback2(async () => {
565
588
  try {
@@ -567,15 +590,14 @@ function ConnectButton(props) {
567
590
  } catch (err) {
568
591
  const msg = err instanceof Error ? err.message : String(err);
569
592
  setLocalError(msg);
570
- onError?.(msg);
571
593
  } finally {
572
594
  setShowDropdown(false);
573
595
  }
574
- }, [stop, onError]);
596
+ }, [stop]);
575
597
  const closeDropdown = useCallback2(() => {
576
598
  setShowDropdown(false);
577
599
  }, []);
578
- const hasError = !!(error || localError);
600
+ const hasError = !!effectiveError;
579
601
  let buttonLabel;
580
602
  let buttonOnClick;
581
603
  let buttonDisabled = false;
@@ -618,188 +640,2496 @@ function ConnectButton(props) {
618
640
  return /* @__PURE__ */ jsxs("div", { className, style: { ...styles.root, ...style }, "data-fpay-connect-button": "", children: [
619
641
  (hasError || !isPasskeySupported && passkeyUnavailableReason && strategy === "passkey") && /* @__PURE__ */ jsx("span", { style: styles.errorText, children: error || localError || passkeyUnavailableReason }),
620
642
  isRunning ? /* @__PURE__ */ jsxs("div", { style: { position: "relative" }, ref: dropdownRef, children: [
621
- /* @__PURE__ */ jsx("button", { type: "button", onClick: buttonOnClick, style: buttonStyle, children: buttonLabel }),
622
- showDropdown && /* @__PURE__ */ jsx("div", { style: { ...styles.dropdown, ...dropdownStyle }, children: renderConnectedDropdown ? renderConnectedDropdown({
623
- fiber,
624
- closeDropdown,
625
- disconnect: handleDisconnect
626
- }) : /* @__PURE__ */ jsxs(Fragment, { children: [
627
- nodeInfo && /* @__PURE__ */ jsxs(Fragment, { children: [
628
- /* @__PURE__ */ jsxs("div", { style: styles.infoRow, children: [
629
- /* @__PURE__ */ jsx("span", { style: styles.infoLabel, children: "Pubkey" }),
630
- /* @__PURE__ */ jsx("span", { style: styles.infoValue, children: truncateNodeId(nodeInfo.pubkey) })
631
- ] }),
632
- /* @__PURE__ */ jsxs("div", { style: styles.infoRow, children: [
633
- /* @__PURE__ */ jsx("span", { style: styles.infoLabel, children: "State" }),
634
- /* @__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
+ )
635
704
  ] })
636
- ] }),
637
- /* @__PURE__ */ jsx("div", { style: styles.separator }),
638
- /* @__PURE__ */ jsxs(
639
- "button",
640
- {
641
- type: "button",
642
- onClick: () => void handleDisconnect(),
643
- style: styles.disconnectButton,
644
- children: [
645
- /* @__PURE__ */ jsx("span", { children: "Disconnect" }),
646
- /* @__PURE__ */ jsx(
647
- "svg",
648
- {
649
- width: "14",
650
- height: "14",
651
- viewBox: "0 0 24 24",
652
- fill: "none",
653
- stroke: "currentColor",
654
- strokeWidth: "2",
655
- strokeLinecap: "round",
656
- strokeLinejoin: "round",
657
- "aria-hidden": "true",
658
- children: /* @__PURE__ */ jsx("path", { d: "M9 18l6-6-6-6" })
659
- }
660
- )
661
- ]
662
- }
663
- )
664
- ] }) })
705
+ }
706
+ )
665
707
  ] }) : /* @__PURE__ */ jsx("button", { type: "button", onClick: buttonOnClick, disabled: buttonDisabled, style: buttonStyle, children: buttonLabel })
666
708
  ] });
667
709
  }
668
710
 
669
- // src/fiber-pay-quick-card.tsx
670
- import { useEffect as useEffect4, useId, useState as useState4 } from "react";
711
+ // src/fiber-node-button/index.tsx
712
+ import { useCallback as useCallback6 } from "react";
671
713
 
672
- // src/use-fiber-payment.ts
673
- import { useCallback as useCallback3, useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
674
- function asErrorMessage2(error) {
675
- if (error instanceof Error) {
676
- 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
677
1083
  }
678
- 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)}`;
679
1093
  }
680
- function useFiberPayment(node) {
681
- const [isPaying, setIsPaying] = useState3(false);
682
- const [paymentResult, setPaymentResult] = useState3(null);
683
- const [error, setError] = useState3(null);
684
- const isMountedRef = useRef3(true);
685
- useEffect3(
686
- () => () => {
687
- isMountedRef.current = false;
688
- },
689
- []
690
- );
691
- const payInvoice = useCallback3(
692
- async (invoice) => {
693
- if (!node) {
694
- if (isMountedRef.current) {
695
- setError("Node is not initialized");
696
- }
697
- return;
698
- }
699
- if (isMountedRef.current) {
700
- setIsPaying(true);
701
- setError(null);
702
- setPaymentResult(null);
703
- }
704
- try {
705
- const parsed = await node.parseInvoice({ invoice });
706
- await node.sendPayment({ invoice });
707
- const paymentHash = parsed.invoice.data.payment_hash;
708
- const result = await node.waitForPayment(paymentHash);
709
- if (result.status === "Failed") {
710
- throw new Error(result.failed_error ?? "Payment failed during routing/execution");
711
- }
712
- if (isMountedRef.current) {
713
- setPaymentResult(result);
714
- }
715
- } catch (payError) {
716
- if (isMountedRef.current) {
717
- setError(asErrorMessage2(payError));
718
- }
719
- } finally {
720
- if (isMountedRef.current) {
721
- setIsPaying(false);
722
- }
723
- }
724
- },
725
- [node]
726
- );
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
+ }
727
1128
  return {
728
- payInvoice,
729
- isPaying,
730
- paymentResult,
731
- error
1129
+ ...style,
1130
+ opacity: 0.55,
1131
+ cursor: "not-allowed"
732
1132
  };
733
1133
  }
734
1134
 
735
- // src/fiber-pay-quick-card.tsx
736
- import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
737
- var ONE_CKB_SHANNONS = "0x5f5e100";
738
- var cardStyle = {
739
- border: "1px solid #ddd",
740
- borderRadius: 8,
741
- padding: 16,
742
- maxWidth: 520
743
- };
744
- var rowStyle = {
745
- display: "flex",
746
- gap: 8
747
- };
748
- var rowWithMarginStyle = {
749
- ...rowStyle,
750
- marginBottom: 8
751
- };
752
- function FiberPayQuickCard(props) {
753
- const network = props.network ?? "testnet";
754
- const passkeyUsername = props.passkeyUsername ?? "User";
755
- const title = props.title ?? "FiberPay Quick Card";
756
- const onError = props.onError;
757
- const onInvoiceCreated = props.onInvoiceCreated;
758
- const onPaymentResult = props.onPaymentResult;
759
- const passwordInputId = useId();
760
- const invoiceInputId = useId();
1135
+ // src/fiber-node-button/render-action.tsx
1136
+ import { jsx as jsx2 } from "react/jsx-runtime";
1137
+ function renderPanelAction(options) {
761
1138
  const {
762
- node,
763
- nodeInfo,
1139
+ id,
1140
+ defaultProps,
1141
+ fiber,
764
1142
  state,
765
- error: nodeError,
766
- isPasskeySupported,
767
- hasPasskeyConfigured,
768
- startWithPassword,
769
- startWithPasskey,
770
- createPasskeyAndStart,
771
- stop
772
- } = useFiberNode({ network, walletId: props.walletId });
773
- const { payInvoice, isPaying, error: payError, paymentResult } = useFiberPayment(node);
774
- const [password, setPassword] = useState4("");
775
- const [invoiceInput, setInvoiceInput] = useState4("");
776
- const [createdInvoice, setCreatedInvoice] = useState4("");
777
- const [isCreatingInvoice, setIsCreatingInvoice] = useState4(false);
778
- const [invoiceError, setInvoiceError] = useState4(null);
779
- useEffect4(() => {
780
- if (nodeError) {
781
- onError?.({ scope: "node", message: nodeError });
782
- }
783
- }, [nodeError, onError]);
784
- useEffect4(() => {
785
- if (payError) {
786
- onError?.({ scope: "payment", message: payError });
787
- }
788
- }, [onError, payError]);
789
- useEffect4(() => {
790
- if (paymentResult) {
791
- onPaymentResult?.(paymentResult);
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
792
1168
  }
793
- }, [onPaymentResult, paymentResult]);
794
- const createInvoice = async () => {
795
- if (!node) {
796
- return;
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(() => {
3115
+ if (payError) {
3116
+ onError?.({ scope: "payment", message: payError });
3117
+ }
3118
+ }, [onError, payError]);
3119
+ useEffect6(() => {
3120
+ if (paymentResult) {
3121
+ onPaymentResult?.(paymentResult);
3122
+ }
3123
+ }, [onPaymentResult, paymentResult]);
3124
+ const createInvoice = async () => {
3125
+ if (!node) {
3126
+ return;
797
3127
  }
798
3128
  setIsCreatingInvoice(true);
799
3129
  setInvoiceError(null);
800
3130
  try {
801
3131
  const created = await node.newInvoice({
802
- amount: ONE_CKB_SHANNONS,
3132
+ amount: ONE_CKB_SHANNONS2,
803
3133
  currency: network === "mainnet" ? "Fibb" : "Fibt",
804
3134
  description: "FiberPay QuickCard invoice"
805
3135
  });
@@ -813,18 +3143,21 @@ function FiberPayQuickCard(props) {
813
3143
  setIsCreatingInvoice(false);
814
3144
  }
815
3145
  };
816
- return /* @__PURE__ */ jsxs2("div", { style: { ...cardStyle, ...props.style }, className: props.className, children: [
817
- /* @__PURE__ */ jsxs2("h3", { children: [
3146
+ return /* @__PURE__ */ jsxs6("div", { style: { ...cardStyle, ...props.style }, className: props.className, children: [
3147
+ /* @__PURE__ */ jsxs6("h3", { children: [
818
3148
  title,
819
3149
  " (",
820
3150
  network,
821
3151
  ")"
822
3152
  ] }),
823
- !nodeInfo ? /* @__PURE__ */ jsxs2(Fragment2, { children: [
824
- 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,
825
- /* @__PURE__ */ jsx2("label", { htmlFor: passwordInputId, children: "Password" }),
826
- /* @__PURE__ */ jsxs2("div", { style: rowStyle, children: [
827
- /* @__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(
828
3161
  "input",
829
3162
  {
830
3163
  id: passwordInputId,
@@ -836,31 +3169,31 @@ function FiberPayQuickCard(props) {
836
3169
  placeholder: "Password"
837
3170
  }
838
3171
  ),
839
- /* @__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" })
840
3173
  ] })
841
- ] }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
842
- /* @__PURE__ */ jsxs2("p", { children: [
843
- /* @__PURE__ */ jsx2("strong", { children: "State:" }),
3174
+ ] }) : /* @__PURE__ */ jsxs6(Fragment6, { children: [
3175
+ /* @__PURE__ */ jsxs6("p", { children: [
3176
+ /* @__PURE__ */ jsx8("strong", { children: "State:" }),
844
3177
  " ",
845
3178
  state
846
3179
  ] }),
847
- /* @__PURE__ */ jsxs2("p", { children: [
848
- /* @__PURE__ */ jsx2("strong", { children: "Pubkey:" }),
3180
+ /* @__PURE__ */ jsxs6("p", { children: [
3181
+ /* @__PURE__ */ jsx8("strong", { children: "Pubkey:" }),
849
3182
  " ",
850
3183
  nodeInfo.pubkey
851
3184
  ] }),
852
- /* @__PURE__ */ jsxs2("div", { style: rowWithMarginStyle, children: [
853
- /* @__PURE__ */ jsx2("button", { type: "button", onClick: () => void createInvoice(), disabled: isCreatingInvoice, children: isCreatingInvoice ? "Creating..." : "Create Invoice (1 CKB)" }),
854
- /* @__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" })
855
3188
  ] }),
856
- createdInvoice ? /* @__PURE__ */ jsxs2("p", { children: [
857
- /* @__PURE__ */ jsx2("strong", { children: "Created invoice:" }),
3189
+ createdInvoice ? /* @__PURE__ */ jsxs6("p", { children: [
3190
+ /* @__PURE__ */ jsx8("strong", { children: "Created invoice:" }),
858
3191
  " ",
859
3192
  createdInvoice
860
3193
  ] }) : null,
861
- /* @__PURE__ */ jsx2("label", { htmlFor: invoiceInputId, children: "Invoice" }),
862
- /* @__PURE__ */ jsxs2("div", { style: rowStyle, children: [
863
- /* @__PURE__ */ jsx2(
3194
+ /* @__PURE__ */ jsx8("label", { htmlFor: invoiceInputId, children: "Invoice" }),
3195
+ /* @__PURE__ */ jsxs6("div", { style: rowStyle, children: [
3196
+ /* @__PURE__ */ jsx8(
864
3197
  "input",
865
3198
  {
866
3199
  id: invoiceInputId,
@@ -870,26 +3203,26 @@ function FiberPayQuickCard(props) {
870
3203
  placeholder: "Paste invoice to pay"
871
3204
  }
872
3205
  ),
873
- /* @__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" })
874
3207
  ] }),
875
- paymentResult ? /* @__PURE__ */ jsxs2("p", { children: [
876
- /* @__PURE__ */ jsx2("strong", { children: "Payment:" }),
3208
+ paymentResult ? /* @__PURE__ */ jsxs6("p", { children: [
3209
+ /* @__PURE__ */ jsx8("strong", { children: "Payment:" }),
877
3210
  " ",
878
3211
  paymentResult.status
879
3212
  ] }) : null
880
3213
  ] }),
881
- nodeError ? /* @__PURE__ */ jsxs2("p", { style: { color: "#b91c1c" }, children: [
882
- /* @__PURE__ */ jsx2("strong", { children: "Node error:" }),
3214
+ nodeError ? /* @__PURE__ */ jsxs6("p", { style: { color: "#b91c1c" }, children: [
3215
+ /* @__PURE__ */ jsx8("strong", { children: "Node error:" }),
883
3216
  " ",
884
3217
  nodeError
885
3218
  ] }) : null,
886
- payError ? /* @__PURE__ */ jsxs2("p", { style: { color: "#b91c1c" }, children: [
887
- /* @__PURE__ */ jsx2("strong", { children: "Payment error:" }),
3219
+ payError ? /* @__PURE__ */ jsxs6("p", { style: { color: "#b91c1c" }, children: [
3220
+ /* @__PURE__ */ jsx8("strong", { children: "Payment error:" }),
888
3221
  " ",
889
3222
  payError
890
3223
  ] }) : null,
891
- invoiceError ? /* @__PURE__ */ jsxs2("p", { style: { color: "#b91c1c" }, children: [
892
- /* @__PURE__ */ jsx2("strong", { children: "Invoice error:" }),
3224
+ invoiceError ? /* @__PURE__ */ jsxs6("p", { style: { color: "#b91c1c" }, children: [
3225
+ /* @__PURE__ */ jsx8("strong", { children: "Invoice error:" }),
893
3226
  " ",
894
3227
  invoiceError
895
3228
  ] }) : null
@@ -904,12 +3237,12 @@ import {
904
3237
  scriptToAddress
905
3238
  } from "@fiber-pay/sdk/browser";
906
3239
  import {
907
- useCallback as useCallback4,
908
- useEffect as useEffect5,
909
- useRef as useRef4,
910
- useState as useState5
3240
+ useCallback as useCallback7,
3241
+ useEffect as useEffect7,
3242
+ useRef as useRef6,
3243
+ useState as useState7
911
3244
  } from "react";
912
- 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";
913
3246
  function truncateMiddle(str, left = 8, right = 8) {
914
3247
  if (str.length <= left + right + 3) return str;
915
3248
  return `${str.slice(0, left)}\u2026${str.slice(-right)}`;
@@ -947,7 +3280,7 @@ async function fetchNodeStats(node, network) {
947
3280
  externalFunding: false
948
3281
  };
949
3282
  }
950
- var styles2 = {
3283
+ var styles3 = {
951
3284
  root: {
952
3285
  fontFamily: "system-ui, -apple-system, sans-serif",
953
3286
  fontSize: "0.875rem",
@@ -1076,7 +3409,7 @@ var STATUS_COLORS = {
1076
3409
  error: { bg: "rgba(239,68,68,0.1)", fg: "#dc2626", dot: "#ef4444" }
1077
3410
  };
1078
3411
  function CopyIcon() {
1079
- return /* @__PURE__ */ jsxs3(
3412
+ return /* @__PURE__ */ jsxs7(
1080
3413
  "svg",
1081
3414
  {
1082
3415
  width: "12",
@@ -1089,26 +3422,26 @@ function CopyIcon() {
1089
3422
  strokeLinejoin: "round",
1090
3423
  "aria-hidden": "true",
1091
3424
  children: [
1092
- /* @__PURE__ */ jsx3("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
1093
- /* @__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" })
1094
3427
  ]
1095
3428
  }
1096
3429
  );
1097
3430
  }
1098
3431
  function InfoRow({ label, value, copyable }) {
1099
- return /* @__PURE__ */ jsxs3("div", { style: styles2.infoRow, children: [
1100
- /* @__PURE__ */ jsx3("span", { style: styles2.infoLabel, children: label }),
1101
- /* @__PURE__ */ jsxs3("div", { style: styles2.infoValueWrapper, children: [
1102
- /* @__PURE__ */ jsx3("span", { style: styles2.infoValue, children: truncateMiddle(value, 6, 6) }),
1103
- 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(
1104
3437
  "button",
1105
3438
  {
1106
3439
  type: "button",
1107
3440
  onClick: () => copyToClipboard(value),
1108
- style: styles2.copyButton,
3441
+ style: styles3.copyButton,
1109
3442
  title: "Copy to clipboard",
1110
3443
  "aria-label": `Copy ${label}`,
1111
- children: /* @__PURE__ */ jsx3(CopyIcon, {})
3444
+ children: /* @__PURE__ */ jsx9(CopyIcon, {})
1112
3445
  }
1113
3446
  )
1114
3447
  ] })
@@ -1124,12 +3457,12 @@ function NodeInfoPanel(props) {
1124
3457
  className,
1125
3458
  style
1126
3459
  } = props;
1127
- const [stats, setStats] = useState5(null);
1128
- const [statsError, setStatsError] = useState5(null);
1129
- const [statsLoading, setStatsLoading] = useState5(false);
1130
- const cancelledRef = useRef4(false);
1131
- const [QRComponent, setQRComponent] = useState5(null);
1132
- 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(() => {
1133
3466
  let cancelled = false;
1134
3467
  if (!showQrCode || renderQrCode) return;
1135
3468
  import("qrcode.react").then((mod) => {
@@ -1142,8 +3475,8 @@ function NodeInfoPanel(props) {
1142
3475
  cancelled = true;
1143
3476
  };
1144
3477
  }, [showQrCode, renderQrCode]);
1145
- const loadingRef = useRef4(false);
1146
- const loadStats = useCallback4(async () => {
3478
+ const loadingRef = useRef6(false);
3479
+ const loadStats = useCallback7(async () => {
1147
3480
  if (!node || node.state !== "running" || loadingRef.current) return;
1148
3481
  loadingRef.current = true;
1149
3482
  setStatsLoading(true);
@@ -1160,7 +3493,7 @@ function NodeInfoPanel(props) {
1160
3493
  if (!cancelledRef.current) setStatsLoading(false);
1161
3494
  }
1162
3495
  }, [node, network]);
1163
- useEffect5(() => {
3496
+ useEffect7(() => {
1164
3497
  cancelledRef.current = false;
1165
3498
  if (!node || node.state !== "running") {
1166
3499
  setStats(null);
@@ -1175,21 +3508,21 @@ function NodeInfoPanel(props) {
1175
3508
  };
1176
3509
  }, [node, node?.state, pollInterval, loadStats]);
1177
3510
  if (!node) {
1178
- 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" }) });
1179
3512
  }
1180
3513
  const nodeState = node.state;
1181
3514
  const statusColor = STATUS_COLORS[nodeState] ?? STATUS_COLORS.idle;
1182
- return /* @__PURE__ */ jsxs3("div", { className, style: { ...styles2.root, ...style }, "data-fpay-node-info": "", children: [
1183
- /* @__PURE__ */ jsxs3(
3515
+ return /* @__PURE__ */ jsxs7("div", { className, style: { ...styles3.root, ...style }, "data-fpay-node-info": "", children: [
3516
+ /* @__PURE__ */ jsxs7(
1184
3517
  "div",
1185
3518
  {
1186
3519
  style: {
1187
- ...styles2.statusBadge,
3520
+ ...styles3.statusBadge,
1188
3521
  backgroundColor: statusColor.bg,
1189
3522
  color: statusColor.fg
1190
3523
  },
1191
3524
  children: [
1192
- /* @__PURE__ */ jsx3(
3525
+ /* @__PURE__ */ jsx9(
1193
3526
  "span",
1194
3527
  {
1195
3528
  style: {
@@ -1205,8 +3538,8 @@ function NodeInfoPanel(props) {
1205
3538
  ]
1206
3539
  }
1207
3540
  ),
1208
- statsLoading && !stats && /* @__PURE__ */ jsxs3("div", { style: styles2.loading, children: [
1209
- /* @__PURE__ */ jsxs3(
3541
+ statsLoading && !stats && /* @__PURE__ */ jsxs7("div", { style: styles3.loading, children: [
3542
+ /* @__PURE__ */ jsxs7(
1210
3543
  "svg",
1211
3544
  {
1212
3545
  width: "12",
@@ -1220,8 +3553,8 @@ function NodeInfoPanel(props) {
1220
3553
  role: "img",
1221
3554
  "aria-label": "Loading",
1222
3555
  children: [
1223
- /* @__PURE__ */ jsx3("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }),
1224
- /* @__PURE__ */ jsx3(
3556
+ /* @__PURE__ */ jsx9("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" }),
3557
+ /* @__PURE__ */ jsx9(
1225
3558
  "animateTransform",
1226
3559
  {
1227
3560
  attributeName: "transform",
@@ -1237,22 +3570,22 @@ function NodeInfoPanel(props) {
1237
3570
  ),
1238
3571
  "Loading\u2026"
1239
3572
  ] }),
1240
- statsError && /* @__PURE__ */ jsx3("div", { style: styles2.errorBox, children: statsError }),
1241
- stats && /* @__PURE__ */ jsxs3(Fragment3, { children: [
1242
- /* @__PURE__ */ jsx3(InfoRow, { label: "Pubkey", value: stats.pubkey, copyable: true }),
1243
- 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,
1244
- /* @__PURE__ */ jsxs3("div", { style: styles2.statsGrid, children: [
1245
- /* @__PURE__ */ jsxs3("div", { style: styles2.statCard, children: [
1246
- /* @__PURE__ */ jsx3("div", { style: styles2.statLabel, children: "Peers" }),
1247
- /* @__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 })
1248
3581
  ] }),
1249
- /* @__PURE__ */ jsxs3("div", { style: styles2.statCard, children: [
1250
- /* @__PURE__ */ jsx3("div", { style: styles2.statLabel, children: "Channels" }),
1251
- /* @__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 })
1252
3585
  ] })
1253
3586
  ] }),
1254
- showQrCode && stats.ckbAddress && /* @__PURE__ */ jsxs3("div", { style: styles2.qrContainer, children: [
1255
- 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(
1256
3589
  QRComponent,
1257
3590
  {
1258
3591
  value: stats.ckbAddress,
@@ -1260,11 +3593,11 @@ function NodeInfoPanel(props) {
1260
3593
  bgColor: "transparent",
1261
3594
  fgColor: "currentColor"
1262
3595
  }
1263
- ) : /* @__PURE__ */ jsx3("div", { style: { fontSize: "0.625rem", color: "#9ca3af" }, children: "Install qrcode.react for QR code" }),
1264
- /* @__PURE__ */ jsx3("span", { style: styles2.qrCaption, children: "Scan to deposit CKB" }),
1265
- /* @__PURE__ */ jsxs3("div", { style: styles2.balanceRow, children: [
1266
- /* @__PURE__ */ jsx3("span", { style: { fontSize: "0.75rem", color: "#6b7280" }, children: "Balance" }),
1267
- /* @__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(
1268
3601
  "span",
1269
3602
  {
1270
3603
  style: {
@@ -1284,10 +3617,11 @@ function NodeInfoPanel(props) {
1284
3617
  ] });
1285
3618
  }
1286
3619
  export {
1287
- ChannelState,
3620
+ ChannelState3 as ChannelState,
1288
3621
  ConfigBuilder2 as ConfigBuilder,
1289
3622
  ConnectButton,
1290
3623
  FiberBrowserNode2 as FiberBrowserNode,
3624
+ FiberNodeButton,
1291
3625
  FiberPayQuickCard,
1292
3626
  FiberRpcError,
1293
3627
  NodeInfoPanel,
@@ -1295,14 +3629,15 @@ export {
1295
3629
  PasswordCredentialProvider2 as PasswordCredentialProvider,
1296
3630
  RawKeyCredentialProvider2 as RawKeyCredentialProvider,
1297
3631
  ckbHash,
1298
- ckbToShannons,
3632
+ ckbToShannons2 as ckbToShannons,
1299
3633
  derivePublicKey,
1300
3634
  formatShannonsAsCkb2 as formatShannonsAsCkb,
1301
3635
  fromHex,
1302
3636
  getLockBalanceShannons2 as getLockBalanceShannons,
1303
3637
  scriptToAddress2 as scriptToAddress,
1304
- shannonsToCkb,
3638
+ shannonsToCkb2 as shannonsToCkb,
1305
3639
  toHex,
3640
+ useChannelOpenFlow,
1306
3641
  useFiberNode,
1307
3642
  useFiberPayment
1308
3643
  };