@pollar/react 0.10.1-rc.0 → 0.10.1-rc.2

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.1-rc.0"
1036
+ "0.10.1-rc.2"
1037
1037
  ] })
1038
1038
  ] })
1039
1039
  ] });
@@ -2457,20 +2457,45 @@ 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",
2471
2469
  completed: "Completed",
2472
2470
  failed: "Failed"
2473
2471
  };
2472
+ var INSTRUCTION_LABELS = {
2473
+ br_code: "Pix code",
2474
+ account_holder_name: "Account holder",
2475
+ bank_name: "Bank",
2476
+ bank_address: "Bank address",
2477
+ bank_account_number: "Account number",
2478
+ bank_routing_number: "Routing number",
2479
+ iban: "IBAN",
2480
+ bic: "BIC",
2481
+ clabe: "CLABE",
2482
+ amount: "Amount",
2483
+ currency: "Currency",
2484
+ payment_rails: "Rails"
2485
+ };
2486
+ function flattenInstructions(instr) {
2487
+ const out = [];
2488
+ for (const [k, v] of Object.entries(instr)) {
2489
+ let value = "";
2490
+ if (typeof v === "string" || typeof v === "number") value = String(v);
2491
+ else if (Array.isArray(v))
2492
+ value = v.filter((x) => typeof x === "string" || typeof x === "number").join(", ");
2493
+ else continue;
2494
+ if (!value) continue;
2495
+ out.push({ key: k, label: INSTRUCTION_LABELS[k] ?? k, value });
2496
+ }
2497
+ return out;
2498
+ }
2474
2499
  function RampWidgetTemplate({
2475
2500
  theme,
2476
2501
  accentColor,
@@ -2479,24 +2504,33 @@ function RampWidgetTemplate({
2479
2504
  amount,
2480
2505
  currency,
2481
2506
  country,
2507
+ email,
2508
+ fullName,
2509
+ countries,
2510
+ countriesLoading,
2511
+ refreshing,
2482
2512
  quotes,
2483
2513
  isLoading,
2484
2514
  provider,
2485
2515
  txStatus,
2486
2516
  kycUrl,
2487
2517
  stellarTxHash,
2518
+ depositInstructions,
2488
2519
  canComplete,
2489
2520
  completing,
2490
2521
  errorMsg,
2491
2522
  onDirectionChange,
2492
2523
  onAmountChange,
2493
- onCurrencyChange,
2524
+ onEmailChange,
2525
+ onFullNameChange,
2494
2526
  onCountryChange,
2495
2527
  onFindRoute,
2496
2528
  onSelectQuote,
2529
+ onContactContinue,
2497
2530
  onOpenKyc,
2498
2531
  onCompleteWithdraw,
2499
2532
  onRetry,
2533
+ onRefresh,
2500
2534
  onClose
2501
2535
  }) {
2502
2536
  const isDark = theme === "dark";
@@ -2524,6 +2558,7 @@ function RampWidgetTemplate({
2524
2558
  input: direction === "onramp" ? "Buy crypto" : "Sell crypto",
2525
2559
  loading_quote: "Finding best route",
2526
2560
  select_route: "Select provider",
2561
+ contact: "Your details",
2527
2562
  status: direction === "onramp" ? "Complete your deposit" : "Complete your withdrawal",
2528
2563
  error: "Something went wrong"
2529
2564
  };
@@ -2531,13 +2566,40 @@ function RampWidgetTemplate({
2531
2566
  input: direction === "onramp" ? "Enter the amount you want to deposit" : "Enter the amount you want to withdraw",
2532
2567
  loading_quote: "Comparing providers in real time\u2026",
2533
2568
  select_route: "All prices include fees",
2569
+ contact: `${provider || "This provider"} needs your name and email to verify you`,
2534
2570
  status: `Finish the flow at ${provider || "the provider"} to continue`,
2535
2571
  error: "Please try again"
2536
2572
  };
2537
2573
  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] })
2574
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header", children: [
2575
+ /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-header-text", children: [
2576
+ /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: stepTitle[step] }),
2577
+ /* @__PURE__ */ jsx("p", { className: "pollar-ramp-subtitle", children: stepSubtitle[step] })
2578
+ ] }),
2579
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header-actions", children: [
2580
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onRefresh, disabled: refreshing, "aria-label": "Refresh", children: /* @__PURE__ */ jsx(
2581
+ "svg",
2582
+ {
2583
+ className: refreshing ? "pollar-modal-refresh-icon spinning" : "pollar-modal-refresh-icon",
2584
+ width: "16",
2585
+ height: "16",
2586
+ viewBox: "0 0 16 16",
2587
+ fill: "none",
2588
+ "aria-hidden": true,
2589
+ children: /* @__PURE__ */ jsx(
2590
+ "path",
2591
+ {
2592
+ d: "M13.5 8a5.5 5.5 0 1 1-1.6-3.9M13.5 2v3h-3",
2593
+ stroke: "currentColor",
2594
+ strokeWidth: "1.6",
2595
+ strokeLinecap: "round",
2596
+ strokeLinejoin: "round"
2597
+ }
2598
+ )
2599
+ }
2600
+ ) }),
2601
+ /* @__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" }) }) })
2602
+ ] })
2541
2603
  ] }),
2542
2604
  step === "input" && /* @__PURE__ */ jsxs(Fragment, { children: [
2543
2605
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-tabs", children: [
@@ -2562,62 +2624,44 @@ function RampWidgetTemplate({
2562
2624
  }
2563
2625
  )
2564
2626
  ] }),
2565
- /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-input-row", children: [
2566
- /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
2567
- /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Amount" }),
2568
- /* @__PURE__ */ jsx(
2569
- "input",
2570
- {
2571
- type: "number",
2572
- className: "pollar-ramp-input",
2573
- placeholder: "0.00",
2574
- value: amount,
2575
- min: "0",
2576
- onChange: (e) => onAmountChange(e.target.value)
2577
- }
2578
- )
2579
- ] }),
2580
- /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", style: { maxWidth: 90 }, children: [
2581
- /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Currency" }),
2582
- /* @__PURE__ */ jsx(
2583
- "input",
2584
- {
2585
- type: "text",
2586
- className: "pollar-ramp-input",
2587
- placeholder: "MXN",
2588
- value: currency,
2589
- maxLength: 5,
2590
- onChange: (e) => onCurrencyChange(e.target.value.toUpperCase())
2591
- }
2592
- )
2593
- ] })
2594
- ] }),
2595
2627
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
2596
2628
  /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Country" }),
2597
- /* @__PURE__ */ jsxs(
2598
- "select",
2629
+ 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: [
2630
+ flagEmoji(c.code),
2631
+ " ",
2632
+ c.code,
2633
+ c.currency ? ` \u2014 ${c.currency}` : ""
2634
+ ] }, c.code)) })
2635
+ ] }),
2636
+ /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
2637
+ /* @__PURE__ */ jsxs("label", { className: "pollar-ramp-label", children: [
2638
+ "Amount",
2639
+ currency ? ` (${currency})` : ""
2640
+ ] }),
2641
+ /* @__PURE__ */ jsx(
2642
+ "input",
2599
2643
  {
2644
+ type: "number",
2600
2645
  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
- ]
2646
+ placeholder: "0.00",
2647
+ value: amount,
2648
+ min: "0",
2649
+ onChange: (e) => onAmountChange(e.target.value)
2615
2650
  }
2616
2651
  )
2617
2652
  ] }),
2618
2653
  /* @__PURE__ */ jsxs("div", { className: "pollar-modal-actions", children: [
2619
2654
  /* @__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" })
2655
+ /* @__PURE__ */ jsx(
2656
+ "button",
2657
+ {
2658
+ type: "button",
2659
+ className: "pollar-btn-primary",
2660
+ disabled: !amount || isLoading || countriesLoading || countries.length === 0,
2661
+ onClick: onFindRoute,
2662
+ children: "Find best route"
2663
+ }
2664
+ )
2621
2665
  ] })
2622
2666
  ] }),
2623
2667
  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: [
@@ -2628,6 +2672,48 @@ function RampWidgetTemplate({
2628
2672
  /* @__PURE__ */ jsx("div", { className: "pollar-ramp-route-list", children: quotes.map((q, i) => /* @__PURE__ */ jsx(RouteDisplay, { quote: q, onSelect: onSelectQuote }, i)) }),
2629
2673
  /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: "Cancel" })
2630
2674
  ] }),
2675
+ step === "contact" && /* @__PURE__ */ jsxs(Fragment, { children: [
2676
+ /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
2677
+ /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Full name" }),
2678
+ /* @__PURE__ */ jsx(
2679
+ "input",
2680
+ {
2681
+ type: "text",
2682
+ className: "pollar-ramp-input",
2683
+ placeholder: "Jane Doe",
2684
+ value: fullName,
2685
+ onChange: (e) => onFullNameChange(e.target.value)
2686
+ }
2687
+ )
2688
+ ] }),
2689
+ /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
2690
+ /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Email" }),
2691
+ /* @__PURE__ */ jsx(
2692
+ "input",
2693
+ {
2694
+ type: "email",
2695
+ className: "pollar-ramp-input",
2696
+ placeholder: "jane@example.com",
2697
+ value: email,
2698
+ autoComplete: "email",
2699
+ onChange: (e) => onEmailChange(e.target.value)
2700
+ }
2701
+ )
2702
+ ] }),
2703
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-actions", children: [
2704
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onRetry, children: "Back" }),
2705
+ /* @__PURE__ */ jsx(
2706
+ "button",
2707
+ {
2708
+ type: "button",
2709
+ className: "pollar-btn-primary",
2710
+ disabled: !fullName.trim() || !email.trim() || isLoading,
2711
+ onClick: onContactContinue,
2712
+ children: isLoading ? "Starting\u2026" : "Continue"
2713
+ }
2714
+ )
2715
+ ] })
2716
+ ] }),
2631
2717
  step === "status" && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment", children: [
2632
2718
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
2633
2719
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Provider" }),
@@ -2635,13 +2721,7 @@ function RampWidgetTemplate({
2635
2721
  ] }),
2636
2722
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
2637
2723
  /* @__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
- ) })
2724
+ /* @__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
2725
  ] }),
2646
2726
  stellarTxHash && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
2647
2727
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Stellar tx" }),
@@ -2651,6 +2731,10 @@ function RampWidgetTemplate({
2651
2731
  stellarTxHash.slice(-8)
2652
2732
  ] }) })
2653
2733
  ] }),
2734
+ depositInstructions && txStatus !== "completed" && flattenInstructions(depositInstructions).map(({ key, label, value }) => /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
2735
+ /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: label }),
2736
+ /* @__PURE__ */ jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsx("code", { style: { wordBreak: "break-all" }, children: value }) })
2737
+ ] }, key)),
2654
2738
  kycUrl && txStatus !== "completed" && /* @__PURE__ */ jsxs("button", { type: "button", className: "pollar-btn-primary", onClick: onOpenKyc, children: [
2655
2739
  "Continue at ",
2656
2740
  provider
@@ -2677,15 +2761,22 @@ function RampWidget({ onClose }) {
2677
2761
  const [step, setStep] = useState("input");
2678
2762
  const [direction, setDirection] = useState("onramp");
2679
2763
  const [amount, setAmount] = useState("");
2680
- const [currency, setCurrency] = useState("ARS");
2681
- const [country, setCountry] = useState("AR");
2764
+ const [currency, setCurrency] = useState("");
2765
+ const [country, setCountry] = useState("");
2766
+ const [email, setEmail] = useState("");
2767
+ const [fullName, setFullName] = useState("");
2768
+ const [countries, setCountries] = useState([]);
2769
+ const [countriesLoading, setCountriesLoading] = useState(true);
2770
+ const [refreshing, setRefreshing] = useState(false);
2682
2771
  const [quotes, setQuotes] = useState([]);
2772
+ const [selectedQuote, setSelectedQuote] = useState(null);
2683
2773
  const [isLoading, setIsLoading] = useState(false);
2684
2774
  const [txId, setTxId] = useState(null);
2685
2775
  const [provider, setProvider] = useState("");
2686
2776
  const [kycUrl, setKycUrl] = useState(null);
2687
2777
  const [txStatus, setTxStatus] = useState(null);
2688
2778
  const [stellarTxHash, setStellarTxHash] = useState(null);
2779
+ const [depositInstructions, setDepositInstructions] = useState(null);
2689
2780
  const [completing, setCompleting] = useState(false);
2690
2781
  const [errorMsg, setErrorMsg] = useState(null);
2691
2782
  const directionRef = useRef(direction);
@@ -2701,6 +2792,8 @@ function RampWidget({ onClose }) {
2701
2792
  setTxStatus(tx.status);
2702
2793
  if (tx.stellarTxHash) setStellarTxHash(tx.stellarTxHash);
2703
2794
  if (tx.kycUrl) setKycUrl(tx.kycUrl);
2795
+ const di = tx.depositInstructions;
2796
+ if (di) setDepositInstructions(di);
2704
2797
  if (TERMINAL.includes(tx.status)) clearInterval(id);
2705
2798
  } catch {
2706
2799
  }
@@ -2710,14 +2803,62 @@ function RampWidget({ onClose }) {
2710
2803
  clearInterval(id);
2711
2804
  };
2712
2805
  }, [step, txId, txStatus, client]);
2806
+ async function loadCountries(resetSelection) {
2807
+ setCountriesLoading(true);
2808
+ try {
2809
+ const { countries: list } = await client.getRampCountries();
2810
+ setCountries(list);
2811
+ const first = list[0];
2812
+ const stillValid = list.some((c) => c.code === country);
2813
+ if (first && (resetSelection || !stillValid)) {
2814
+ setCountry(first.code);
2815
+ if (first.currency) setCurrency(first.currency);
2816
+ }
2817
+ } catch {
2818
+ setCountries([]);
2819
+ } finally {
2820
+ setCountriesLoading(false);
2821
+ }
2822
+ }
2823
+ useEffect(() => {
2824
+ loadCountries(true);
2825
+ }, []);
2826
+ function handleCountryChange(code) {
2827
+ setCountry(code);
2828
+ const match = countries.find((c) => c.code === code);
2829
+ if (match?.currency) setCurrency(match.currency);
2830
+ }
2831
+ async function handleRefresh() {
2832
+ if (refreshing) return;
2833
+ setRefreshing(true);
2834
+ try {
2835
+ await loadCountries(false);
2836
+ if (step === "select_route") {
2837
+ const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
2838
+ setQuotes(result.quotes ?? []);
2839
+ } else if (step === "status" && txId) {
2840
+ const tx = await client.getRampTransaction(txId);
2841
+ setTxStatus(tx.status);
2842
+ if (tx.stellarTxHash) setStellarTxHash(tx.stellarTxHash);
2843
+ if (tx.kycUrl) setKycUrl(tx.kycUrl);
2844
+ const di = tx.depositInstructions;
2845
+ if (di) setDepositInstructions(di);
2846
+ }
2847
+ } catch {
2848
+ } finally {
2849
+ setRefreshing(false);
2850
+ }
2851
+ }
2713
2852
  function resetToInput() {
2714
2853
  setStep("input");
2715
2854
  setQuotes([]);
2855
+ setSelectedQuote(null);
2716
2856
  setTxId(null);
2717
2857
  setProvider("");
2718
2858
  setKycUrl(null);
2719
2859
  setTxStatus(null);
2720
2860
  setStellarTxHash(null);
2861
+ setDepositInstructions(null);
2721
2862
  setErrorMsg(null);
2722
2863
  }
2723
2864
  async function resumeWithSignature(id, ps) {
@@ -2743,6 +2884,7 @@ function RampWidget({ onClose }) {
2743
2884
  setKycUrl(result.kycUrl ?? null);
2744
2885
  setTxStatus(result.status);
2745
2886
  setStellarTxHash(result.stellarTxHash ?? null);
2887
+ setDepositInstructions(result.depositInstructions ?? null);
2746
2888
  setStep("status");
2747
2889
  }
2748
2890
  async function handleFindRoute() {
@@ -2766,12 +2908,33 @@ function RampWidget({ onClose }) {
2766
2908
  setIsLoading(false);
2767
2909
  }
2768
2910
  }
2769
- async function handleSelectQuote(quote) {
2911
+ function handleSelectQuote(quote) {
2912
+ setSelectedQuote(quote);
2913
+ setErrorMsg(null);
2914
+ const requiresContact = quote.requiresContact ?? false;
2915
+ if (requiresContact && (!email.trim() || !fullName.trim())) {
2916
+ setStep("contact");
2917
+ return;
2918
+ }
2919
+ void startRamp(quote);
2920
+ }
2921
+ function handleContactContinue() {
2922
+ if (selectedQuote) void startRamp(selectedQuote);
2923
+ }
2924
+ async function startRamp(quote) {
2770
2925
  setIsLoading(true);
2771
2926
  setErrorMsg(null);
2772
2927
  try {
2773
- const base = { quoteId: quote.quoteId, amount: Number(amount), currency, country };
2774
- const result = direction === "onramp" ? await client.createOnRamp({ ...base, ...walletAddress ? { walletAddress } : {} }) : await client.createOffRamp({ ...base, ...walletAddress ? { walletAddress } : {} });
2928
+ const base = {
2929
+ quoteId: quote.quoteId,
2930
+ amount: Number(amount),
2931
+ currency,
2932
+ country,
2933
+ ...email.trim() ? { email: email.trim() } : {},
2934
+ ...fullName.trim() ? { fullName: fullName.trim() } : {},
2935
+ ...walletAddress ? { walletAddress } : {}
2936
+ };
2937
+ const result = direction === "onramp" ? await client.createOnRamp(base) : await client.createOffRamp(base);
2775
2938
  await applyResult(result);
2776
2939
  } catch (e) {
2777
2940
  setErrorMsg(e instanceof Error ? e.message : "Failed to start the ramp.");
@@ -2815,24 +2978,33 @@ function RampWidget({ onClose }) {
2815
2978
  amount,
2816
2979
  currency,
2817
2980
  country,
2981
+ email,
2982
+ fullName,
2983
+ countries,
2984
+ countriesLoading,
2985
+ refreshing,
2818
2986
  quotes,
2819
2987
  isLoading,
2820
2988
  provider,
2821
2989
  txStatus,
2822
2990
  kycUrl,
2823
2991
  stellarTxHash,
2992
+ depositInstructions,
2824
2993
  canComplete,
2825
2994
  completing,
2826
2995
  errorMsg,
2827
2996
  onDirectionChange: setDirection,
2828
2997
  onAmountChange: setAmount,
2829
- onCurrencyChange: setCurrency,
2830
- onCountryChange: setCountry,
2998
+ onEmailChange: setEmail,
2999
+ onFullNameChange: setFullName,
3000
+ onCountryChange: handleCountryChange,
2831
3001
  onFindRoute: handleFindRoute,
2832
3002
  onSelectQuote: handleSelectQuote,
3003
+ onContactContinue: handleContactContinue,
2833
3004
  onOpenKyc: handleOpenKyc,
2834
3005
  onCompleteWithdraw: handleCompleteWithdraw,
2835
3006
  onRetry: resetToInput,
3007
+ onRefresh: handleRefresh,
2836
3008
  onClose
2837
3009
  }
2838
3010
  ) });
@@ -3542,6 +3714,7 @@ function SwapModalTemplate({
3542
3714
  onRefresh,
3543
3715
  onSelectSell,
3544
3716
  onSelectBuy,
3717
+ onAddCustomToken,
3545
3718
  onAmountChange,
3546
3719
  onProviderChange,
3547
3720
  onSwap,
@@ -3551,6 +3724,21 @@ function SwapModalTemplate({
3551
3724
  onDone
3552
3725
  }) {
3553
3726
  const isDark = theme === "dark";
3727
+ const [customOpen, setCustomOpen] = useState(false);
3728
+ const [customCode, setCustomCode] = useState("");
3729
+ const [customIssuer, setCustomIssuer] = useState("");
3730
+ const [customError, setCustomError] = useState("");
3731
+ const submitCustomToken = () => {
3732
+ const err = onAddCustomToken(customCode, customIssuer);
3733
+ if (err) {
3734
+ setCustomError(err);
3735
+ return;
3736
+ }
3737
+ setCustomCode("");
3738
+ setCustomIssuer("");
3739
+ setCustomError("");
3740
+ setCustomOpen(false);
3741
+ };
3554
3742
  const cssVars = {
3555
3743
  "--pollar-accent": accentColor,
3556
3744
  "--pollar-bg": isDark ? "#1a1a1a" : "#ffffff",
@@ -3669,7 +3857,45 @@ function SwapModalTemplate({
3669
3857
  ] }, assetOptionKey(o)))
3670
3858
  ]
3671
3859
  }
3672
- )
3860
+ ),
3861
+ !isLoadingData && (customOpen ? /* @__PURE__ */ jsxs("div", { className: "pollar-swap-custom", children: [
3862
+ /* @__PURE__ */ jsx(
3863
+ "input",
3864
+ {
3865
+ className: "pollar-input",
3866
+ type: "text",
3867
+ placeholder: "Code (e.g. USDC)",
3868
+ value: customCode,
3869
+ onChange: (e) => setCustomCode(e.target.value)
3870
+ }
3871
+ ),
3872
+ /* @__PURE__ */ jsx(
3873
+ "input",
3874
+ {
3875
+ className: "pollar-input",
3876
+ type: "text",
3877
+ placeholder: "Issuer (G...)",
3878
+ value: customIssuer,
3879
+ onChange: (e) => setCustomIssuer(e.target.value)
3880
+ }
3881
+ ),
3882
+ customError && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: customError }),
3883
+ /* @__PURE__ */ jsxs("div", { className: "pollar-swap-custom-actions", children: [
3884
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-primary", onClick: submitCustomToken, children: "Add token" }),
3885
+ /* @__PURE__ */ jsx(
3886
+ "button",
3887
+ {
3888
+ type: "button",
3889
+ className: "pollar-swap-custom-cancel",
3890
+ onClick: () => {
3891
+ setCustomOpen(false);
3892
+ setCustomError("");
3893
+ },
3894
+ children: "Cancel"
3895
+ }
3896
+ )
3897
+ ] })
3898
+ ] }) : /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-swap-custom-toggle", onClick: () => setCustomOpen(true), children: "+ Add a token by code / issuer" }))
3673
3899
  ] }),
3674
3900
  /* @__PURE__ */ jsxs("div", { className: "pollar-send-field", children: [
3675
3901
  /* @__PURE__ */ jsx("label", { className: "pollar-send-label", children: "Route" }),
@@ -3777,6 +4003,7 @@ function SwapModal({ onClose }) {
3777
4003
  const [provider, setProvider] = useState("auto");
3778
4004
  const [venues, setVenues] = useState(null);
3779
4005
  const [catalogTokens, setCatalogTokens] = useState([]);
4006
+ const [customTokens, setCustomTokens] = useState([]);
3780
4007
  const [quotes, setQuotes] = useState([]);
3781
4008
  const [quoteLoading, setQuoteLoading] = useState(false);
3782
4009
  const [quoteError, setQuoteError] = useState("");
@@ -3834,7 +4061,21 @@ function SwapModal({ onClose }) {
3834
4061
  issuer: tk.issuer,
3835
4062
  enabledInApp: false
3836
4063
  })).filter((o) => !enabledKeys.has(optKey(o)));
3837
- const buyOptions = [...enabledBuy, ...catalogBuy].filter((o) => optKey(o) !== buyKeyOfSell);
4064
+ const knownKeys = /* @__PURE__ */ new Set([...enabledKeys, ...catalogBuy.map(optKey)]);
4065
+ const customBuy = customTokens.filter((o) => !knownKeys.has(optKey(o)));
4066
+ const buyOptions = [...enabledBuy, ...catalogBuy, ...customBuy].filter(
4067
+ (o) => optKey(o) !== buyKeyOfSell
4068
+ );
4069
+ function addCustomToken(code, issuer) {
4070
+ const c = code.trim().toUpperCase();
4071
+ const i = issuer.trim();
4072
+ if (c.length < 1 || c.length > 12) return "Code must be 1-12 characters";
4073
+ if (i.length !== 56 || !i.startsWith("G")) return "Issuer must be a Stellar address (G..., 56 chars)";
4074
+ const opt = { ref: catalogRef(c, i), code: c, issuer: i, enabledInApp: false };
4075
+ setCustomTokens((prev) => prev.some((o) => optKey(o) === optKey(opt)) ? prev : [...prev, opt]);
4076
+ setSelectedBuy(opt);
4077
+ return null;
4078
+ }
3838
4079
  const buyNeedsTrustline = (() => {
3839
4080
  if (!selectedBuy || smartUnsupported) return false;
3840
4081
  if (selectedBuy.ref.type === "native") return false;
@@ -3974,6 +4215,7 @@ function SwapModal({ onClose }) {
3974
4215
  onRefresh: handleRefresh,
3975
4216
  onSelectSell: setSelectedSell,
3976
4217
  onSelectBuy: setSelectedBuy,
4218
+ onAddCustomToken: addCustomToken,
3977
4219
  onAmountChange: setAmount,
3978
4220
  onProviderChange: setProvider,
3979
4221
  onSwap: () => void handleSwap(),
@@ -4411,6 +4653,10 @@ function TxHistoryModalTemplate({
4411
4653
  /* @__PURE__ */ jsx(StatusBadge, { status: record.status }),
4412
4654
  /* @__PURE__ */ jsxs("span", { className: "pollar-hist-item-meta", children: [
4413
4655
  /* @__PURE__ */ jsx("span", { children: record.operation }),
4656
+ typeof record.details?.sponsored === "boolean" && /* @__PURE__ */ jsxs("span", { children: [
4657
+ "\xB7 ",
4658
+ record.details.sponsored ? "Sponsored" : "Self-paid"
4659
+ ] }),
4414
4660
  record.feeXlm && /* @__PURE__ */ jsxs("span", { children: [
4415
4661
  "\xB7 ",
4416
4662
  record.feeXlm,