@pollar/react 0.10.0 → 0.10.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.0"
1037
1037
  ] })
1038
1038
  ] })
1039
1039
  ] });
@@ -3505,7 +3505,6 @@ var PROVIDER_LABELS = {
3505
3505
  soroswap: "Soroswap",
3506
3506
  sdex: "Stellar DEX"
3507
3507
  };
3508
- var IMPLEMENTED_PROVIDERS = ["auto", "aquarius"];
3509
3508
  function formatAmount2(value) {
3510
3509
  const n = parseFloat(value);
3511
3510
  return isNaN(n) ? value : n.toLocaleString(void 0, { maximumFractionDigits: 7 });
@@ -3528,6 +3527,9 @@ function SwapModalTemplate({
3528
3527
  formError,
3529
3528
  isLoadingData,
3530
3529
  smartUnsupported,
3530
+ configLoading,
3531
+ swapUnavailable,
3532
+ buyNeedsTrustline,
3531
3533
  transaction,
3532
3534
  showXdr,
3533
3535
  copied,
@@ -3537,6 +3539,7 @@ function SwapModalTemplate({
3537
3539
  isInProgress,
3538
3540
  onClose,
3539
3541
  onBack,
3542
+ onRefresh,
3540
3543
  onSelectSell,
3541
3544
  onSelectBuy,
3542
3545
  onAmountChange,
@@ -3575,9 +3578,34 @@ function SwapModalTemplate({
3575
3578
  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
3579
  /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: title })
3577
3580
  ] }),
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" }) }) }) })
3581
+ !isInProgress && /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header-actions", children: [
3582
+ step === "form" && /* @__PURE__ */ jsx(
3583
+ "button",
3584
+ {
3585
+ type: "button",
3586
+ className: "pollar-modal-close",
3587
+ onClick: onRefresh,
3588
+ disabled: configLoading || isLoadingData,
3589
+ "aria-label": "Refresh",
3590
+ title: "Refresh balances and options",
3591
+ children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx(
3592
+ "path",
3593
+ {
3594
+ d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2.5v3h-3",
3595
+ stroke: "currentColor",
3596
+ strokeWidth: "1.6",
3597
+ strokeLinecap: "round",
3598
+ strokeLinejoin: "round"
3599
+ }
3600
+ ) })
3601
+ }
3602
+ ),
3603
+ /* @__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" }) }) })
3604
+ ] })
3579
3605
  ] }),
3580
- step === "form" && /* @__PURE__ */ jsxs(Fragment, { children: [
3606
+ step === "form" && configLoading && /* @__PURE__ */ jsx("div", { className: "pollar-send-hint", children: "Loading swap options\u2026" }),
3607
+ step === "form" && !configLoading && swapUnavailable && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "Swap is not available for this app." }),
3608
+ step === "form" && !configLoading && !swapUnavailable && /* @__PURE__ */ jsxs(Fragment, { children: [
3581
3609
  smartUnsupported && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "Swaps are not yet available for smart (passkey) wallets." }),
3582
3610
  /* @__PURE__ */ jsxs("div", { className: "pollar-send-field", children: [
3583
3611
  /* @__PURE__ */ jsx("label", { className: "pollar-send-label", children: "You pay" }),
@@ -3651,13 +3679,7 @@ function SwapModalTemplate({
3651
3679
  className: "pollar-input pollar-send-select",
3652
3680
  value: provider,
3653
3681
  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
- })
3682
+ children: providers.map((p) => /* @__PURE__ */ jsx("option", { value: p, children: PROVIDER_LABELS[p] }, p))
3661
3683
  }
3662
3684
  )
3663
3685
  ] }),
@@ -3694,8 +3716,13 @@ function SwapModalTemplate({
3694
3716
  ] })
3695
3717
  ] }),
3696
3718
  !quoteLoading && !quoteError && !quote && selectedSell && selectedBuy && !!amount && /* @__PURE__ */ jsx("div", { className: "pollar-send-hint", children: "No route found for this pair." }),
3719
+ buyNeedsTrustline && selectedBuy && /* @__PURE__ */ jsxs("div", { className: "pollar-swap-trustline-notice", children: [
3720
+ "To receive ",
3721
+ selectedBuy.code,
3722
+ " your wallet needs a trustline. Swapping will create it first (~0.5 XLM reserve, refundable if you later remove it)."
3723
+ ] }),
3697
3724
  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" }) })
3725
+ /* @__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
3726
  ] }),
3700
3727
  step === "tx" && /* @__PURE__ */ jsx(
3701
3728
  TxStatusView,
@@ -3717,14 +3744,18 @@ function SwapModalTemplate({
3717
3744
  ] });
3718
3745
  }
3719
3746
  var QUOTE_DEBOUNCE_MS = 400;
3720
- var ALL_PROVIDERS = ["auto", "aquarius", "soroswap", "sdex"];
3721
3747
  function toRef(a) {
3722
3748
  if (a.type === "native") return { type: "native" };
3723
3749
  if (a.type === "credit_alphanum4") return { type: "credit_alphanum4", code: a.code, issuer: a.issuer };
3724
3750
  return { type: "credit_alphanum12", code: a.code, issuer: a.issuer };
3725
3751
  }
3752
+ function catalogRef(code, issuer) {
3753
+ return code.length <= 4 ? { type: "credit_alphanum4", code, issuer } : { type: "credit_alphanum12", code, issuer };
3754
+ }
3726
3755
  function SwapModal({ onClose }) {
3727
3756
  const {
3757
+ getSwapConfig,
3758
+ getSwapTokens,
3728
3759
  getSwapQuote,
3729
3760
  swap,
3730
3761
  walletBalance,
@@ -3744,6 +3775,8 @@ function SwapModal({ onClose }) {
3744
3775
  const [selectedBuy, setSelectedBuy] = useState(null);
3745
3776
  const [amount, setAmount] = useState("");
3746
3777
  const [provider, setProvider] = useState("auto");
3778
+ const [venues, setVenues] = useState(null);
3779
+ const [catalogTokens, setCatalogTokens] = useState([]);
3747
3780
  const [quotes, setQuotes] = useState([]);
3748
3781
  const [quoteLoading, setQuoteLoading] = useState(false);
3749
3782
  const [quoteError, setQuoteError] = useState("");
@@ -3756,6 +3789,26 @@ function SwapModal({ onClose }) {
3756
3789
  void refreshWalletBalance();
3757
3790
  void refreshAssets();
3758
3791
  }, [refreshWalletBalance, refreshAssets]);
3792
+ const loadConfig = useCallback(() => {
3793
+ setVenues(null);
3794
+ return getSwapConfig().then(setVenues).catch(() => setVenues([]));
3795
+ }, [getSwapConfig]);
3796
+ useEffect(() => {
3797
+ void loadConfig();
3798
+ }, [loadConfig]);
3799
+ const loadCatalog = useCallback(
3800
+ () => getSwapTokens().then(setCatalogTokens).catch(() => setCatalogTokens([])),
3801
+ [getSwapTokens]
3802
+ );
3803
+ useEffect(() => {
3804
+ void loadCatalog();
3805
+ }, [loadCatalog]);
3806
+ function handleRefresh() {
3807
+ void refreshWalletBalance();
3808
+ void refreshAssets();
3809
+ void loadConfig();
3810
+ void loadCatalog();
3811
+ }
3759
3812
  useEffect(
3760
3813
  () => () => {
3761
3814
  if (copyTimerRef.current !== null) clearTimeout(copyTimerRef.current);
@@ -3767,11 +3820,33 @@ function SwapModal({ onClose }) {
3767
3820
  const isLoadingData = walletBalance.step === "loading" || enabledAssets.step === "loading";
3768
3821
  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
3822
  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;
3823
+ const optKey = (o) => `${o.code}:${o.issuer ?? "native"}`;
3824
+ const enabledBuy = assetRecords.map((a) => ({
3825
+ ref: toRef(a),
3826
+ code: a.code,
3827
+ issuer: a.issuer,
3828
+ enabledInApp: a.enabledInApp
3829
+ }));
3830
+ const enabledKeys = new Set(enabledBuy.map(optKey));
3831
+ const catalogBuy = catalogTokens.map((tk) => ({
3832
+ ref: catalogRef(tk.code, tk.issuer),
3833
+ code: tk.code,
3834
+ issuer: tk.issuer,
3835
+ enabledInApp: false
3836
+ })).filter((o) => !enabledKeys.has(optKey(o)));
3837
+ const buyOptions = [...enabledBuy, ...catalogBuy].filter((o) => optKey(o) !== buyKeyOfSell);
3838
+ const buyNeedsTrustline = (() => {
3839
+ if (!selectedBuy || smartUnsupported) return false;
3840
+ if (selectedBuy.ref.type === "native") return false;
3841
+ const rec = assetRecords.find((a) => a.code === selectedBuy.code && a.issuer === selectedBuy.issuer);
3842
+ return !rec?.trustlineEstablished;
3843
+ })();
3844
+ const configLoading = venues === null;
3845
+ const swapUnavailable = venues !== null && venues.length === 0;
3846
+ const providers = venues && venues.length > 0 ? ["auto", ...venues] : [];
3772
3847
  const quote = quotes[0] ?? null;
3773
3848
  useEffect(() => {
3774
- if (step !== "form" || !selectedSell || !selectedBuy || !amount || parseFloat(amount) <= 0) {
3849
+ if (step !== "form" || swapUnavailable || configLoading || !selectedSell || !selectedBuy || !amount || parseFloat(amount) <= 0) {
3775
3850
  setQuotes([]);
3776
3851
  setQuoteError("");
3777
3852
  setQuoteLoading(false);
@@ -3803,7 +3878,7 @@ function SwapModal({ onClose }) {
3803
3878
  cancelled = true;
3804
3879
  clearTimeout(t);
3805
3880
  };
3806
- }, [step, selectedSell, selectedBuy, amount, provider, getSwapQuote]);
3881
+ }, [step, swapUnavailable, configLoading, selectedSell, selectedBuy, amount, provider, getSwapQuote]);
3807
3882
  const hash = transaction.step === "success" ? transaction.hash : null;
3808
3883
  const buildData = "buildData" in transaction ? transaction.buildData : null;
3809
3884
  const explorerNetwork = buildData?.summary.network?.toLowerCase().includes("testnet") ? "testnet" : buildData ? "public" : network === "testnet" ? "testnet" : "public";
@@ -3884,6 +3959,9 @@ function SwapModal({ onClose }) {
3884
3959
  formError,
3885
3960
  isLoadingData,
3886
3961
  smartUnsupported,
3962
+ configLoading,
3963
+ swapUnavailable,
3964
+ buyNeedsTrustline,
3887
3965
  transaction,
3888
3966
  showXdr,
3889
3967
  copied,
@@ -3893,6 +3971,7 @@ function SwapModal({ onClose }) {
3893
3971
  isInProgress,
3894
3972
  onClose,
3895
3973
  onBack: handleBack,
3974
+ onRefresh: handleRefresh,
3896
3975
  onSelectSell: setSelectedSell,
3897
3976
  onSelectBuy: setSelectedBuy,
3898
3977
  onAmountChange: setAmount,
@@ -4606,6 +4685,18 @@ function PollarProvider({
4606
4685
  const [pollarClient] = useState(
4607
4686
  () => client instanceof PollarClient ? client : new PollarClient({ passkey: browserPasskeyCeremony, passkeySign: browserPasskeySigner, ...client })
4608
4687
  );
4688
+ const ownsClientRef = useRef(!(client instanceof PollarClient));
4689
+ const destroyTimerRef = useRef(null);
4690
+ useEffect(() => {
4691
+ if (!ownsClientRef.current) return;
4692
+ if (destroyTimerRef.current) {
4693
+ clearTimeout(destroyTimerRef.current);
4694
+ destroyTimerRef.current = null;
4695
+ }
4696
+ return () => {
4697
+ destroyTimerRef.current = setTimeout(() => pollarClient.destroy(), 0);
4698
+ };
4699
+ }, [pollarClient]);
4609
4700
  const [networkState, setNetworkState] = useState(() => pollarClient.getNetworkState());
4610
4701
  const [sessionState, setSessionState] = useState(null);
4611
4702
  const [verified, setVerified] = useState(false);
@@ -4745,6 +4836,8 @@ function PollarProvider({
4745
4836
  openSendModal: () => setSendModalOpen(true),
4746
4837
  openReceiveModal: () => setReceiveModalOpen(true),
4747
4838
  // swap
4839
+ getSwapConfig: () => pollarClient.getSwapConfig(),
4840
+ getSwapTokens: () => pollarClient.getSwapTokens(),
4748
4841
  getSwapQuote: (params) => pollarClient.getSwapQuote(params),
4749
4842
  swap: (quote, opts) => pollarClient.swap(quote, opts),
4750
4843
  openSwapModal: () => setSwapModalOpen(true),