@pollar/react 0.10.0 → 0.10.1-rc.1

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.mjs CHANGED
@@ -1033,7 +1033,7 @@ var PollarModalFooter = () => {
1033
1033
  /* @__PURE__ */ jsx("span", { className: "pollar-footer-name", children: "Pollar" }),
1034
1034
  /* @__PURE__ */ jsxs("span", { className: "pollar-footer-version", children: [
1035
1035
  "v",
1036
- "0.10.0"
1036
+ "0.10.1-rc.1"
1037
1037
  ] })
1038
1038
  ] })
1039
1039
  ] });
@@ -2457,14 +2457,12 @@ function RouteDisplay({ quote, onSelect }) {
2457
2457
  );
2458
2458
  }
2459
2459
  var LOADING_STEPS = ["Detecting your country\u2026", "Consulting providers\u2026", "Route found!"];
2460
- var COUNTRY_CURRENCIES = {
2461
- MX: "MXN",
2462
- BR: "BRL",
2463
- CO: "COP",
2464
- CL: "CLP",
2465
- PE: "PEN",
2466
- AR: "ARS"
2467
- };
2460
+ function flagEmoji(code) {
2461
+ const cc = code.trim().toUpperCase();
2462
+ if (!/^[A-Z]{2}$/.test(cc)) return "";
2463
+ const base = 127462;
2464
+ return String.fromCodePoint(base + (cc.charCodeAt(0) - 65), base + (cc.charCodeAt(1) - 65));
2465
+ }
2468
2466
  var STATUS_LABEL = {
2469
2467
  pending: "Pending",
2470
2468
  processing: "Processing",
@@ -2479,6 +2477,9 @@ function RampWidgetTemplate({
2479
2477
  amount,
2480
2478
  currency,
2481
2479
  country,
2480
+ countries,
2481
+ countriesLoading,
2482
+ refreshing,
2482
2483
  quotes,
2483
2484
  isLoading,
2484
2485
  provider,
@@ -2497,6 +2498,7 @@ function RampWidgetTemplate({
2497
2498
  onOpenKyc,
2498
2499
  onCompleteWithdraw,
2499
2500
  onRetry,
2501
+ onRefresh,
2500
2502
  onClose
2501
2503
  }) {
2502
2504
  const isDark = theme === "dark";
@@ -2535,9 +2537,35 @@ function RampWidgetTemplate({
2535
2537
  error: "Please try again"
2536
2538
  };
2537
2539
  return /* @__PURE__ */ jsxs("div", { className: "pollar-modal-card pollar-ramp-modal", style: cssVars, onClick: (e) => e.stopPropagation(), children: [
2538
- /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-header", children: [
2539
- /* @__PURE__ */ jsx("h2", { className: "pollar-ramp-title", children: stepTitle[step] }),
2540
- /* @__PURE__ */ jsx("p", { className: "pollar-ramp-subtitle", children: stepSubtitle[step] })
2540
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header", children: [
2541
+ /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-header-text", children: [
2542
+ /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: stepTitle[step] }),
2543
+ /* @__PURE__ */ jsx("p", { className: "pollar-ramp-subtitle", children: stepSubtitle[step] })
2544
+ ] }),
2545
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header-actions", children: [
2546
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onRefresh, disabled: refreshing, "aria-label": "Refresh", children: /* @__PURE__ */ jsx(
2547
+ "svg",
2548
+ {
2549
+ className: refreshing ? "pollar-modal-refresh-icon spinning" : "pollar-modal-refresh-icon",
2550
+ width: "16",
2551
+ height: "16",
2552
+ viewBox: "0 0 16 16",
2553
+ fill: "none",
2554
+ "aria-hidden": true,
2555
+ children: /* @__PURE__ */ jsx(
2556
+ "path",
2557
+ {
2558
+ d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2v3h-3",
2559
+ stroke: "currentColor",
2560
+ strokeWidth: "1.6",
2561
+ strokeLinecap: "round",
2562
+ strokeLinejoin: "round"
2563
+ }
2564
+ )
2565
+ }
2566
+ ) }),
2567
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M2 2l12 12M14 2L2 14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })
2568
+ ] })
2541
2569
  ] }),
2542
2570
  step === "input" && /* @__PURE__ */ jsxs(Fragment, { children: [
2543
2571
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-tabs", children: [
@@ -2594,30 +2622,25 @@ function RampWidgetTemplate({
2594
2622
  ] }),
2595
2623
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
2596
2624
  /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Country" }),
2597
- /* @__PURE__ */ jsxs(
2598
- "select",
2599
- {
2600
- className: "pollar-ramp-input",
2601
- value: country,
2602
- onChange: (e) => {
2603
- const c = e.target.value;
2604
- onCountryChange(c);
2605
- if (COUNTRY_CURRENCIES[c]) onCurrencyChange(COUNTRY_CURRENCIES[c]);
2606
- },
2607
- children: [
2608
- /* @__PURE__ */ jsx("option", { value: "MX", children: "\u{1F1F2}\u{1F1FD} Mexico" }),
2609
- /* @__PURE__ */ jsx("option", { value: "BR", children: "\u{1F1E7}\u{1F1F7} Brazil" }),
2610
- /* @__PURE__ */ jsx("option", { value: "CO", children: "\u{1F1E8}\u{1F1F4} Colombia" }),
2611
- /* @__PURE__ */ jsx("option", { value: "CL", children: "\u{1F1E8}\u{1F1F1} Chile" }),
2612
- /* @__PURE__ */ jsx("option", { value: "PE", children: "\u{1F1F5}\u{1F1EA} Peru" }),
2613
- /* @__PURE__ */ jsx("option", { value: "AR", children: "\u{1F1E6}\u{1F1F7} Argentina" })
2614
- ]
2615
- }
2616
- )
2625
+ countriesLoading ? /* @__PURE__ */ jsx("div", { className: "pollar-ramp-input pollar-ramp-input-loading", children: "Loading countries\u2026" }) : countries.length === 0 ? /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "No ramp providers available on this network yet." }) : /* @__PURE__ */ jsx("select", { className: "pollar-ramp-input", value: country, onChange: (e) => onCountryChange(e.target.value), children: countries.map((c) => /* @__PURE__ */ jsxs("option", { value: c.code, children: [
2626
+ flagEmoji(c.code),
2627
+ " ",
2628
+ c.code,
2629
+ c.currency ? ` \u2014 ${c.currency}` : ""
2630
+ ] }, c.code)) })
2617
2631
  ] }),
2618
2632
  /* @__PURE__ */ jsxs("div", { className: "pollar-modal-actions", children: [
2619
2633
  /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: "Cancel" }),
2620
- /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-primary", disabled: !amount || isLoading, onClick: onFindRoute, children: "Find best route" })
2634
+ /* @__PURE__ */ jsx(
2635
+ "button",
2636
+ {
2637
+ type: "button",
2638
+ className: "pollar-btn-primary",
2639
+ disabled: !amount || isLoading || countriesLoading || countries.length === 0,
2640
+ onClick: onFindRoute,
2641
+ children: "Find best route"
2642
+ }
2643
+ )
2621
2644
  ] })
2622
2645
  ] }),
2623
2646
  step === "loading_quote" && /* @__PURE__ */ jsx("div", { className: "pollar-ramp-loading", children: LOADING_STEPS.map((text, i) => /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-loading-row", children: [
@@ -2635,13 +2658,7 @@ function RampWidgetTemplate({
2635
2658
  ] }),
2636
2659
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
2637
2660
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Status" }),
2638
- /* @__PURE__ */ jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsx(
2639
- "code",
2640
- {
2641
- style: { color: txStatus === "completed" ? "var(--pollar-success-text)" : void 0 },
2642
- children: txStatus ? STATUS_LABEL[txStatus] : "Processing"
2643
- }
2644
- ) })
2661
+ /* @__PURE__ */ jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsx("code", { style: { color: txStatus === "completed" ? "var(--pollar-success-text)" : void 0 }, children: txStatus ? STATUS_LABEL[txStatus] : "Processing" }) })
2645
2662
  ] }),
2646
2663
  stellarTxHash && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
2647
2664
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Stellar tx" }),
@@ -2677,8 +2694,11 @@ function RampWidget({ onClose }) {
2677
2694
  const [step, setStep] = useState("input");
2678
2695
  const [direction, setDirection] = useState("onramp");
2679
2696
  const [amount, setAmount] = useState("");
2680
- const [currency, setCurrency] = useState("ARS");
2681
- const [country, setCountry] = useState("AR");
2697
+ const [currency, setCurrency] = useState("");
2698
+ const [country, setCountry] = useState("");
2699
+ const [countries, setCountries] = useState([]);
2700
+ const [countriesLoading, setCountriesLoading] = useState(true);
2701
+ const [refreshing, setRefreshing] = useState(false);
2682
2702
  const [quotes, setQuotes] = useState([]);
2683
2703
  const [isLoading, setIsLoading] = useState(false);
2684
2704
  const [txId, setTxId] = useState(null);
@@ -2710,6 +2730,50 @@ function RampWidget({ onClose }) {
2710
2730
  clearInterval(id);
2711
2731
  };
2712
2732
  }, [step, txId, txStatus, client]);
2733
+ async function loadCountries(resetSelection) {
2734
+ setCountriesLoading(true);
2735
+ try {
2736
+ const { countries: list } = await client.getRampCountries();
2737
+ setCountries(list);
2738
+ const first = list[0];
2739
+ const stillValid = list.some((c) => c.code === country);
2740
+ if (first && (resetSelection || !stillValid)) {
2741
+ setCountry(first.code);
2742
+ if (first.currency) setCurrency(first.currency);
2743
+ }
2744
+ } catch {
2745
+ setCountries([]);
2746
+ } finally {
2747
+ setCountriesLoading(false);
2748
+ }
2749
+ }
2750
+ useEffect(() => {
2751
+ loadCountries(true);
2752
+ }, []);
2753
+ function handleCountryChange(code) {
2754
+ setCountry(code);
2755
+ const match = countries.find((c) => c.code === code);
2756
+ if (match?.currency) setCurrency(match.currency);
2757
+ }
2758
+ async function handleRefresh() {
2759
+ if (refreshing) return;
2760
+ setRefreshing(true);
2761
+ try {
2762
+ await loadCountries(false);
2763
+ if (step === "select_route") {
2764
+ const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
2765
+ setQuotes(result.quotes ?? []);
2766
+ } else if (step === "status" && txId) {
2767
+ const tx = await client.getRampTransaction(txId);
2768
+ setTxStatus(tx.status);
2769
+ if (tx.stellarTxHash) setStellarTxHash(tx.stellarTxHash);
2770
+ if (tx.kycUrl) setKycUrl(tx.kycUrl);
2771
+ }
2772
+ } catch {
2773
+ } finally {
2774
+ setRefreshing(false);
2775
+ }
2776
+ }
2713
2777
  function resetToInput() {
2714
2778
  setStep("input");
2715
2779
  setQuotes([]);
@@ -2815,6 +2879,9 @@ function RampWidget({ onClose }) {
2815
2879
  amount,
2816
2880
  currency,
2817
2881
  country,
2882
+ countries,
2883
+ countriesLoading,
2884
+ refreshing,
2818
2885
  quotes,
2819
2886
  isLoading,
2820
2887
  provider,
@@ -2827,12 +2894,13 @@ function RampWidget({ onClose }) {
2827
2894
  onDirectionChange: setDirection,
2828
2895
  onAmountChange: setAmount,
2829
2896
  onCurrencyChange: setCurrency,
2830
- onCountryChange: setCountry,
2897
+ onCountryChange: handleCountryChange,
2831
2898
  onFindRoute: handleFindRoute,
2832
2899
  onSelectQuote: handleSelectQuote,
2833
2900
  onOpenKyc: handleOpenKyc,
2834
2901
  onCompleteWithdraw: handleCompleteWithdraw,
2835
2902
  onRetry: resetToInput,
2903
+ onRefresh: handleRefresh,
2836
2904
  onClose
2837
2905
  }
2838
2906
  ) });
@@ -3505,7 +3573,6 @@ var PROVIDER_LABELS = {
3505
3573
  soroswap: "Soroswap",
3506
3574
  sdex: "Stellar DEX"
3507
3575
  };
3508
- var IMPLEMENTED_PROVIDERS = ["auto", "aquarius"];
3509
3576
  function formatAmount2(value) {
3510
3577
  const n = parseFloat(value);
3511
3578
  return isNaN(n) ? value : n.toLocaleString(void 0, { maximumFractionDigits: 7 });
@@ -3528,6 +3595,9 @@ function SwapModalTemplate({
3528
3595
  formError,
3529
3596
  isLoadingData,
3530
3597
  smartUnsupported,
3598
+ configLoading,
3599
+ swapUnavailable,
3600
+ buyNeedsTrustline,
3531
3601
  transaction,
3532
3602
  showXdr,
3533
3603
  copied,
@@ -3537,8 +3607,10 @@ function SwapModalTemplate({
3537
3607
  isInProgress,
3538
3608
  onClose,
3539
3609
  onBack,
3610
+ onRefresh,
3540
3611
  onSelectSell,
3541
3612
  onSelectBuy,
3613
+ onAddCustomToken,
3542
3614
  onAmountChange,
3543
3615
  onProviderChange,
3544
3616
  onSwap,
@@ -3548,6 +3620,21 @@ function SwapModalTemplate({
3548
3620
  onDone
3549
3621
  }) {
3550
3622
  const isDark = theme === "dark";
3623
+ const [customOpen, setCustomOpen] = useState(false);
3624
+ const [customCode, setCustomCode] = useState("");
3625
+ const [customIssuer, setCustomIssuer] = useState("");
3626
+ const [customError, setCustomError] = useState("");
3627
+ const submitCustomToken = () => {
3628
+ const err = onAddCustomToken(customCode, customIssuer);
3629
+ if (err) {
3630
+ setCustomError(err);
3631
+ return;
3632
+ }
3633
+ setCustomCode("");
3634
+ setCustomIssuer("");
3635
+ setCustomError("");
3636
+ setCustomOpen(false);
3637
+ };
3551
3638
  const cssVars = {
3552
3639
  "--pollar-accent": accentColor,
3553
3640
  "--pollar-bg": isDark ? "#1a1a1a" : "#ffffff",
@@ -3575,9 +3662,34 @@ function SwapModalTemplate({
3575
3662
  showBack && /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onBack, "aria-label": "Back", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M10 3L5 8l5 5", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }),
3576
3663
  /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: title })
3577
3664
  ] }),
3578
- !isInProgress && /* @__PURE__ */ jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M2 2l12 12M14 2L2 14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) }) })
3665
+ !isInProgress && /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header-actions", children: [
3666
+ step === "form" && /* @__PURE__ */ jsx(
3667
+ "button",
3668
+ {
3669
+ type: "button",
3670
+ className: "pollar-modal-close",
3671
+ onClick: onRefresh,
3672
+ disabled: configLoading || isLoadingData,
3673
+ "aria-label": "Refresh",
3674
+ title: "Refresh balances and options",
3675
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx(
3676
+ "path",
3677
+ {
3678
+ d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2.5v3h-3",
3679
+ stroke: "currentColor",
3680
+ strokeWidth: "1.6",
3681
+ strokeLinecap: "round",
3682
+ strokeLinejoin: "round"
3683
+ }
3684
+ ) })
3685
+ }
3686
+ ),
3687
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M2 2l12 12M14 2L2 14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) })
3688
+ ] })
3579
3689
  ] }),
3580
- step === "form" && /* @__PURE__ */ jsxs(Fragment, { children: [
3690
+ step === "form" && configLoading && /* @__PURE__ */ jsx("div", { className: "pollar-send-hint", children: "Loading swap options\u2026" }),
3691
+ step === "form" && !configLoading && swapUnavailable && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "Swap is not available for this app." }),
3692
+ step === "form" && !configLoading && !swapUnavailable && /* @__PURE__ */ jsxs(Fragment, { children: [
3581
3693
  smartUnsupported && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "Swaps are not yet available for smart (passkey) wallets." }),
3582
3694
  /* @__PURE__ */ jsxs("div", { className: "pollar-send-field", children: [
3583
3695
  /* @__PURE__ */ jsx("label", { className: "pollar-send-label", children: "You pay" }),
@@ -3641,7 +3753,45 @@ function SwapModalTemplate({
3641
3753
  ] }, assetOptionKey(o)))
3642
3754
  ]
3643
3755
  }
3644
- )
3756
+ ),
3757
+ !isLoadingData && (customOpen ? /* @__PURE__ */ jsxs("div", { className: "pollar-swap-custom", children: [
3758
+ /* @__PURE__ */ jsx(
3759
+ "input",
3760
+ {
3761
+ className: "pollar-input",
3762
+ type: "text",
3763
+ placeholder: "Code (e.g. USDC)",
3764
+ value: customCode,
3765
+ onChange: (e) => setCustomCode(e.target.value)
3766
+ }
3767
+ ),
3768
+ /* @__PURE__ */ jsx(
3769
+ "input",
3770
+ {
3771
+ className: "pollar-input",
3772
+ type: "text",
3773
+ placeholder: "Issuer (G...)",
3774
+ value: customIssuer,
3775
+ onChange: (e) => setCustomIssuer(e.target.value)
3776
+ }
3777
+ ),
3778
+ customError && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: customError }),
3779
+ /* @__PURE__ */ jsxs("div", { className: "pollar-swap-custom-actions", children: [
3780
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-primary", onClick: submitCustomToken, children: "Add token" }),
3781
+ /* @__PURE__ */ jsx(
3782
+ "button",
3783
+ {
3784
+ type: "button",
3785
+ className: "pollar-swap-custom-cancel",
3786
+ onClick: () => {
3787
+ setCustomOpen(false);
3788
+ setCustomError("");
3789
+ },
3790
+ children: "Cancel"
3791
+ }
3792
+ )
3793
+ ] })
3794
+ ] }) : /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-swap-custom-toggle", onClick: () => setCustomOpen(true), children: "+ Add a token by code / issuer" }))
3645
3795
  ] }),
3646
3796
  /* @__PURE__ */ jsxs("div", { className: "pollar-send-field", children: [
3647
3797
  /* @__PURE__ */ jsx("label", { className: "pollar-send-label", children: "Route" }),
@@ -3651,13 +3801,7 @@ function SwapModalTemplate({
3651
3801
  className: "pollar-input pollar-send-select",
3652
3802
  value: provider,
3653
3803
  onChange: (e) => onProviderChange(e.target.value),
3654
- children: providers.map((p) => {
3655
- const implemented = IMPLEMENTED_PROVIDERS.includes(p);
3656
- return /* @__PURE__ */ jsxs("option", { value: p, disabled: !implemented, children: [
3657
- PROVIDER_LABELS[p],
3658
- implemented ? "" : " (coming soon)"
3659
- ] }, p);
3660
- })
3804
+ children: providers.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: PROVIDER_LABELS[p] }, p))
3661
3805
  }
3662
3806
  )
3663
3807
  ] }),
@@ -3694,8 +3838,13 @@ function SwapModalTemplate({
3694
3838
  ] })
3695
3839
  ] }),
3696
3840
  !quoteLoading && !quoteError && !quote && selectedSell && selectedBuy && !!amount && /* @__PURE__ */ jsx("div", { className: "pollar-send-hint", children: "No route found for this pair." }),
3841
+ buyNeedsTrustline && selectedBuy && /* @__PURE__ */ jsxs("div", { className: "pollar-swap-trustline-notice", children: [
3842
+ "To receive ",
3843
+ selectedBuy.code,
3844
+ " your wallet needs a trustline. Swapping will create it first (~0.5 XLM reserve, refundable if you later remove it)."
3845
+ ] }),
3697
3846
  formError && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: formError }),
3698
- /* @__PURE__ */ jsx("div", { className: "pollar-modal-actions", children: /* @__PURE__ */ jsx("button", { className: "pollar-btn-primary", onClick: onSwap, disabled: !canSwap, children: "Swap" }) })
3847
+ /* @__PURE__ */ jsx("div", { className: "pollar-modal-actions", children: /* @__PURE__ */ jsx("button", { className: "pollar-btn-primary", onClick: onSwap, disabled: !canSwap, children: buyNeedsTrustline ? "Create trustline & swap" : "Swap" }) })
3699
3848
  ] }),
3700
3849
  step === "tx" && /* @__PURE__ */ jsx(
3701
3850
  TxStatusView,
@@ -3717,14 +3866,18 @@ function SwapModalTemplate({
3717
3866
  ] });
3718
3867
  }
3719
3868
  var QUOTE_DEBOUNCE_MS = 400;
3720
- var ALL_PROVIDERS = ["auto", "aquarius", "soroswap", "sdex"];
3721
3869
  function toRef(a) {
3722
3870
  if (a.type === "native") return { type: "native" };
3723
3871
  if (a.type === "credit_alphanum4") return { type: "credit_alphanum4", code: a.code, issuer: a.issuer };
3724
3872
  return { type: "credit_alphanum12", code: a.code, issuer: a.issuer };
3725
3873
  }
3874
+ function catalogRef(code, issuer) {
3875
+ return code.length <= 4 ? { type: "credit_alphanum4", code, issuer } : { type: "credit_alphanum12", code, issuer };
3876
+ }
3726
3877
  function SwapModal({ onClose }) {
3727
3878
  const {
3879
+ getSwapConfig,
3880
+ getSwapTokens,
3728
3881
  getSwapQuote,
3729
3882
  swap,
3730
3883
  walletBalance,
@@ -3744,6 +3897,9 @@ function SwapModal({ onClose }) {
3744
3897
  const [selectedBuy, setSelectedBuy] = useState(null);
3745
3898
  const [amount, setAmount] = useState("");
3746
3899
  const [provider, setProvider] = useState("auto");
3900
+ const [venues, setVenues] = useState(null);
3901
+ const [catalogTokens, setCatalogTokens] = useState([]);
3902
+ const [customTokens, setCustomTokens] = useState([]);
3747
3903
  const [quotes, setQuotes] = useState([]);
3748
3904
  const [quoteLoading, setQuoteLoading] = useState(false);
3749
3905
  const [quoteError, setQuoteError] = useState("");
@@ -3756,6 +3912,26 @@ function SwapModal({ onClose }) {
3756
3912
  void refreshWalletBalance();
3757
3913
  void refreshAssets();
3758
3914
  }, [refreshWalletBalance, refreshAssets]);
3915
+ const loadConfig = useCallback(() => {
3916
+ setVenues(null);
3917
+ return getSwapConfig().then(setVenues).catch(() => setVenues([]));
3918
+ }, [getSwapConfig]);
3919
+ useEffect(() => {
3920
+ void loadConfig();
3921
+ }, [loadConfig]);
3922
+ const loadCatalog = useCallback(
3923
+ () => getSwapTokens().then(setCatalogTokens).catch(() => setCatalogTokens([])),
3924
+ [getSwapTokens]
3925
+ );
3926
+ useEffect(() => {
3927
+ void loadCatalog();
3928
+ }, [loadCatalog]);
3929
+ function handleRefresh() {
3930
+ void refreshWalletBalance();
3931
+ void refreshAssets();
3932
+ void loadConfig();
3933
+ void loadCatalog();
3934
+ }
3759
3935
  useEffect(
3760
3936
  () => () => {
3761
3937
  if (copyTimerRef.current !== null) clearTimeout(copyTimerRef.current);
@@ -3767,11 +3943,47 @@ function SwapModal({ onClose }) {
3767
3943
  const isLoadingData = walletBalance.step === "loading" || enabledAssets.step === "loading";
3768
3944
  const sellOptions = balances.filter((b) => parseFloat(b.available) > 0).map((b) => ({ ref: toRef(b), code: b.code, issuer: b.issuer, available: b.available, enabledInApp: b.enabledInApp }));
3769
3945
  const buyKeyOfSell = selectedSell ? `${selectedSell.code}:${selectedSell.issuer ?? "native"}` : "";
3770
- const buyOptions = assetRecords.map((a) => ({ ref: toRef(a), code: a.code, issuer: a.issuer, enabledInApp: a.enabledInApp })).filter((o) => `${o.code}:${o.issuer ?? "native"}` !== buyKeyOfSell);
3771
- const providers = smartUnsupported ? ALL_PROVIDERS.filter((p) => p !== "sdex") : ALL_PROVIDERS;
3946
+ const optKey = (o) => `${o.code}:${o.issuer ?? "native"}`;
3947
+ const enabledBuy = assetRecords.map((a) => ({
3948
+ ref: toRef(a),
3949
+ code: a.code,
3950
+ issuer: a.issuer,
3951
+ enabledInApp: a.enabledInApp
3952
+ }));
3953
+ const enabledKeys = new Set(enabledBuy.map(optKey));
3954
+ const catalogBuy = catalogTokens.map((tk) => ({
3955
+ ref: catalogRef(tk.code, tk.issuer),
3956
+ code: tk.code,
3957
+ issuer: tk.issuer,
3958
+ enabledInApp: false
3959
+ })).filter((o) => !enabledKeys.has(optKey(o)));
3960
+ const knownKeys = /* @__PURE__ */ new Set([...enabledKeys, ...catalogBuy.map(optKey)]);
3961
+ const customBuy = customTokens.filter((o) => !knownKeys.has(optKey(o)));
3962
+ const buyOptions = [...enabledBuy, ...catalogBuy, ...customBuy].filter(
3963
+ (o) => optKey(o) !== buyKeyOfSell
3964
+ );
3965
+ function addCustomToken(code, issuer) {
3966
+ const c = code.trim().toUpperCase();
3967
+ const i = issuer.trim();
3968
+ if (c.length < 1 || c.length > 12) return "Code must be 1-12 characters";
3969
+ if (i.length !== 56 || !i.startsWith("G")) return "Issuer must be a Stellar address (G..., 56 chars)";
3970
+ const opt = { ref: catalogRef(c, i), code: c, issuer: i, enabledInApp: false };
3971
+ setCustomTokens((prev) => prev.some((o) => optKey(o) === optKey(opt)) ? prev : [...prev, opt]);
3972
+ setSelectedBuy(opt);
3973
+ return null;
3974
+ }
3975
+ const buyNeedsTrustline = (() => {
3976
+ if (!selectedBuy || smartUnsupported) return false;
3977
+ if (selectedBuy.ref.type === "native") return false;
3978
+ const rec = assetRecords.find((a) => a.code === selectedBuy.code && a.issuer === selectedBuy.issuer);
3979
+ return !rec?.trustlineEstablished;
3980
+ })();
3981
+ const configLoading = venues === null;
3982
+ const swapUnavailable = venues !== null && venues.length === 0;
3983
+ const providers = venues && venues.length > 0 ? ["auto", ...venues] : [];
3772
3984
  const quote = quotes[0] ?? null;
3773
3985
  useEffect(() => {
3774
- if (step !== "form" || !selectedSell || !selectedBuy || !amount || parseFloat(amount) <= 0) {
3986
+ if (step !== "form" || swapUnavailable || configLoading || !selectedSell || !selectedBuy || !amount || parseFloat(amount) <= 0) {
3775
3987
  setQuotes([]);
3776
3988
  setQuoteError("");
3777
3989
  setQuoteLoading(false);
@@ -3803,7 +4015,7 @@ function SwapModal({ onClose }) {
3803
4015
  cancelled = true;
3804
4016
  clearTimeout(t);
3805
4017
  };
3806
- }, [step, selectedSell, selectedBuy, amount, provider, getSwapQuote]);
4018
+ }, [step, swapUnavailable, configLoading, selectedSell, selectedBuy, amount, provider, getSwapQuote]);
3807
4019
  const hash = transaction.step === "success" ? transaction.hash : null;
3808
4020
  const buildData = "buildData" in transaction ? transaction.buildData : null;
3809
4021
  const explorerNetwork = buildData?.summary.network?.toLowerCase().includes("testnet") ? "testnet" : buildData ? "public" : network === "testnet" ? "testnet" : "public";
@@ -3884,6 +4096,9 @@ function SwapModal({ onClose }) {
3884
4096
  formError,
3885
4097
  isLoadingData,
3886
4098
  smartUnsupported,
4099
+ configLoading,
4100
+ swapUnavailable,
4101
+ buyNeedsTrustline,
3887
4102
  transaction,
3888
4103
  showXdr,
3889
4104
  copied,
@@ -3893,8 +4108,10 @@ function SwapModal({ onClose }) {
3893
4108
  isInProgress,
3894
4109
  onClose,
3895
4110
  onBack: handleBack,
4111
+ onRefresh: handleRefresh,
3896
4112
  onSelectSell: setSelectedSell,
3897
4113
  onSelectBuy: setSelectedBuy,
4114
+ onAddCustomToken: addCustomToken,
3898
4115
  onAmountChange: setAmount,
3899
4116
  onProviderChange: setProvider,
3900
4117
  onSwap: () => void handleSwap(),
@@ -4606,6 +4823,18 @@ function PollarProvider({
4606
4823
  const [pollarClient] = useState(
4607
4824
  () => client instanceof PollarClient ? client : new PollarClient({ passkey: browserPasskeyCeremony, passkeySign: browserPasskeySigner, ...client })
4608
4825
  );
4826
+ const ownsClientRef = useRef(!(client instanceof PollarClient));
4827
+ const destroyTimerRef = useRef(null);
4828
+ useEffect(() => {
4829
+ if (!ownsClientRef.current) return;
4830
+ if (destroyTimerRef.current) {
4831
+ clearTimeout(destroyTimerRef.current);
4832
+ destroyTimerRef.current = null;
4833
+ }
4834
+ return () => {
4835
+ destroyTimerRef.current = setTimeout(() => pollarClient.destroy(), 0);
4836
+ };
4837
+ }, [pollarClient]);
4609
4838
  const [networkState, setNetworkState] = useState(() => pollarClient.getNetworkState());
4610
4839
  const [sessionState, setSessionState] = useState(null);
4611
4840
  const [verified, setVerified] = useState(false);
@@ -4745,6 +4974,8 @@ function PollarProvider({
4745
4974
  openSendModal: () => setSendModalOpen(true),
4746
4975
  openReceiveModal: () => setReceiveModalOpen(true),
4747
4976
  // swap
4977
+ getSwapConfig: () => pollarClient.getSwapConfig(),
4978
+ getSwapTokens: () => pollarClient.getSwapTokens(),
4748
4979
  getSwapQuote: (params) => pollarClient.getSwapQuote(params),
4749
4980
  swap: (quote, opts) => pollarClient.swap(quote, opts),
4750
4981
  openSwapModal: () => setSwapModalOpen(true),