@openreceive/react 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -56,7 +56,6 @@ import {
56
56
  OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES,
57
57
  copyInvoice as copyInvoiceHelper,
58
58
  createCheckoutStatusModel,
59
- createPaymentDataEntries,
60
59
  createQrSvg,
61
60
  formatAmountCaption,
62
61
  checkoutLabels,
@@ -320,31 +319,6 @@ function SettledCheckIcon() {
320
319
  })
321
320
  );
322
321
  }
323
- function PaymentData(props) {
324
- const entries = createPaymentDataEntries(props.source);
325
- if (entries.length === 0) return null;
326
- return React3.createElement(
327
- "details",
328
- { className: joinClassNames(orClasses.paymentData, props.className) },
329
- React3.createElement(
330
- "summary",
331
- { className: orClasses.paymentDataTitle },
332
- checkoutLabels.viewPaymentData
333
- ),
334
- React3.createElement(
335
- "div",
336
- { className: orClasses.paymentDataBody },
337
- entries.map(
338
- (entry) => React3.createElement(
339
- "div",
340
- { key: entry.label, className: orClasses.paymentDataRow },
341
- React3.createElement("span", { className: orClasses.paymentDataKey }, entry.label),
342
- React3.createElement("p", { className: orClasses.paymentDataValue }, entry.value)
343
- )
344
- )
345
- )
346
- );
347
- }
348
322
  function WaitingState(props) {
349
323
  const status = props.status ?? createCheckoutStatusModel({
350
324
  phase: props.phase,
@@ -447,17 +421,106 @@ function SatsDetail(props) {
447
421
  );
448
422
  }
449
423
 
450
- // src/theme.ts
424
+ // src/transaction-details.ts
425
+ import {
426
+ checkoutLabels as checkoutLabels2,
427
+ orClasses as orClasses2,
428
+ resolveTransactionDetailRows
429
+ } from "@openreceive/browser/headless";
451
430
  import * as React4 from "react";
431
+ function TransactionDetails(props) {
432
+ const rows = resolveTransactionDetailRows(props.state);
433
+ if (rows.length === 0) return null;
434
+ return React4.createElement(
435
+ "details",
436
+ {
437
+ className: joinClassNames(orClasses2.transactionDetails, props.className),
438
+ open: props.open === true ? true : void 0
439
+ },
440
+ React4.createElement(
441
+ "summary",
442
+ { className: orClasses2.transactionDetailsTitle },
443
+ checkoutLabels2.transactionDetails
444
+ ),
445
+ React4.createElement(
446
+ "div",
447
+ { className: orClasses2.transactionDetailsContent },
448
+ React4.createElement(
449
+ "dl",
450
+ { className: orClasses2.swapDetails },
451
+ rows.flatMap(
452
+ (row) => renderTransactionDetailRow(row, {
453
+ clipboard: props.clipboard,
454
+ onError: props.onError
455
+ })
456
+ )
457
+ )
458
+ )
459
+ );
460
+ }
461
+ function renderTransactionDetailRow(row, options) {
462
+ const copyValue = row.copyValue ?? row.value;
463
+ return [
464
+ React4.createElement(
465
+ "dt",
466
+ { key: `${row.label}-label`, className: orClasses2.swapDetailsDt },
467
+ row.label
468
+ ),
469
+ React4.createElement(
470
+ "dd",
471
+ { key: `${row.label}-value`, className: orClasses2.swapDetailsDd },
472
+ React4.createElement("code", { className: orClasses2.swapDetailsCode }, row.value),
473
+ React4.createElement(
474
+ "div",
475
+ { className: orClasses2.swapDetailsActions },
476
+ React4.createElement(TransactionDetailCopyButton, {
477
+ value: copyValue,
478
+ clipboard: options.clipboard,
479
+ onError: options.onError
480
+ }),
481
+ row.href === void 0 ? null : React4.createElement(
482
+ "a",
483
+ {
484
+ className: orClasses2.swapDetailsLink,
485
+ href: row.href,
486
+ rel: "noreferrer",
487
+ target: "_blank"
488
+ },
489
+ row.hrefLabel ?? checkoutLabels2.viewOnExplorer
490
+ )
491
+ )
492
+ )
493
+ ];
494
+ }
495
+ function TransactionDetailCopyButton(props) {
496
+ const [copied, setCopied] = useTransientValue(false);
497
+ const label = copied ? checkoutLabels2.copied : "Copy";
498
+ return React4.createElement(
499
+ "button",
500
+ {
501
+ className: orClasses2.detailCopy,
502
+ type: "button",
503
+ "aria-label": label,
504
+ title: label,
505
+ onClick: () => {
506
+ void copyText(props.value, props.clipboard).then(() => setCopied(true)).catch((error) => props.onError?.(error));
507
+ }
508
+ },
509
+ React4.createElement(ClipboardIcon)
510
+ );
511
+ }
512
+
513
+ // src/theme.ts
514
+ import * as React5 from "react";
452
515
  import {
453
516
  OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES as OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES2,
454
517
  OPENRECEIVE_THEME_STORAGE_KEY,
455
518
  createThemeModel,
456
- orClasses as orClasses2,
519
+ orClasses as orClasses3,
457
520
  readThemePreference,
458
521
  writeThemePreference
459
522
  } from "@openreceive/browser/headless";
460
- var ThemeContext = React4.createContext(null);
523
+ var ThemeContext = React5.createContext(null);
461
524
  var storedThemeListeners = /* @__PURE__ */ new Set();
462
525
  var lastSetTheme = /* @__PURE__ */ new Map();
463
526
  function notifyStoredThemeChange() {
@@ -486,9 +549,9 @@ function useLocalTheme(options = {}) {
486
549
  const storageKey = options.storageKey ?? OPENRECEIVE_THEME_STORAGE_KEY;
487
550
  const storage = options.storage;
488
551
  const defaultTheme = options.defaultTheme;
489
- const theme = React4.useSyncExternalStore(
552
+ const theme = React5.useSyncExternalStore(
490
553
  subscribeStoredTheme,
491
- React4.useCallback(
554
+ React5.useCallback(
492
555
  () => readThemePreference({
493
556
  storage,
494
557
  storageKey,
@@ -496,7 +559,7 @@ function useLocalTheme(options = {}) {
496
559
  }),
497
560
  [storage, storageKey, defaultTheme]
498
561
  ),
499
- React4.useCallback(
562
+ React5.useCallback(
500
563
  () => (
501
564
  // A host-supplied storage is readable on the server (that is how SSR theming is
502
565
  // wired); the implicit browser localStorage is not, so fall back to the default.
@@ -505,13 +568,13 @@ function useLocalTheme(options = {}) {
505
568
  [storage, storageKey, defaultTheme]
506
569
  )
507
570
  );
508
- const systemDark = React4.useSyncExternalStore(
571
+ const systemDark = React5.useSyncExternalStore(
509
572
  subscribeSystemDark,
510
573
  getSystemDark,
511
574
  getServerSystemDark
512
575
  );
513
576
  const themeModel = createThemeModel(theme, { systemDark });
514
- const setTheme = React4.useCallback(
577
+ const setTheme = React5.useCallback(
515
578
  (nextTheme) => {
516
579
  lastSetTheme.set(storageKey, nextTheme);
517
580
  writeThemePreference(nextTheme, {
@@ -522,7 +585,7 @@ function useLocalTheme(options = {}) {
522
585
  },
523
586
  [storage, storageKey]
524
587
  );
525
- const toggleTheme = React4.useCallback(() => {
588
+ const toggleTheme = React5.useCallback(() => {
526
589
  setTheme(themeModel.nextTheme);
527
590
  }, [setTheme, themeModel.nextTheme]);
528
591
  return {
@@ -539,7 +602,7 @@ function useLocalTheme(options = {}) {
539
602
  };
540
603
  }
541
604
  function useTheme(options = {}) {
542
- const scoped = React4.useContext(ThemeContext);
605
+ const scoped = React5.useContext(ThemeContext);
543
606
  const local = useLocalTheme(options);
544
607
  const storageKey = options.storageKey ?? OPENRECEIVE_THEME_STORAGE_KEY;
545
608
  if (scoped !== null && options.storage === void 0 && (options.storageKey === void 0 || options.storageKey === storageKey) && options.defaultTheme === void 0) {
@@ -566,7 +629,7 @@ function ThemeToggle(props) {
566
629
  const componentProps = {
567
630
  ...buttonProps,
568
631
  "aria-label": themeModel.toggleLabel,
569
- className: joinClassNames(orClasses2.themeToggle, buttonProps.className),
632
+ className: joinClassNames(orClasses3.themeToggle, buttonProps.className),
570
633
  title: themeModel.toggleLabel,
571
634
  type,
572
635
  [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES2.themeToggle]: "",
@@ -577,7 +640,7 @@ function ThemeToggle(props) {
577
640
  if (onThemeChange === void 0) fallback.setTheme(themeModel.nextTheme);
578
641
  }
579
642
  };
580
- return React4.createElement(ButtonComponent, componentProps, children ?? themeModel.toggleLabel);
643
+ return React5.createElement(ButtonComponent, componentProps, children ?? themeModel.toggleLabel);
581
644
  }
582
645
  function ThemeScope(props) {
583
646
  const {
@@ -598,22 +661,22 @@ function ThemeScope(props) {
598
661
  storage
599
662
  });
600
663
  const scopedChildren = typeof children === "function" ? children(theme) : children;
601
- return React4.createElement(
664
+ return React5.createElement(
602
665
  ThemeContext.Provider,
603
666
  { value: theme },
604
667
  // Variadic children, not an array: an array child needs a key on every entry.
605
- React4.createElement(
668
+ React5.createElement(
606
669
  Element,
607
670
  {
608
671
  ...elementProps,
609
672
  ...theme.attributes
610
673
  },
611
- themeToggle ? React4.createElement(
674
+ themeToggle ? React5.createElement(
612
675
  "div",
613
676
  {
614
677
  className: topbarClassName
615
678
  },
616
- React4.createElement(ThemeToggle, {
679
+ React5.createElement(ThemeToggle, {
617
680
  className: themeToggleClassName,
618
681
  theme: theme.theme,
619
682
  resolvedTheme: theme.resolvedTheme,
@@ -627,7 +690,7 @@ function ThemeScope(props) {
627
690
  }
628
691
 
629
692
  // src/use-checkout.ts
630
- import * as React5 from "react";
693
+ import * as React6 from "react";
631
694
  import {
632
695
  copyInvoice as copyInvoiceHelper2,
633
696
  createCheckoutController,
@@ -639,46 +702,46 @@ import {
639
702
  function useCheckout(options) {
640
703
  const checkout = options.checkout;
641
704
  const [copied, showCopied] = useTransientValue(false);
642
- const [latestSnapshot, setLatestSnapshot] = React5.useState(checkout);
705
+ const [latestSnapshot, setLatestSnapshot] = React6.useState(checkout);
643
706
  const incomingIdentity = `${checkout.checkout_id} ${checkout.reference}`;
644
- const checkoutRef = React5.useRef(checkout);
707
+ const checkoutRef = React6.useRef(checkout);
645
708
  checkoutRef.current = checkout;
646
- React5.useEffect(() => {
709
+ React6.useEffect(() => {
647
710
  setLatestSnapshot(checkoutRef.current);
648
711
  }, [incomingIdentity]);
649
712
  const snapshot = latestSnapshot;
650
- const [state, setState] = React5.useState(
713
+ const [state, setState] = React6.useState(
651
714
  () => createCheckoutState(snapshot, {
652
715
  logger: options.logger
653
716
  })
654
717
  );
655
- const controllerRef = React5.useRef(null);
656
- const onStateRef = React5.useRef(options.onState);
718
+ const controllerRef = React6.useRef(null);
719
+ const onStateRef = React6.useRef(options.onState);
657
720
  onStateRef.current = options.onState;
658
- const onSettledRef = React5.useRef(options.onSettled);
721
+ const onSettledRef = React6.useRef(options.onSettled);
659
722
  onSettledRef.current = options.onSettled;
660
- const loggerRef = React5.useRef(options.logger);
723
+ const loggerRef = React6.useRef(options.logger);
661
724
  loggerRef.current = options.logger;
662
- const onErrorRef = React5.useRef(options.onError);
725
+ const onErrorRef = React6.useRef(options.onError);
663
726
  onErrorRef.current = options.onError;
664
- const onCopyRef = React5.useRef(options.onCopy);
727
+ const onCopyRef = React6.useRef(options.onCopy);
665
728
  onCopyRef.current = options.onCopy;
666
- const onOpenWalletRef = React5.useRef(options.onOpenWallet);
729
+ const onOpenWalletRef = React6.useRef(options.onOpenWallet);
667
730
  onOpenWalletRef.current = options.onOpenWallet;
668
- const settledAnnouncementRef = React5.useRef({
731
+ const settledAnnouncementRef = React6.useRef({
669
732
  reference: snapshot.reference,
670
733
  fired: false
671
734
  });
672
- const logContext = React5.useMemo(() => getCheckoutLogContext(state), [state]);
735
+ const logContext = React6.useMemo(() => getCheckoutLogContext(state), [state]);
673
736
  const refreshStatus = options.polling === false ? void 0 : options.refreshStatus;
674
- const refreshStatusRef = React5.useRef(refreshStatus);
737
+ const refreshStatusRef = React6.useRef(refreshStatus);
675
738
  refreshStatusRef.current = refreshStatus;
676
739
  const polls = refreshStatus !== void 0;
677
740
  const prefix = options.polling === false ? void 0 : options.prefix;
678
- const snapshotRef = React5.useRef(snapshot);
741
+ const snapshotRef = React6.useRef(snapshot);
679
742
  snapshotRef.current = snapshot;
680
743
  const checkoutIdentity = `${snapshot.checkout_id} ${snapshot.reference}`;
681
- React5.useEffect(() => {
744
+ React6.useEffect(() => {
682
745
  const controller = createCheckoutController({
683
746
  snapshot: snapshotRef.current,
684
747
  ...polls ? {
@@ -715,7 +778,7 @@ function useCheckout(options) {
715
778
  }, [checkoutIdentity, polls, prefix, options.pollIntervalMs]);
716
779
  const publicStatus = deriveStatus(state);
717
780
  const richStatus = createCheckoutStatusModel2(state);
718
- React5.useEffect(() => {
781
+ React6.useEffect(() => {
719
782
  const announced = settledAnnouncementRef.current;
720
783
  if (announced.reference !== snapshot.reference) {
721
784
  settledAnnouncementRef.current = {
@@ -724,7 +787,7 @@ function useCheckout(options) {
724
787
  };
725
788
  }
726
789
  }, [snapshot.reference]);
727
- React5.useEffect(() => {
790
+ React6.useEffect(() => {
728
791
  const announced = settledAnnouncementRef.current;
729
792
  if (publicStatus !== "settled" || announced.fired) return;
730
793
  settledAnnouncementRef.current = {
@@ -733,7 +796,7 @@ function useCheckout(options) {
733
796
  };
734
797
  onSettledRef.current?.();
735
798
  }, [publicStatus, snapshot.reference]);
736
- const copyInvoice = React5.useCallback(async () => {
799
+ const copyInvoice = React6.useCallback(async () => {
737
800
  try {
738
801
  const controller = controllerRef.current;
739
802
  if (controller === null) {
@@ -753,7 +816,7 @@ function useCheckout(options) {
753
816
  throw error;
754
817
  }
755
818
  }, [logContext, state.invoice, options.clipboard, options.logger, showCopied]);
756
- const openWallet = React5.useCallback(() => {
819
+ const openWallet = React6.useCallback(() => {
757
820
  try {
758
821
  const controller = controllerRef.current;
759
822
  const uri = controller === null ? openWalletHelper2({
@@ -769,7 +832,7 @@ function useCheckout(options) {
769
832
  throw error;
770
833
  }
771
834
  }, [logContext, state.invoice, options.open, options.logger]);
772
- const reloadState = React5.useCallback(async () => {
835
+ const reloadState = React6.useCallback(async () => {
773
836
  try {
774
837
  const next = await controllerRef.current?.reloadState();
775
838
  if (next !== void 0) setState(next);
@@ -779,10 +842,42 @@ function useCheckout(options) {
779
842
  }
780
843
  }, []);
781
844
  const retry = reloadState;
782
- const cancel = React5.useCallback(() => {
845
+ const cancel = React6.useCallback(() => {
783
846
  const next = controllerRef.current?.cancel();
784
847
  if (next !== void 0) setState(next);
785
848
  }, []);
849
+ const swapRefundController = React6.useCallback(() => {
850
+ const controller = controllerRef.current;
851
+ if (controller === null) {
852
+ throw new Error("Swap refunds need a started checkout controller.");
853
+ }
854
+ return controller;
855
+ }, []);
856
+ const stageSwapRefund = React6.useCallback(
857
+ async (refund) => {
858
+ try {
859
+ return await swapRefundController().stageSwapRefund(refund);
860
+ } catch (error) {
861
+ onErrorRef.current?.(error);
862
+ throw error;
863
+ }
864
+ },
865
+ [swapRefundController]
866
+ );
867
+ const confirmSwapRefund = React6.useCallback(
868
+ async (refund) => {
869
+ try {
870
+ return await swapRefundController().confirmSwapRefund(refund);
871
+ } catch (error) {
872
+ onErrorRef.current?.(error);
873
+ throw error;
874
+ }
875
+ },
876
+ [swapRefundController]
877
+ );
878
+ const clearSwapRefundStaging = React6.useCallback(() => {
879
+ controllerRef.current?.clearSwapRefundStaging();
880
+ }, []);
786
881
  return {
787
882
  // The state IS the view model now: one derivation, one set of display
788
883
  // fields. `status` stays on top of it because the component's terminal
@@ -801,12 +896,15 @@ function useCheckout(options) {
801
896
  retry,
802
897
  cancel,
803
898
  copyInvoice,
804
- openWallet
899
+ openWallet,
900
+ stageSwapRefund,
901
+ confirmSwapRefund,
902
+ clearSwapRefundStaging
805
903
  };
806
904
  }
807
- var CheckoutContext = React5.createContext(null);
905
+ var CheckoutContext = React6.createContext(null);
808
906
  function useCheckoutContext() {
809
- const checkout = React5.useContext(CheckoutContext);
907
+ const checkout = React6.useContext(CheckoutContext);
810
908
  if (checkout === null) {
811
909
  throw new Error("useCheckoutContext must be used within CheckoutProvider.");
812
910
  }
@@ -816,7 +914,7 @@ function CheckoutProvider(props) {
816
914
  const { children, ...options } = props;
817
915
  const checkout = useCheckout(options);
818
916
  const content = typeof children === "function" ? children(checkout) : children;
819
- return React5.createElement(CheckoutContext.Provider, { value: checkout }, content);
917
+ return React6.createElement(CheckoutContext.Provider, { value: checkout }, content);
820
918
  }
821
919
 
822
920
  // src/wizard.ts
@@ -825,12 +923,12 @@ import {
825
923
  buildMethodGridEntries,
826
924
  checkoutLabels as checkoutLabels5,
827
925
  copyInvoice as copyInvoiceHelper3,
926
+ createAssetBaseUrlResolver,
927
+ createMethodGridDisplay,
828
928
  createPaymentWizardController,
829
929
  createPaymentWizardModel,
830
930
  createWizardRouteAssetDisplays,
831
931
  createWizardRouteDisplays,
832
- findSwapGridGroup,
833
- formatChooseNetworkHeading,
834
932
  formatNetworkSummary,
835
933
  getNetworkIcon,
836
934
  getPaymentMethodIcon,
@@ -841,32 +939,28 @@ import {
841
939
  networkMobileRevealClasses,
842
940
  networkSummaryIconClasses,
843
941
  orClasses as orClasses6,
844
- overlaySwapRefundStaging,
845
- paymentAccentId,
846
942
  paymentMethods,
943
+ resolveWizardSelection,
847
944
  requestSwapRefund,
945
+ selectCurrentSwapInvoice,
848
946
  swapAssetMatchesRoute,
849
- swapGroupLimitOption,
850
- swapOptionLimitMessage as swapOptionLimitMessage2,
851
- swapPickerKey,
852
- updateSelectedSwapNetworks,
853
- wizardNetworkGroupIds
947
+ swapOptionLimitMessage as swapOptionLimitMessage2
854
948
  } from "@openreceive/browser/headless";
855
949
  import * as React9 from "react";
856
950
 
857
951
  // src/provider-tutorial.ts
858
952
  import {
859
953
  createLightningInvoiceDecodeUrl,
860
- checkoutLabels as checkoutLabels2,
861
- orClasses as orClasses3
954
+ checkoutLabels as checkoutLabels3,
955
+ orClasses as orClasses4
862
956
  } from "@openreceive/browser/headless";
863
- import * as React6 from "react";
957
+ import * as React7 from "react";
864
958
  function renderProviderOpenAction(provider, onOpenTutorial) {
865
959
  if (provider.tutorials.length === 0) {
866
- return React6.createElement(
960
+ return React7.createElement(
867
961
  "a",
868
962
  {
869
- className: orClasses3.providerOpen,
963
+ className: orClasses4.providerOpen,
870
964
  href: provider.url,
871
965
  rel: "noreferrer",
872
966
  target: "_blank"
@@ -874,10 +968,10 @@ function renderProviderOpenAction(provider, onOpenTutorial) {
874
968
  provider.openLabel
875
969
  );
876
970
  }
877
- return React6.createElement(
971
+ return React7.createElement(
878
972
  "button",
879
973
  {
880
- className: orClasses3.providerOpen,
974
+ className: orClasses4.providerOpen,
881
975
  onClick: onOpenTutorial,
882
976
  type: "button"
883
977
  },
@@ -886,8 +980,8 @@ function renderProviderOpenAction(provider, onOpenTutorial) {
886
980
  }
887
981
  function ProviderTutorialModal(options) {
888
982
  const { provider } = options;
889
- const dialogRef = React6.useRef(null);
890
- React6.useEffect(() => {
983
+ const dialogRef = React7.useRef(null);
984
+ React7.useEffect(() => {
891
985
  const dialog = dialogRef.current;
892
986
  if (dialog === null) return;
893
987
  const opener = dialog.ownerDocument.activeElement instanceof HTMLElement ? dialog.ownerDocument.activeElement : null;
@@ -925,13 +1019,13 @@ function ProviderTutorialModal(options) {
925
1019
  const nextIndex = Math.min(provider.tutorials.length, stepIndex + 1);
926
1020
  const isFinalStep = stepIndex === provider.tutorials.length;
927
1021
  const decodeHref = createLightningInvoiceDecodeUrl(options.invoice, options.decodeLinkUrl);
928
- return React6.createElement(
1022
+ return React7.createElement(
929
1023
  "div",
930
1024
  {
931
1025
  ref: dialogRef,
932
- "aria-label": `${checkoutLabels2.tutorialTitlePrefix} ${provider.name}`,
1026
+ "aria-label": `${checkoutLabels3.tutorialTitlePrefix} ${provider.name}`,
933
1027
  "aria-modal": true,
934
- className: orClasses3.tutorialModal,
1028
+ className: orClasses4.tutorialModal,
935
1029
  onClick: (event) => {
936
1030
  if (event.target === event.currentTarget) options.onClose();
937
1031
  },
@@ -942,143 +1036,143 @@ function ProviderTutorialModal(options) {
942
1036
  role: "dialog",
943
1037
  tabIndex: -1
944
1038
  },
945
- React6.createElement(
1039
+ React7.createElement(
946
1040
  "div",
947
1041
  {
948
- className: orClasses3.tutorialBox
1042
+ className: orClasses4.tutorialBox
949
1043
  },
950
- React6.createElement(
1044
+ React7.createElement(
951
1045
  "div",
952
1046
  {
953
- className: orClasses3.tutorialHeader
1047
+ className: orClasses4.tutorialHeader
954
1048
  },
955
- React6.createElement(
1049
+ React7.createElement(
956
1050
  "div",
957
1051
  {
958
- className: orClasses3.tutorialTitle
1052
+ className: orClasses4.tutorialTitle
959
1053
  },
960
- React6.createElement("img", {
1054
+ React7.createElement("img", {
961
1055
  alt: "",
962
- className: orClasses3.tutorialHeaderLogo,
1056
+ className: orClasses4.tutorialHeaderLogo,
963
1057
  src: provider.icon
964
1058
  }),
965
- React6.createElement("h3", null, `${checkoutLabels2.tutorialTitlePrefix} ${provider.name}`)
1059
+ React7.createElement("h3", null, `${checkoutLabels3.tutorialTitlePrefix} ${provider.name}`)
966
1060
  ),
967
- React6.createElement(
1061
+ React7.createElement(
968
1062
  "button",
969
1063
  {
970
- "aria-label": checkoutLabels2.tutorialClose,
971
- className: orClasses3.tutorialClose,
1064
+ "aria-label": checkoutLabels3.tutorialClose,
1065
+ className: orClasses4.tutorialClose,
972
1066
  onClick: options.onClose,
973
1067
  type: "button"
974
1068
  },
975
1069
  "X"
976
1070
  )
977
1071
  ),
978
- stepIndex === 0 ? React6.createElement(
1072
+ stepIndex === 0 ? React7.createElement(
979
1073
  "div",
980
1074
  {
981
- className: orClasses3.tutorialIntro
1075
+ className: orClasses4.tutorialIntro
982
1076
  },
983
- React6.createElement("img", {
1077
+ React7.createElement("img", {
984
1078
  alt: "",
985
- className: orClasses3.tutorialProviderLogo,
1079
+ className: orClasses4.tutorialProviderLogo,
986
1080
  src: provider.icon
987
1081
  }),
988
- React6.createElement(
1082
+ React7.createElement(
989
1083
  "p",
990
1084
  null,
991
- `${checkoutLabels2.tutorialIntroPrefix} ${provider.name}.`
1085
+ `${checkoutLabels3.tutorialIntroPrefix} ${provider.name}.`
992
1086
  ),
993
- React6.createElement("p", null, checkoutLabels2.tutorialIntroCopy),
994
- React6.createElement(
1087
+ React7.createElement("p", null, checkoutLabels3.tutorialIntroCopy),
1088
+ React7.createElement(
995
1089
  "button",
996
1090
  {
997
- className: orClasses3.tutorialCopy,
1091
+ className: orClasses4.tutorialCopy,
998
1092
  onClick: () => void options.onCopy(),
999
1093
  type: "button"
1000
1094
  },
1001
- checkoutLabels2.copyInvoice
1095
+ checkoutLabels3.copyInvoice
1002
1096
  ),
1003
- decodeHref === void 0 ? null : React6.createElement(
1097
+ decodeHref === void 0 ? null : React7.createElement(
1004
1098
  "a",
1005
1099
  {
1006
- className: orClasses3.tutorialCopy,
1100
+ className: orClasses4.tutorialCopy,
1007
1101
  href: decodeHref,
1008
1102
  rel: "noreferrer",
1009
1103
  target: "_blank"
1010
1104
  },
1011
- checkoutLabels2.decodeInvoice
1105
+ checkoutLabels3.decodeInvoice
1012
1106
  ),
1013
- options.copied ? React6.createElement(
1107
+ options.copied ? React7.createElement(
1014
1108
  "p",
1015
1109
  {
1016
- className: orClasses3.tutorialCopyMessage
1110
+ className: orClasses4.tutorialCopyMessage
1017
1111
  },
1018
- checkoutLabels2.tutorialCopiedContinue
1112
+ checkoutLabels3.tutorialCopiedContinue
1019
1113
  ) : null
1020
- ) : React6.createElement(
1021
- React6.Fragment,
1114
+ ) : React7.createElement(
1115
+ React7.Fragment,
1022
1116
  null,
1023
- React6.createElement(
1117
+ React7.createElement(
1024
1118
  "div",
1025
1119
  {
1026
- className: orClasses3.tutorialFrame
1120
+ className: orClasses4.tutorialFrame
1027
1121
  },
1028
- React6.createElement("img", {
1122
+ React7.createElement("img", {
1029
1123
  alt: tutorial?.caption ?? "",
1030
- className: orClasses3.tutorialImage,
1124
+ className: orClasses4.tutorialImage,
1031
1125
  src: tutorial?.image ?? ""
1032
1126
  })
1033
1127
  ),
1034
- React6.createElement(
1128
+ React7.createElement(
1035
1129
  "p",
1036
1130
  {
1037
- className: orClasses3.tutorialCaption
1131
+ className: orClasses4.tutorialCaption
1038
1132
  },
1039
1133
  tutorial?.caption ?? ""
1040
1134
  )
1041
1135
  ),
1042
- React6.createElement(
1136
+ React7.createElement(
1043
1137
  "div",
1044
1138
  {
1045
1139
  "aria-hidden": "true",
1046
- className: orClasses3.tutorialSteps
1140
+ className: orClasses4.tutorialSteps
1047
1141
  },
1048
1142
  Array.from(
1049
1143
  { length: totalSteps },
1050
- (_, index) => React6.createElement("span", {
1051
- className: index === stepIndex ? orClasses3.tutorialStepActive : orClasses3.tutorialStep,
1144
+ (_, index) => React7.createElement("span", {
1145
+ className: index === stepIndex ? orClasses4.tutorialStepActive : orClasses4.tutorialStep,
1052
1146
  key: index
1053
1147
  })
1054
1148
  )
1055
1149
  ),
1056
- React6.createElement(
1150
+ React7.createElement(
1057
1151
  "p",
1058
1152
  {
1059
- className: orClasses3.tutorialProgress
1153
+ className: orClasses4.tutorialProgress
1060
1154
  },
1061
1155
  `Step ${stepIndex + 1} of ${totalSteps}`
1062
1156
  ),
1063
- React6.createElement(
1157
+ React7.createElement(
1064
1158
  "div",
1065
1159
  {
1066
- className: orClasses3.tutorialControls
1160
+ className: orClasses4.tutorialControls
1067
1161
  },
1068
- React6.createElement(
1162
+ React7.createElement(
1069
1163
  "button",
1070
1164
  {
1071
- className: orClasses3.btn,
1165
+ className: orClasses4.btn,
1072
1166
  disabled: stepIndex === 0,
1073
1167
  onClick: () => options.onStep(previousIndex),
1074
1168
  type: "button"
1075
1169
  },
1076
- checkoutLabels2.tutorialBack
1170
+ checkoutLabels3.tutorialBack
1077
1171
  ),
1078
- React6.createElement(
1172
+ React7.createElement(
1079
1173
  "button",
1080
1174
  {
1081
- className: orClasses3.btn,
1175
+ className: orClasses4.btn,
1082
1176
  onClick: () => {
1083
1177
  if (isFinalStep) {
1084
1178
  options.onClose();
@@ -1088,7 +1182,7 @@ function ProviderTutorialModal(options) {
1088
1182
  },
1089
1183
  type: "button"
1090
1184
  },
1091
- isFinalStep ? checkoutLabels2.tutorialExit : checkoutLabels2.tutorialNext
1185
+ isFinalStep ? checkoutLabels3.tutorialExit : checkoutLabels3.tutorialNext
1092
1186
  )
1093
1187
  )
1094
1188
  )
@@ -1109,97 +1203,6 @@ import {
1109
1203
  selectCheckoutDisplayInvoice
1110
1204
  } from "@openreceive/browser/headless";
1111
1205
  import * as React8 from "react";
1112
-
1113
- // src/transaction-details.ts
1114
- import {
1115
- checkoutLabels as checkoutLabels3,
1116
- orClasses as orClasses4,
1117
- resolveTransactionDetailRows
1118
- } from "@openreceive/browser/headless";
1119
- import * as React7 from "react";
1120
- function TransactionDetails(props) {
1121
- const rows = resolveTransactionDetailRows(props.state);
1122
- if (rows.length === 0) return null;
1123
- return React7.createElement(
1124
- "details",
1125
- {
1126
- className: joinClassNames(orClasses4.transactionDetails, props.className),
1127
- open: props.open === true ? true : void 0
1128
- },
1129
- React7.createElement(
1130
- "summary",
1131
- { className: orClasses4.transactionDetailsTitle },
1132
- checkoutLabels3.transactionDetails
1133
- ),
1134
- React7.createElement(
1135
- "div",
1136
- { className: orClasses4.transactionDetailsContent },
1137
- React7.createElement(
1138
- "dl",
1139
- { className: orClasses4.swapDetails },
1140
- rows.flatMap(
1141
- (row) => renderTransactionDetailRow(row, {
1142
- clipboard: props.clipboard,
1143
- onError: props.onError
1144
- })
1145
- )
1146
- )
1147
- )
1148
- );
1149
- }
1150
- function renderTransactionDetailRow(row, options) {
1151
- const copyValue = row.copyValue ?? row.value;
1152
- return [
1153
- React7.createElement(
1154
- "dt",
1155
- { key: `${row.label}-label`, className: orClasses4.swapDetailsDt },
1156
- row.label
1157
- ),
1158
- React7.createElement(
1159
- "dd",
1160
- { key: `${row.label}-value`, className: orClasses4.swapDetailsDd },
1161
- React7.createElement("code", { className: orClasses4.swapDetailsCode }, row.value),
1162
- React7.createElement(
1163
- "div",
1164
- { className: orClasses4.swapDetailsActions },
1165
- React7.createElement(TransactionDetailCopyButton, {
1166
- value: copyValue,
1167
- clipboard: options.clipboard,
1168
- onError: options.onError
1169
- }),
1170
- row.href === void 0 ? null : React7.createElement(
1171
- "a",
1172
- {
1173
- className: orClasses4.swapDetailsLink,
1174
- href: row.href,
1175
- rel: "noreferrer",
1176
- target: "_blank"
1177
- },
1178
- row.hrefLabel ?? checkoutLabels3.viewOnExplorer
1179
- )
1180
- )
1181
- )
1182
- ];
1183
- }
1184
- function TransactionDetailCopyButton(props) {
1185
- const [copied, setCopied] = useTransientValue(false);
1186
- const label = copied ? checkoutLabels3.copied : "Copy";
1187
- return React7.createElement(
1188
- "button",
1189
- {
1190
- className: orClasses4.detailCopy,
1191
- type: "button",
1192
- "aria-label": label,
1193
- title: label,
1194
- onClick: () => {
1195
- void copyText(props.value, props.clipboard).then(() => setCopied(true)).catch((error) => props.onError?.(error));
1196
- }
1197
- },
1198
- React7.createElement(ClipboardIcon)
1199
- );
1200
- }
1201
-
1202
- // src/swap.ts
1203
1206
  function renderSwapActions(options) {
1204
1207
  const shown = options.options.filter((option) => option.provider.length > 0);
1205
1208
  if (!options.enabled || shown.length === 0) return null;
@@ -1303,7 +1306,6 @@ function renderSwapDepositPanel(options) {
1303
1306
  options.now === void 0 ? {} : { now: options.now }
1304
1307
  );
1305
1308
  if (display === void 0) return null;
1306
- const memo = display.depositMemo;
1307
1309
  const backButton = React8.createElement(
1308
1310
  "button",
1309
1311
  {
@@ -1519,16 +1521,14 @@ function renderSwapDepositPanel(options) {
1519
1521
  {
1520
1522
  className: orClasses5.swapDetails
1521
1523
  },
1522
- // Explorer stays hidden until payment completes (tx ids / settled details).
1523
- // No explorer link on the LIVE deposit row: the address has nothing on
1524
- // chain yet, and sending the payer to a third-party explorer mid-payment
1525
- // is not something this pane does. The settled/refund panes link theirs.
1526
- renderSwapCopyRow("Address", display.depositAddress, {
1527
- ...options,
1528
- selectable: true
1529
- }),
1530
- memo === void 0 ? null : renderSwapCopyRow("Memo", memo, options),
1531
- renderSwapCopyRow("Amount", display.depositAmount, { ...options, selectable: true })
1524
+ ...display.copyRows.flatMap(
1525
+ (row) => renderSwapCopyRow(
1526
+ row.label,
1527
+ row.copyValue ?? row.value,
1528
+ { ...options, selectable: row.selectable },
1529
+ row.value
1530
+ )
1531
+ )
1532
1532
  ),
1533
1533
  waitingCard(display.countdownLabel),
1534
1534
  renderSwapFeeBreakdown(display.feeBreakdown)
@@ -1553,16 +1553,18 @@ function renderSwapDepositPanel(options) {
1553
1553
  );
1554
1554
  }
1555
1555
  function renderSwapNetworkWarning(display) {
1556
+ const alarm = display.depositRisk !== "pinned";
1556
1557
  const emphasisStart = display.networkWarning.indexOf(display.networkWarningEmphasis);
1557
1558
  const before = emphasisStart === -1 ? display.networkWarning : display.networkWarning.slice(0, emphasisStart);
1558
1559
  const after = emphasisStart === -1 ? "" : display.networkWarning.slice(emphasisStart + display.networkWarningEmphasis.length);
1559
1560
  return React8.createElement(
1560
1561
  "div",
1561
1562
  {
1562
- role: "alert",
1563
- className: orClasses5.swapNetworkWarning
1563
+ ...alarm ? { role: "alert" } : {},
1564
+ "data-or-deposit-risk": display.depositRisk,
1565
+ className: alarm ? orClasses5.swapNetworkWarning : orClasses5.swapNetworkNotice
1564
1566
  },
1565
- React8.createElement(
1567
+ alarm ? React8.createElement(
1566
1568
  "svg",
1567
1569
  {
1568
1570
  xmlns: "http://www.w3.org/2000/svg",
@@ -1577,7 +1579,7 @@ function renderSwapNetworkWarning(display) {
1577
1579
  strokeWidth: 2,
1578
1580
  d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
1579
1581
  })
1580
- ),
1582
+ ) : null,
1581
1583
  React8.createElement(
1582
1584
  "div",
1583
1585
  { className: orClasses5.swapNetworkWarningContent },
@@ -1901,7 +1903,8 @@ function PaymentWizard(props) {
1901
1903
  setSelectedPickerKey(null);
1902
1904
  setSelectedSwapNetworks({});
1903
1905
  session.clearSwapStartError();
1904
- }, [session]);
1906
+ props.swapRefund?.clearSwapRefundStaging();
1907
+ }, [session, props.swapRefund]);
1905
1908
  const onSwapFocusChange = props.onSwapFocusChange;
1906
1909
  React9.useEffect(() => {
1907
1910
  onSwapFocusChange?.(selectedSwapAsset !== null);
@@ -1912,7 +1915,10 @@ function PaymentWizard(props) {
1912
1915
  return { enabled: methods.length > 0, options: methods };
1913
1916
  }, [checkout]);
1914
1917
  const currentSwapInvoice = React9.useMemo(
1915
- () => selectCurrentSwapInvoice(checkout, startedSwapInvoice, dismissedSwapInvoiceId),
1918
+ () => selectCurrentSwapInvoice(checkout, {
1919
+ started: startedSwapInvoice,
1920
+ dismissedInvoiceId: dismissedSwapInvoiceId
1921
+ }),
1916
1922
  [checkout, startedSwapInvoice, dismissedSwapInvoiceId]
1917
1923
  );
1918
1924
  const now = useTickingUnixSeconds(currentSwapInvoice !== void 0);
@@ -1925,6 +1931,7 @@ function PaymentWizard(props) {
1925
1931
  },
1926
1932
  [props.prefix, session]
1927
1933
  );
1934
+ const swapRefund = props.swapRefund;
1928
1935
  const refundSwap = React9.useCallback(
1929
1936
  async (attemptId, refundAddress, confirm) => {
1930
1937
  const prefix = props.prefix;
@@ -1932,12 +1939,17 @@ function PaymentWizard(props) {
1932
1939
  return;
1933
1940
  }
1934
1941
  try {
1935
- const invoice = await requestSwapRefund({
1942
+ const invoice = swapRefund !== void 0 ? await (confirm ? swapRefund.confirmSwapRefund : swapRefund.stageSwapRefund).call(
1943
+ swapRefund,
1944
+ { attemptId, refundAddress }
1945
+ ) : await requestSwapRefund({
1936
1946
  fetch: fetcher,
1937
1947
  prefix,
1938
1948
  reference,
1939
- invoices: [startedSwapInvoice, ...checkout?.invoices ?? []],
1940
- attemptId,
1949
+ paymentHash: resolveAttemptPaymentHash(
1950
+ [startedSwapInvoice, ...checkout?.invoices ?? []],
1951
+ attemptId
1952
+ ),
1941
1953
  refundAddress,
1942
1954
  confirm,
1943
1955
  ...props.logger === void 0 ? {} : { logger: props.logger }
@@ -1954,6 +1966,7 @@ function PaymentWizard(props) {
1954
1966
  fetcher,
1955
1967
  props.onError,
1956
1968
  props.logger,
1969
+ swapRefund,
1957
1970
  startedSwapInvoice,
1958
1971
  checkout?.invoices
1959
1972
  ]
@@ -1966,10 +1979,14 @@ function PaymentWizard(props) {
1966
1979
  );
1967
1980
  const model = createPaymentWizardModel(selection);
1968
1981
  const { wizard } = model;
1982
+ const resolveAssetUrl = props.resolveAssetUrl ?? (props.assetBaseUrl === void 0 || props.assetBaseUrl.trim() === "" ? void 0 : createAssetBaseUrlResolver(props.assetBaseUrl));
1969
1983
  const routeAssetDisplays = createWizardRouteAssetDisplays(model.routeAssets, {
1970
- selectedRoute: model.selectedRoute
1984
+ selectedRoute: model.selectedRoute,
1985
+ ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
1986
+ });
1987
+ const routeDisplays = createWizardRouteDisplays(wizard.routes, {
1988
+ ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
1971
1989
  });
1972
- const routeDisplays = createWizardRouteDisplays(wizard.routes);
1973
1990
  const showRoutePicker = routeAssetDisplays.length > 0 && (model.selectedRoute === null || routeDisplays.length === 0);
1974
1991
  const activeTutorialProvider = activeTutorial === null ? void 0 : routeDisplays.flatMap((route) => route.providers).find((provider) => provider.id === activeTutorial.providerId);
1975
1992
  const swapAssetOptions = swapOptions.enabled ? swapOptions.options.filter((option) => option.provider.length > 0) : [];
@@ -2045,17 +2062,33 @@ function PaymentWizard(props) {
2045
2062
  selectedPickerKey,
2046
2063
  startingAsset: swapStartingAsset,
2047
2064
  selectedSwapNetworks,
2065
+ // One resolver decides what a tile click MEANS, so the "ask the
2066
+ // network question only when it is a real question" rule is not
2067
+ // re-derived here: a single-network group comes back as start_swap
2068
+ // and the wizard starts it instead of opening a one-answer step.
2048
2069
  onSelectPicker: (key, previousKey) => {
2070
+ const resolved = resolveWizardSelection({
2071
+ pickerKey: key,
2072
+ previousKey,
2073
+ entries: buildMethodGridEntries(paymentMethods, swapAssetOptions),
2074
+ selectedNetworks: selectedSwapNetworks
2075
+ });
2076
+ if (resolved.kind === "none") return;
2077
+ if (resolved.kind === "select_method") {
2078
+ setSelectedPickerKey(null);
2079
+ updateWizardSelection(
2080
+ (controller) => controller.selectMethod(resolved.methodId)
2081
+ );
2082
+ if (resolved.methodId === "bitcoin") void props.onRequestLightning?.();
2083
+ return;
2084
+ }
2085
+ if (resolved.kind === "start_swap") {
2086
+ setSelectedPickerKey(null);
2087
+ selectSwapAsset(resolved.payInAsset);
2088
+ return;
2089
+ }
2049
2090
  setSelectedPickerKey(key);
2050
- const entries = buildMethodGridEntries(paymentMethods, swapAssetOptions);
2051
- setSelectedSwapNetworks(
2052
- (current) => updateSelectedSwapNetworks({
2053
- entries,
2054
- nextKey: key,
2055
- previousKey,
2056
- selectedNetworks: current
2057
- })
2058
- );
2091
+ setSelectedSwapNetworks(resolved.selectedNetworks);
2059
2092
  },
2060
2093
  onSelectNetwork: (groupKey, payInAsset) => {
2061
2094
  setSelectedSwapNetworks((current) => ({
@@ -2071,7 +2104,8 @@ function PaymentWizard(props) {
2071
2104
  void props.onRequestLightning?.();
2072
2105
  }
2073
2106
  },
2074
- onContinueSwap: selectSwapAsset
2107
+ onContinueSwap: selectSwapAsset,
2108
+ ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
2075
2109
  }) : null,
2076
2110
  selection.selectedMethod === null ? null : React9.createElement(
2077
2111
  "div",
@@ -2238,23 +2272,16 @@ function PaymentWizard(props) {
2238
2272
  );
2239
2273
  }
2240
2274
  function renderCompactPaymentMethodSelector(options) {
2241
- const entries = buildMethodGridEntries(paymentMethods, options.swapAssetOptions);
2275
+ const display = createMethodGridDisplay({
2276
+ entries: buildMethodGridEntries(paymentMethods, options.swapAssetOptions),
2277
+ selectedPickerKey: options.selectedPickerKey,
2278
+ selectedNetworks: options.selectedSwapNetworks,
2279
+ startingAsset: options.startingAsset,
2280
+ ...options.checkout === void 0 ? {} : { checkout: options.checkout }
2281
+ });
2242
2282
  const currenciesLoading = options.currenciesLoading === true && options.swapAssetOptions.length === 0;
2243
- const selectedKey = options.selectedPickerKey;
2244
- const selectedGroup = findSwapGridGroup(entries, selectedKey);
2245
- const networkRequired = selectedGroup !== void 0 && selectedGroup.options.length > 1;
2246
- const selectedGroupKey = selectedGroup?.label.trim().toUpperCase();
2247
- const selectedNetworkAsset = selectedGroupKey === void 0 ? void 0 : options.selectedSwapNetworks[selectedGroupKey];
2248
- const selectedNetworkOption = selectedGroup === void 0 || selectedNetworkAsset === void 0 ? void 0 : selectedGroup.options.find((option) => option.pay_in_asset === selectedNetworkAsset);
2249
- const continueTarget = selectedNetworkOption !== void 0 ? {
2250
- payInAsset: selectedNetworkOption.pay_in_asset,
2251
- disabled: selectedNetworkOption.available === false,
2252
- limitMessage: swapOptionLimitMessage2(selectedNetworkOption, options.checkout)
2253
- } : null;
2254
- const startingAsset = options.startingAsset;
2255
- const gridBusy = startingAsset !== null;
2256
- const continueStarting = continueTarget !== null && continueTarget.payInAsset === startingAsset;
2257
- const canContinue = continueTarget !== null && !continueTarget.disabled && selectedNetworkOption !== void 0 && !gridBusy;
2283
+ const { gridBusy, networkRequired, selectedGroup, continueTarget, canContinue } = display;
2284
+ const continueStarting = continueTarget?.starting === true;
2258
2285
  const continueButton = (className) => React9.createElement(
2259
2286
  "button",
2260
2287
  {
@@ -2264,7 +2291,7 @@ function renderCompactPaymentMethodSelector(options) {
2264
2291
  "aria-disabled": canContinue ? void 0 : "true",
2265
2292
  "aria-busy": continueStarting ? "true" : void 0,
2266
2293
  onClick: !canContinue ? void 0 : () => {
2267
- if (continueTarget === null) return;
2294
+ if (continueTarget === void 0) return;
2268
2295
  options.onContinueSwap(continueTarget.payInAsset);
2269
2296
  }
2270
2297
  },
@@ -2276,14 +2303,10 @@ function renderCompactPaymentMethodSelector(options) {
2276
2303
  "aria-hidden": "true"
2277
2304
  }),
2278
2305
  checkoutLabels5.preparingPayment
2279
- ) : continueTarget?.disabled && continueTarget.limitMessage !== void 0 ? continueTarget.limitMessage : checkoutLabels5.continue
2306
+ ) : continueTarget?.label ?? checkoutLabels5.continue
2280
2307
  );
2281
2308
  const renderNetworkSelector = (group, mobile) => {
2282
- const accent = paymentAccentId(group.label);
2283
- const groupKey = group.label.trim().toUpperCase();
2284
- const selectedAsset = options.selectedSwapNetworks[groupKey];
2285
- const selectedOption = selectedAsset === void 0 ? void 0 : group.options.find((option) => option.pay_in_asset === selectedAsset);
2286
- const { panelId, headingId } = wizardNetworkGroupIds(groupKey);
2309
+ const { accent, groupKey, panelId, headingId, selectedOption } = group;
2287
2310
  return React9.createElement(
2288
2311
  "div",
2289
2312
  {
@@ -2304,7 +2327,7 @@ function renderCompactPaymentMethodSelector(options) {
2304
2327
  id: headingId,
2305
2328
  className: orClasses6.methodNetworkHeading
2306
2329
  },
2307
- formatChooseNetworkHeading(group.label)
2330
+ group.heading
2308
2331
  ),
2309
2332
  React9.createElement(
2310
2333
  "p",
@@ -2346,7 +2369,7 @@ function renderCompactPaymentMethodSelector(options) {
2346
2369
  React9.createElement("img", {
2347
2370
  alt: "",
2348
2371
  className: orClasses6.methodNetworkIcon,
2349
- src: getNetworkIcon(option.network_label)
2372
+ src: getNetworkIcon(option.network_label, options.resolveAssetUrl)
2350
2373
  })
2351
2374
  ),
2352
2375
  React9.createElement("span", { className: "truncate" }, option.network_label),
@@ -2418,23 +2441,22 @@ function renderCompactPaymentMethodSelector(options) {
2418
2441
  "aria-label": checkoutLabels5.paymentMethod,
2419
2442
  className: orClasses6.methodGrid
2420
2443
  },
2421
- ...entries.map((entry) => {
2444
+ ...display.entries.map((entry) => {
2422
2445
  if (entry.kind === "method") {
2423
2446
  const method = entry.method;
2424
- const accent2 = paymentAccentId(method.id);
2425
2447
  return React9.createElement(
2426
2448
  "button",
2427
2449
  {
2428
2450
  key: method.id,
2429
2451
  type: "button",
2430
2452
  className: assetButtonClasses({
2431
- accent: accent2,
2453
+ accent: entry.accent,
2432
2454
  selected: false,
2433
- disabled: gridBusy
2455
+ disabled: entry.disabled
2434
2456
  }),
2435
- disabled: gridBusy,
2436
- "aria-disabled": gridBusy ? "true" : void 0,
2437
- onClick: gridBusy ? void 0 : () => options.onContinueMethod(method.id)
2457
+ disabled: entry.disabled,
2458
+ "aria-disabled": entry.disabled ? "true" : void 0,
2459
+ onClick: entry.disabled ? void 0 : () => options.onContinueMethod(method.id)
2438
2460
  },
2439
2461
  React9.createElement(
2440
2462
  "span",
@@ -2442,7 +2464,7 @@ function renderCompactPaymentMethodSelector(options) {
2442
2464
  React9.createElement("img", {
2443
2465
  alt: "",
2444
2466
  className: orClasses6.methodIcon,
2445
- src: getPaymentMethodIcon(method.id)
2467
+ src: getPaymentMethodIcon(method.id, options.resolveAssetUrl)
2446
2468
  })
2447
2469
  ),
2448
2470
  React9.createElement(
@@ -2453,21 +2475,18 @@ function renderCompactPaymentMethodSelector(options) {
2453
2475
  );
2454
2476
  }
2455
2477
  const group = entry.group;
2456
- const groupKey = group.label.trim().toUpperCase();
2457
- const pickerKey = swapPickerKey(group.label);
2458
- const selected = selectedKey === pickerKey;
2459
- const multiNetwork = group.options.length > 1;
2460
- const displayOption = group.options.find((option) => option.available !== false) ?? group.options[0];
2461
- if (displayOption === void 0) return null;
2462
- const selectedAsset = options.selectedSwapNetworks[groupKey];
2463
- const selectedOption = selectedAsset === void 0 ? void 0 : group.options.find((option) => option.pay_in_asset === selectedAsset);
2464
- const activeOption = selectedOption ?? displayOption;
2465
- const starting = group.options.some((option) => option.pay_in_asset === startingAsset);
2466
- const disabled = group.options.every((option) => option.available === false);
2467
- const accent = paymentAccentId(group.label);
2468
- const limitOption = disabled ? swapGroupLimitOption(group.options) ?? activeOption : activeOption;
2469
- const limitMessage = swapOptionLimitMessage2(limitOption, options.checkout);
2470
- const { panelId } = wizardNetworkGroupIds(groupKey);
2478
+ const {
2479
+ pickerKey,
2480
+ selected,
2481
+ multiNetwork,
2482
+ displayOption,
2483
+ selectedOption,
2484
+ starting,
2485
+ disabled,
2486
+ accent,
2487
+ limitMessage,
2488
+ panelId
2489
+ } = group;
2471
2490
  return React9.createElement(
2472
2491
  "div",
2473
2492
  { key: pickerKey, className: orClasses6.methodTile },
@@ -2486,7 +2505,14 @@ function renderCompactPaymentMethodSelector(options) {
2486
2505
  selected: starting || multiNetwork && selected,
2487
2506
  disabled: disabled || gridBusy && !starting
2488
2507
  }),
2489
- onClick: disabled || gridBusy ? void 0 : multiNetwork ? () => options.onSelectPicker(pickerKey, selectedKey) : () => options.onContinueSwap(displayOption.pay_in_asset)
2508
+ onClick: disabled || gridBusy ? void 0 : (
2509
+ // `needsNetworkStep` / `startPayInAsset` are the
2510
+ // options.length > 1 rule as DATA, so neither renderer
2511
+ // re-derives which of the two a tile click means.
2512
+ group.needsNetworkStep ? () => options.onSelectPicker(pickerKey, options.selectedPickerKey) : () => options.onContinueSwap(
2513
+ group.startPayInAsset ?? displayOption.pay_in_asset
2514
+ )
2515
+ )
2490
2516
  },
2491
2517
  React9.createElement(
2492
2518
  "span",
@@ -2497,7 +2523,7 @@ function renderCompactPaymentMethodSelector(options) {
2497
2523
  }) : React9.createElement("img", {
2498
2524
  alt: "",
2499
2525
  className: orClasses6.methodIcon,
2500
- src: getSwapOptionIcon(displayOption)
2526
+ src: getSwapOptionIcon(displayOption, options.resolveAssetUrl)
2501
2527
  })
2502
2528
  ),
2503
2529
  React9.createElement(
@@ -2643,13 +2669,13 @@ function renderWizardBreadcrumbs(options) {
2643
2669
  function swapOptionsForRoute(routeKey, options) {
2644
2670
  return options.filter((option) => swapAssetMatchesRoute(routeKey, option.pay_in_asset));
2645
2671
  }
2646
- function selectCurrentSwapInvoice(checkout, local, dismissedInvoiceId) {
2647
- const fromCheckout = checkout?.invoices.find(
2648
- (invoice) => invoice.rail === "swap" && invoice.swap !== void 0 && invoice.invoice_id !== dismissedInvoiceId
2649
- );
2650
- if (local === null || local.invoice_id === dismissedInvoiceId) return fromCheckout;
2651
- const matched = checkout?.invoices.find((invoice) => invoice.invoice_id === local.invoice_id) ?? local;
2652
- return overlaySwapRefundStaging(matched, local);
2672
+ function resolveAttemptPaymentHash(invoices, attemptId) {
2673
+ for (const invoice of invoices) {
2674
+ if (invoice == null) continue;
2675
+ if ((invoice.swap?.attempt_id ?? invoice.invoice_id) !== attemptId) continue;
2676
+ if (invoice.payment_hash !== void 0) return invoice.payment_hash;
2677
+ }
2678
+ throw new Error(`No swap attempt ${attemptId} in this checkout.`);
2653
2679
  }
2654
2680
  function renderRoutePicker(options) {
2655
2681
  return React9.createElement(
@@ -2890,6 +2916,8 @@ function CheckoutView(props) {
2890
2916
  components,
2891
2917
  classNames,
2892
2918
  children,
2919
+ resolveAssetUrl,
2920
+ assetBaseUrl,
2893
2921
  className,
2894
2922
  ...sectionProps
2895
2923
  } = props;
@@ -2960,6 +2988,21 @@ function CheckoutView(props) {
2960
2988
  ...ownsTheme ? theme.attributes : {}
2961
2989
  },
2962
2990
  customChildren === void 0 ? [
2991
+ // Above the amount and OUTSIDE the Lightning pane, which drops out on
2992
+ // the swap deposit panel, on expiry and on the receipt — the payer
2993
+ // needs to know what they are buying on all four screens.
2994
+ checkoutModel.checkout.description === void 0 || checkoutModel.checkout.description === "" ? null : React10.createElement(
2995
+ "p",
2996
+ {
2997
+ key: "order-description",
2998
+ className: joinClassNames(
2999
+ orClasses7.orderDescription,
3000
+ classNames?.orderDescription
3001
+ ),
3002
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3.orderDescription]: ""
3003
+ },
3004
+ checkoutModel.checkout.description
3005
+ ),
2963
3006
  ownsTheme ? React10.createElement(ThemeToggle, {
2964
3007
  key: "theme",
2965
3008
  className: classNames?.themeToggle,
@@ -3033,16 +3076,25 @@ function CheckoutView(props) {
3033
3076
  className: classNames?.summary,
3034
3077
  classNames
3035
3078
  }) : null,
3036
- settled ? React10.createElement(PaymentData, {
3037
- key: "payment-data",
3038
- // The model IS the checkout state, so the payment-data
3039
- // panel reads the same derivation the rest of the screen
3040
- // does. It used to re-flatten the snapshot separately,
3041
- // which reported the displayed ATTEMPT's transaction /
3079
+ settled ? React10.createElement(TransactionDetails, {
3080
+ key: "transaction-details",
3081
+ // The model IS the checkout state, so the panel reads
3082
+ // the same derivation the rest of the screen does. It
3083
+ // used to re-flatten the snapshot separately, which
3084
+ // reported the displayed ATTEMPT's transaction /
3042
3085
  // workflow state on a checkout the state already knows
3043
3086
  // is paid.
3044
- source: checkoutModel,
3045
- className: classNames?.details
3087
+ //
3088
+ // The SAME panel the swap flow renders one screen
3089
+ // earlier. A payer's whole evidence that they paid is a
3090
+ // payment hash and, on a swap, a deposit txid — so the
3091
+ // most keep-worthy screen gets copy buttons and
3092
+ // explorer links, not un-copyable text. No
3093
+ // `decodeLinkUrl` is passed, so the bolt11 never
3094
+ // reaches a third party.
3095
+ state: checkoutModel,
3096
+ className: classNames?.details,
3097
+ onError
3046
3098
  }) : expired ? React10.createElement(
3047
3099
  "div",
3048
3100
  {
@@ -3120,7 +3172,12 @@ function CheckoutView(props) {
3120
3172
  onProviderCopy,
3121
3173
  onCopy,
3122
3174
  onRequestLightning,
3123
- onSwapStarted
3175
+ onSwapStarted,
3176
+ resolveAssetUrl,
3177
+ assetBaseUrl,
3178
+ // The engine's staged refund address rides the controller this
3179
+ // model owns, so a poll cannot wipe a review in progress.
3180
+ swapRefund: checkoutModel
3124
3181
  }) : null
3125
3182
  ] : customChildren
3126
3183
  );
@@ -3153,7 +3210,6 @@ export {
3153
3210
  CopyInvoiceButton,
3154
3211
  InvoiceSummary,
3155
3212
  OpenWalletButton,
3156
- PaymentData,
3157
3213
  PaymentState,
3158
3214
  PaymentWizard,
3159
3215
  QRCode,