@pollar/react 0.11.4 → 0.12.0-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
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { AUTH_ERROR_CODES, WalletType, isPollarClient, PollarClient, isInteractiveAuthAdapter, isPollarApiError, toBaseUnits } from '@pollar/core';
2
+ import { AUTH_ERROR_CODES, describeRampAction, mergeRampCountries, transactionErrorMessage, WalletType, isPollarApiError, isPollarClient, PollarClient, isInteractiveAuthAdapter, mergeRampSnapshot, toBaseUnits, fromBaseUnits } from '@pollar/core';
3
3
  import { forwardRef, createContext, useState, useCallback, useEffect, useMemo, useRef, useContext, Component } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
  import { startAuthentication, startRegistration } from '@simplewebauthn/browser';
@@ -1000,6 +1000,10 @@ var require_QRCode = __commonJS({
1000
1000
 
1001
1001
  // src/constants.ts
1002
1002
  var LOGO_POLLAR = "https://pollar.xyz/assets/logo_pollar.png";
1003
+ var LOGO_POLLAR_WORDMARK_ON_LIGHT = "https://www.pollar.xyz/brandkit/pollar-logo-horizontal-on-light.png";
1004
+ var LOGO_POLLAR_WORDMARK_ON_DARK = "https://www.pollar.xyz/brandkit/pollar-logo-horizontal-on-dark.png";
1005
+ var LOGO_POLLAR_MARK_ON_LIGHT = "https://www.pollar.xyz/brandkit/pollar-mark-on-light.png";
1006
+ var LOGO_POLLAR_MARK_ON_DARK = "https://www.pollar.xyz/brandkit/pollar-mark-on-dark.png";
1003
1007
  var LOGO_FREIGHTER = "https://pollar.xyz/assets/logo_freighter.png";
1004
1008
  var LOGO_ALBEDO = "https://pollar.xyz/assets/logo_albedo.svg";
1005
1009
  var _modalLog = console;
@@ -1025,22 +1029,26 @@ var ModalErrorBoundary = class extends Component {
1025
1029
  return this.props.children;
1026
1030
  }
1027
1031
  };
1028
- var PollarModalFooter = ({ shield = false }) => {
1032
+ var PollarModalFooter = () => {
1029
1033
  return /* @__PURE__ */ jsxs("div", { className: "pollar-footer", children: [
1030
1034
  /* @__PURE__ */ jsx("span", { className: "pollar-footer-protected", children: "Protected by" }),
1031
- /* @__PURE__ */ jsxs("div", { className: "pollar-footer-brand", children: [
1032
- shield ? /* @__PURE__ */ jsxs("svg", { className: "pollar-footer-shield", width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", "aria-hidden": true, children: [
1033
- /* @__PURE__ */ jsx("path", { d: "M12 3.2 19 6v5.3c0 4.4-2.9 7.8-7 9.5-4.1-1.7-7-5.1-7-9.5V6l7-2.8Z", fill: "var(--pollar-accent)" }),
1034
- /* @__PURE__ */ jsx("path", { d: "m8.5 12 2.2 2.2 4.8-5", stroke: "white", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" })
1035
- ] }) : /* @__PURE__ */ jsx("img", { src: LOGO_POLLAR, alt: "Pollar", className: "pollar-footer-logo" }),
1036
- /* @__PURE__ */ jsx("span", { className: "pollar-footer-name", children: "Pollar" }),
1037
- /* @__PURE__ */ jsxs("span", { className: "pollar-footer-version", children: [
1038
- "v",
1039
- "0.11.4"
1040
- ] })
1035
+ /* @__PURE__ */ jsx("img", { src: LOGO_POLLAR_WORDMARK_ON_LIGHT, alt: "Pollar", className: "pollar-footer-logo pollar-logo-on-light" }),
1036
+ /* @__PURE__ */ jsx("img", { src: LOGO_POLLAR_WORDMARK_ON_DARK, alt: "Pollar", className: "pollar-footer-logo pollar-logo-on-dark" }),
1037
+ /* @__PURE__ */ jsxs("span", { className: "pollar-footer-version", children: [
1038
+ "v",
1039
+ "0.12.0-rc.2"
1041
1040
  ] })
1042
1041
  ] });
1043
1042
  };
1043
+ function explorerTxUrl(chain, hash, network) {
1044
+ if (chain === "SOLANA") {
1045
+ return `https://explorer.solana.com/tx/${hash}${network === "testnet" ? "?cluster=devnet" : ""}`;
1046
+ }
1047
+ if (chain === "POLYGON") {
1048
+ return `https://${network === "testnet" ? "amoy." : ""}polygonscan.com/tx/${hash}`;
1049
+ }
1050
+ return `https://stellar.expert/explorer/${network === "testnet" ? "testnet" : "public"}/tx/${hash}`;
1051
+ }
1044
1052
  function RefreshIcon({ spinning = false, className = "" }) {
1045
1053
  return /* @__PURE__ */ jsxs(
1046
1054
  "svg",
@@ -1232,6 +1240,8 @@ function buildModalCssVars(theme, accentColor, overrides = {}, variant = "compac
1232
1240
  "--pollar-error-border": isDark ? "#7f1d1d" : "#fecaca",
1233
1241
  "--pollar-error-text": isDark ? "#f87171" : "#dc2626",
1234
1242
  "--pollar-success-text": isDark ? "#4ade80" : "#16a34a",
1243
+ "--pollar-logo-on-light-display": isDark ? "none" : "block",
1244
+ "--pollar-logo-on-dark-display": isDark ? "block" : "none",
1235
1245
  "--pollar-modal-border-radius": px(overrides.modalBorderRadius, DEFAULT_MODAL_BORDER_RADIUS),
1236
1246
  "--pollar-buttons-border-radius": px(overrides.buttonBorderRadius, DEFAULT_BUTTON_BORDER_RADIUS),
1237
1247
  "--pollar-buttons-height": "44px",
@@ -1903,7 +1913,7 @@ function KycStatus({ status, label, className }) {
1903
1913
 
1904
1914
  // src/components/kyc-modal/kyc-messages.ts
1905
1915
  function kycErrorMessage(error, phase) {
1906
- const code = error?.code;
1916
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
1907
1917
  switch (code) {
1908
1918
  case "SDK_KYC_NOT_CONFIGURED":
1909
1919
  return "Identity verification is not available for this app yet.";
@@ -2148,7 +2158,13 @@ function KycModal({
2148
2158
  finish("approved");
2149
2159
  return;
2150
2160
  }
2151
- setSession(result);
2161
+ const { sessionId } = result;
2162
+ if (sessionId === void 0) throw new Error("The KYC provider answered without a session.");
2163
+ setSession({
2164
+ sessionId,
2165
+ ...result.kycUrl !== void 0 ? { kycUrl: result.kycUrl } : {},
2166
+ ...result.fields !== void 0 ? { fields: result.fields } : {}
2167
+ });
2152
2168
  setStep("verifying");
2153
2169
  } catch (e) {
2154
2170
  const code = isPollarApiError(e) ? e.code : void 0;
@@ -2335,10 +2351,14 @@ function fieldErrorMessage(code, language) {
2335
2351
  }
2336
2352
  function fieldErrorsOf(error) {
2337
2353
  if (!error || typeof error !== "object") return null;
2338
- const { code, body } = error;
2339
- if (code !== "KYC_FORM_INVALID_ANSWERS" || !Array.isArray(body?.errors)) return null;
2354
+ if (!("code" in error) || error.code !== "KYC_FORM_INVALID_ANSWERS") return null;
2355
+ const body = "body" in error ? error.body : void 0;
2356
+ const list = typeof body === "object" && body !== null && "errors" in body ? body.errors : void 0;
2357
+ if (!Array.isArray(list)) return null;
2358
+ const items = list;
2340
2359
  const errors = {};
2341
- for (const item of body.errors) {
2360
+ for (const item of items) {
2361
+ if (typeof item !== "object" || item === null || !("key" in item) || !("code" in item)) continue;
2342
2362
  if (typeof item.key === "string" && typeof item.code === "string") errors[item.key] = item.code;
2343
2363
  }
2344
2364
  return Object.keys(errors).length ? errors : null;
@@ -3237,7 +3257,11 @@ function LoginModal({ onClose }) {
3237
3257
  const [email, setEmail] = useState("");
3238
3258
  const { getClient, styles, appConfig: config, configStatus, retryConfig } = usePollar();
3239
3259
  const [authState, setAuthState] = useState(() => getClient().getAuthState());
3240
- const walletAdapters = useMemo(() => getClient().listWalletAdapters(), [getClient]);
3260
+ const servedChains = config?.application?.chains;
3261
+ const walletAdapters = useMemo(
3262
+ () => getClient().listWalletAdapters().filter((a) => !servedChains || servedChains.includes(a.chain)),
3263
+ [getClient, servedChains]
3264
+ );
3241
3265
  const [codeInputKey, setCodeInputKey] = useState(0);
3242
3266
  const pendingEmail = useRef(null);
3243
3267
  const [interactiveAdapter, setInteractiveAdapter] = useState(null);
@@ -3384,13 +3408,200 @@ function LoginModal({ onClose }) {
3384
3408
  ) });
3385
3409
  }
3386
3410
 
3411
+ // src/lib/qr-code/index.tsx
3412
+ var import_ErrorCorrectLevel = __toESM(require_ErrorCorrectLevel());
3413
+ var import_QRCode = __toESM(require_QRCode());
3414
+ var QRCodeSvg = forwardRef(function QRCodeSvg2({ bgColor, bgD, fgD, fgColor, size, title, viewBoxSize, xmlns = "http://www.w3.org/2000/svg", ...props }, ref) {
3415
+ return /* @__PURE__ */ jsxs("svg", { ...props, height: size, ref, viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`, width: size, xmlns, children: [
3416
+ title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
3417
+ /* @__PURE__ */ jsx("path", { d: bgD, fill: bgColor }),
3418
+ /* @__PURE__ */ jsx("path", { d: fgD, fill: fgColor })
3419
+ ] });
3420
+ });
3421
+ QRCodeSvg.displayName = "QRCodeSvg";
3422
+ function bytesToBinaryString(bytes) {
3423
+ return bytes.map((b) => String.fromCharCode(b & 255)).join("");
3424
+ }
3425
+ function encodeStringToUtf8Bytes(input) {
3426
+ return Array.from(new TextEncoder().encode(input));
3427
+ }
3428
+ var QRCode = forwardRef(function QRCode2({ bgColor = "#FFFFFF", fgColor = "#000000", level = "L", size = 256, value, ...props }, ref) {
3429
+ const qrcode = new import_QRCode.default(-1, import_ErrorCorrectLevel.default[level]);
3430
+ const utf8Bytes = encodeStringToUtf8Bytes(value);
3431
+ const binaryString = bytesToBinaryString(utf8Bytes);
3432
+ qrcode.addData(binaryString, "Byte");
3433
+ qrcode.make();
3434
+ const cells = qrcode.modules;
3435
+ return /* @__PURE__ */ jsx(
3436
+ QRCodeSvg,
3437
+ {
3438
+ ...props,
3439
+ bgColor,
3440
+ bgD: cells.map(
3441
+ (row, rowIndex) => row.map((cell, cellIndex) => !cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
3442
+ ).join(" "),
3443
+ fgColor,
3444
+ fgD: cells.map(
3445
+ (row, rowIndex) => row.map((cell, cellIndex) => cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
3446
+ ).join(" "),
3447
+ ref,
3448
+ size,
3449
+ viewBoxSize: cells.length
3450
+ }
3451
+ );
3452
+ });
3453
+ function RampWorkflow({
3454
+ client,
3455
+ snapshot,
3456
+ onChange,
3457
+ onBusyChange
3458
+ }) {
3459
+ const model = describeRampAction(snapshot);
3460
+ const locked = useRef(false);
3461
+ useEffect(() => {
3462
+ setFields({});
3463
+ setError(null);
3464
+ }, [model.action?.actionId]);
3465
+ const [busy, setBusy] = useState(false);
3466
+ const [error, setError] = useState(null);
3467
+ const [fields, setFields] = useState({});
3468
+ const fieldsComplete = model.action?.kind !== "collect_information" || model.action.fields.every((field) => {
3469
+ const value = (fields[field.key] ?? "").trim();
3470
+ if (!value) return field.optional;
3471
+ if (field.type === "email") return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
3472
+ return field.type !== "select" || field.options?.some((option) => option.value === value);
3473
+ });
3474
+ async function continueAction() {
3475
+ if (!model.action || locked.current || !fieldsComplete) return;
3476
+ locked.current = true;
3477
+ setBusy(true);
3478
+ onBusyChange?.(true);
3479
+ setError(null);
3480
+ try {
3481
+ onChange(
3482
+ model.canSign ? await client.signRampAction(snapshot.txId, snapshot) : await client.continueRamp(snapshot.txId, {
3483
+ actionId: model.action.actionId,
3484
+ transactionVersion: snapshot.transactionVersion,
3485
+ ...model.action.kind === "collect_information" ? { fields } : {}
3486
+ })
3487
+ );
3488
+ } catch (e) {
3489
+ setError(e instanceof Error ? e.message : "Unable to continue.");
3490
+ } finally {
3491
+ locked.current = false;
3492
+ setBusy(false);
3493
+ onBusyChange?.(false);
3494
+ }
3495
+ }
3496
+ return /* @__PURE__ */ jsxs(
3497
+ "section",
3498
+ {
3499
+ className: "pollar-ramp-payment pollar-ramp-workflow",
3500
+ "aria-label": "Ramp progress",
3501
+ "aria-live": "polite",
3502
+ "aria-busy": busy,
3503
+ children: [
3504
+ /* @__PURE__ */ jsx("h3", { className: "pollar-ramp-payment-title", children: model.title }),
3505
+ snapshot.terms && /* @__PURE__ */ jsxs("p", { className: "pollar-ramp-terms", children: [
3506
+ snapshot.terms.fiatAmount,
3507
+ " ",
3508
+ snapshot.terms.fiatCurrency,
3509
+ " \xB7 ",
3510
+ snapshot.terms.cryptoAmount,
3511
+ " ",
3512
+ snapshot.terms.assetCode,
3513
+ /* @__PURE__ */ jsx("br", {}),
3514
+ "Includes ",
3515
+ snapshot.terms.feeAmount,
3516
+ " ",
3517
+ snapshot.terms.feeCurrency,
3518
+ " fee"
3519
+ ] }),
3520
+ model.details.map((item, index) => /* @__PURE__ */ jsxs("p", { className: "pollar-ramp-workflow-detail", children: [
3521
+ /* @__PURE__ */ jsxs("strong", { children: [
3522
+ item.label,
3523
+ ": "
3524
+ ] }),
3525
+ item.value
3526
+ ] }, index)),
3527
+ model.links.map((link, index) => /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", { className: "pollar-btn-primary pollar-ramp-hosted-link", href: link.url, target: "_blank", rel: "noopener noreferrer", children: link.label }) }, index)),
3528
+ model.qr && /* @__PURE__ */ jsx(QRCode, { value: model.qr, size: 180 }),
3529
+ model.qr && /* @__PURE__ */ jsx(
3530
+ "button",
3531
+ {
3532
+ className: "pollar-btn-secondary",
3533
+ type: "button",
3534
+ onClick: () => void navigator.clipboard.writeText(model.qr).catch(() => setError("Unable to copy. Select the payment code to copy it manually.")),
3535
+ children: "Copy payment code"
3536
+ }
3537
+ ),
3538
+ model.action?.kind === "collect_information" && model.action.fields.map((field) => /* @__PURE__ */ jsxs("label", { className: "pollar-ramp-field", children: [
3539
+ /* @__PURE__ */ jsxs("span", { className: "pollar-ramp-label", children: [
3540
+ field.label,
3541
+ field.optional ? " (optional)" : ""
3542
+ ] }),
3543
+ field.type === "select" ? /* @__PURE__ */ jsxs(
3544
+ "select",
3545
+ {
3546
+ className: "pollar-ramp-input",
3547
+ disabled: busy,
3548
+ value: fields[field.key] ?? "",
3549
+ onChange: (e) => setFields((previous) => ({ ...previous, [field.key]: e.target.value })),
3550
+ children: [
3551
+ /* @__PURE__ */ jsx("option", { value: "", children: "Choose" }),
3552
+ field.options?.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
3553
+ ]
3554
+ }
3555
+ ) : /* @__PURE__ */ jsx(
3556
+ "input",
3557
+ {
3558
+ className: "pollar-ramp-input",
3559
+ disabled: busy,
3560
+ type: field.type,
3561
+ placeholder: field.placeholder,
3562
+ value: fields[field.key] ?? "",
3563
+ onChange: (e) => setFields((previous) => ({ ...previous, [field.key]: e.target.value }))
3564
+ }
3565
+ ),
3566
+ field.hint && /* @__PURE__ */ jsx("small", { className: "pollar-ramp-field-hint", children: field.hint })
3567
+ ] }, field.key)),
3568
+ (model.canContinue || model.canSign) && /* @__PURE__ */ jsx(
3569
+ "button",
3570
+ {
3571
+ className: "pollar-btn-primary",
3572
+ type: "button",
3573
+ disabled: busy || !fieldsComplete,
3574
+ onClick: () => void continueAction(),
3575
+ children: busy ? model.canSign ? "Waiting for your wallet\u2026" : "Continuing\u2026" : model.canSign ? "Authorize" : "Continue"
3576
+ }
3577
+ ),
3578
+ snapshot.milestones?.map((milestone, index) => /* @__PURE__ */ jsxs("p", { children: [
3579
+ milestone.kind.replace(/_/g, " "),
3580
+ " \xB7 ",
3581
+ milestone.verifiedAt
3582
+ ] }, index)),
3583
+ error && /* @__PURE__ */ jsx("p", { className: "pollar-ramp-field-error", role: "alert", children: error }),
3584
+ /* @__PURE__ */ jsxs("small", { className: "pollar-ramp-reference", children: [
3585
+ "Reference: ",
3586
+ snapshot.txId
3587
+ ] })
3588
+ ]
3589
+ }
3590
+ );
3591
+ }
3592
+
3387
3593
  // src/components/ramp-widget/ramp-kyc.ts
3388
3594
  var nonEmpty = (value) => typeof value === "string" && !!value.trim();
3389
3595
  function requiredRampKyc(error) {
3390
3596
  if (!error || typeof error !== "object") return null;
3391
- const { code, body } = error;
3392
- if (code !== "SDK_RAMPS_KYC_REQUIRED" || !body || typeof body !== "object") return null;
3393
- const { rampProviderId, corridorId, requirementType, optionId, kycProviderId } = body;
3597
+ if (!("code" in error) || error.code !== "SDK_RAMPS_KYC_REQUIRED") return null;
3598
+ const body = "body" in error ? error.body : void 0;
3599
+ if (typeof body !== "object" || body === null) return null;
3600
+ const rampProviderId = "rampProviderId" in body ? body.rampProviderId : void 0;
3601
+ const corridorId = "corridorId" in body ? body.corridorId : void 0;
3602
+ const requirementType = "requirementType" in body ? body.requirementType : void 0;
3603
+ const optionId = "optionId" in body ? body.optionId : void 0;
3604
+ const kycProviderId = "kycProviderId" in body ? body.kycProviderId : void 0;
3394
3605
  if (!nonEmpty(rampProviderId) || !nonEmpty(corridorId)) return null;
3395
3606
  if ((requirementType === "FORM" || requirementType === "REGISTRY_CHECK" || requirementType === "PROVIDER_REGISTRATION") && nonEmpty(optionId)) {
3396
3607
  return { rampProviderId, corridorId, type: requirementType, optionId };
@@ -3457,13 +3668,14 @@ function RouteDisplay({ quote, busy = false, disabled = false, onSelect }) {
3457
3668
  children: [
3458
3669
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-route-left", children: [
3459
3670
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-provider", children: quote.provider }),
3460
- /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-meta", children: busy ? "Starting\u2026" : `${RAIL_LABELS[quote.rail] ?? quote.rail} \xB7 ${quote.protocol} \xB7 ${quote.estimatedTime}` })
3671
+ /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-meta", children: busy ? "Starting\u2026" : [
3672
+ quote.route ? `${quote.route.asset.code} on ${quote.route.asset.chain}` : null,
3673
+ RAIL_LABELS[quote.rail] ?? quote.rail,
3674
+ quote.estimatedTime
3675
+ ].filter(Boolean).join(" \xB7 ") })
3461
3676
  ] }),
3462
3677
  /* @__PURE__ */ jsx("div", { className: "pollar-ramp-route-right", children: busy ? /* @__PURE__ */ jsx("span", { className: "pollar-spinner pollar-spinner-sm" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3463
- /* @__PURE__ */ jsxs("span", { className: "pollar-ramp-route-fee", children: [
3464
- quote.fee,
3465
- "% fee"
3466
- ] }),
3678
+ /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-fee", children: quote.terms ? `${quote.terms.fiatAmount} ${quote.terms.fiatCurrency} \xB7 ${quote.terms.cryptoAmount} ${quote.terms.assetCode} \xB7 Includes ${quote.terms.feeAmount} ${quote.terms.feeCurrency}` : `${quote.fee}% fee` }),
3467
3679
  quote.recommended && /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-badge", children: "Best rate" })
3468
3680
  ] }) })
3469
3681
  ]
@@ -3522,6 +3734,8 @@ function placeholderFor(field, fields, values) {
3522
3734
  }
3523
3735
  return field.placeholder;
3524
3736
  }
3737
+ var CHAIN_NAME = { STELLAR: "Stellar", POLYGON: "Polygon", SOLANA: "Solana" };
3738
+ var EXPLORER_NAME = { STELLAR: "Stellar Expert", POLYGON: "Polygonscan", SOLANA: "Solana Explorer" };
3525
3739
  var LOADING_STEPS = ["Detecting your country\u2026", "Consulting providers\u2026", "Route found!"];
3526
3740
  function flagEmoji(code) {
3527
3741
  const cc = code.trim().toUpperCase();
@@ -3541,6 +3755,8 @@ function displayValue(field) {
3541
3755
  return Number.isNaN(at.getTime()) ? field.value : at.toLocaleString();
3542
3756
  }
3543
3757
  function RampWidgetTemplate({
3758
+ workflowContent,
3759
+ routeSelector,
3544
3760
  theme,
3545
3761
  accentColor,
3546
3762
  styleOverrides,
@@ -3568,10 +3784,20 @@ function RampWidgetTemplate({
3568
3784
  onboardingStatus,
3569
3785
  kycJustApproved,
3570
3786
  stellarTxHash,
3787
+ txHash,
3788
+ txChain,
3571
3789
  explorerUrl,
3790
+ fundsInTransit = false,
3791
+ retrying = false,
3792
+ onRetryTransit,
3572
3793
  depositInstructions,
3573
3794
  canComplete,
3574
3795
  completing,
3796
+ savedRamps = [],
3797
+ onResumeRamp,
3798
+ onDismissRamp,
3799
+ onDismissCurrentRamp,
3800
+ onExploreRoutes,
3575
3801
  errorMsg,
3576
3802
  noticeMsg,
3577
3803
  onDirectionChange,
@@ -3590,6 +3816,8 @@ function RampWidgetTemplate({
3590
3816
  onRefresh,
3591
3817
  onClose
3592
3818
  }) {
3819
+ const settledHash = txHash ?? stellarTxHash;
3820
+ const chainName = CHAIN_NAME[txChain ?? "STELLAR"];
3593
3821
  const cssVars = buildModalCssVars(theme, accentColor, styleOverrides, "hero");
3594
3822
  const stepTitle = {
3595
3823
  input: direction === "onramp" ? "Buy crypto" : "Sell crypto",
@@ -3608,7 +3836,7 @@ function RampWidgetTemplate({
3608
3836
  // money, not who you are. Say which, rather than asserting the Bridge case
3609
3837
  // over every provider.
3610
3838
  contact: `${provider || "This provider"} needs ${listOf(requiredFields.filter((f) => !f.optional).map((f) => f.label.toLowerCase()))} to continue`,
3611
- status: `Finish the flow at ${provider || "the provider"} to continue`,
3839
+ status: workflowContent ? "Follow the next step below to continue" : `Finish the flow at ${provider || "the provider"} to continue`,
3612
3840
  error: "Please try again"
3613
3841
  };
3614
3842
  return /* @__PURE__ */ jsxs("div", { className: "pollar-modal-card pollar-ramp-modal", style: cssVars, onClick: (e) => e.stopPropagation(), children: [
@@ -3662,7 +3890,39 @@ function RampWidgetTemplate({
3662
3890
  }
3663
3891
  )
3664
3892
  ] }),
3893
+ step === "input" && savedRamps.length > 0 && /* @__PURE__ */ jsxs("section", { className: "pollar-ramp-payment pollar-ramp-saved", "aria-label": "Pending operations", children: [
3894
+ /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", children: "Your pending operations are saved. Starting another route does not cancel them." }),
3895
+ savedRamps.map((ramp) => /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-saved-row", children: [
3896
+ /* @__PURE__ */ jsxs(
3897
+ "button",
3898
+ {
3899
+ type: "button",
3900
+ className: "pollar-btn-secondary",
3901
+ disabled: isLoading,
3902
+ onClick: () => onResumeRamp?.(ramp.txId),
3903
+ children: [
3904
+ "Resume ",
3905
+ ramp.provider,
3906
+ " \xB7 ",
3907
+ ramp.txId.slice(-8)
3908
+ ]
3909
+ }
3910
+ ),
3911
+ onDismissRamp && /* @__PURE__ */ jsx(
3912
+ "button",
3913
+ {
3914
+ type: "button",
3915
+ className: "pollar-btn-secondary",
3916
+ disabled: isLoading,
3917
+ "aria-label": `Remove ${ramp.provider} ${ramp.txId.slice(-8)} from pending list`,
3918
+ onClick: () => onDismissRamp(ramp.txId),
3919
+ children: "Remove"
3920
+ }
3921
+ )
3922
+ ] }, ramp.txId))
3923
+ ] }),
3665
3924
  step === "input" && /* @__PURE__ */ jsxs(Fragment, { children: [
3925
+ noticeMsg && /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", role: "status", children: noticeMsg }),
3666
3926
  /* @__PURE__ */ jsxs("div", { className: "pollar-tabs", children: [
3667
3927
  /* @__PURE__ */ jsx(
3668
3928
  "button",
@@ -3686,25 +3946,36 @@ function RampWidgetTemplate({
3686
3946
  )
3687
3947
  ] }),
3688
3948
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
3689
- /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Country" }),
3949
+ /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-country", children: "Country" }),
3690
3950
  countriesLoading ? /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-input pollar-select-loading", children: [
3691
3951
  /* @__PURE__ */ jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3692
3952
  /* @__PURE__ */ jsx("span", { children: "Loading countries\u2026" })
3693
- ] }) : 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: [
3694
- flagEmoji(c.code),
3695
- " ",
3696
- c.code,
3697
- c.currency ? ` \u2014 ${c.currency}` : ""
3698
- ] }, c.code)) })
3953
+ ] }) : countries.length === 0 ? /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "No ramp providers available on this network yet." }) : /* @__PURE__ */ jsx(
3954
+ "select",
3955
+ {
3956
+ id: "pollar-ramp-country",
3957
+ className: "pollar-ramp-input",
3958
+ value: country,
3959
+ onChange: (e) => onCountryChange(e.target.value),
3960
+ children: countries.map((c) => /* @__PURE__ */ jsxs("option", { value: c.code, children: [
3961
+ flagEmoji(c.code),
3962
+ " ",
3963
+ c.code,
3964
+ c.currency ? ` \u2014 ${c.currency}` : ""
3965
+ ] }, c.code))
3966
+ }
3967
+ )
3699
3968
  ] }),
3969
+ routeSelector,
3700
3970
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
3701
- /* @__PURE__ */ jsxs("label", { className: "pollar-ramp-label", children: [
3971
+ /* @__PURE__ */ jsxs("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-amount", children: [
3702
3972
  "Amount",
3703
3973
  currency ? ` (${currency})` : ""
3704
3974
  ] }),
3705
3975
  /* @__PURE__ */ jsx(
3706
3976
  "input",
3707
3977
  {
3978
+ id: "pollar-ramp-amount",
3708
3979
  type: "number",
3709
3980
  className: "pollar-ramp-input",
3710
3981
  placeholder: "0.00",
@@ -3810,7 +4081,16 @@ function RampWidgetTemplate({
3810
4081
  )
3811
4082
  ] })
3812
4083
  ] }),
3813
- step === "status" && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment", children: [
4084
+ step === "status" && workflowContent,
4085
+ step === "status" && onDismissCurrentRamp && txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-explore", children: [
4086
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", disabled: completing, onClick: onDismissCurrentRamp, children: "Remove from pending list" }),
4087
+ /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", children: "This only hides the operation in this tab. To request cancellation, open the provider link above when available or contact the provider\u2019s support. Payments and refunds must be confirmed by the provider." })
4088
+ ] }),
4089
+ step === "status" && onExploreRoutes && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-explore", children: [
4090
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", disabled: completing, onClick: onExploreRoutes, children: txStatus === "completed" || txStatus === "failed" ? "Start another operation" : "Try another route" }),
4091
+ txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", children: "Your current operation stays saved so you can resume it later." })
4092
+ ] }),
4093
+ step === "status" && !workflowContent && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment", children: [
3814
4094
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
3815
4095
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Provider" }),
3816
4096
  /* @__PURE__ */ jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsx("code", { children: provider }) })
@@ -3822,15 +4102,23 @@ function RampWidgetTemplate({
3822
4102
  txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsx("span", { className: "pollar-spinner pollar-spinner-sm", "aria-label": "Checking status" })
3823
4103
  ] })
3824
4104
  ] }),
3825
- stellarTxHash && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
3826
- /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Stellar tx" }),
4105
+ fundsInTransit && txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
4106
+ /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Funds in transit" }),
4107
+ /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", children: "Your money is safe in your own wallet, waiting for the next step of the transfer." }),
4108
+ onRetryTransit && /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onRetryTransit, disabled: retrying, children: retrying ? "Retrying..." : "Retry now" })
4109
+ ] }),
4110
+ settledHash && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
4111
+ /* @__PURE__ */ jsxs("span", { className: "pollar-ramp-payment-label", children: [
4112
+ chainName,
4113
+ " tx"
4114
+ ] }),
3827
4115
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-value", style: { display: "flex", alignItems: "center", gap: 6 }, children: [
3828
4116
  /* @__PURE__ */ jsxs("code", { style: { flex: 1, wordBreak: "break-all" }, children: [
3829
- stellarTxHash.slice(0, 8),
4117
+ settledHash.slice(0, 8),
3830
4118
  "\u2026",
3831
- stellarTxHash.slice(-8)
4119
+ settledHash.slice(-8)
3832
4120
  ] }),
3833
- /* @__PURE__ */ jsx(CopyButton, { value: stellarTxHash, label: "Copy transaction hash" }),
4121
+ /* @__PURE__ */ jsx(CopyButton, { value: settledHash, label: "Copy transaction hash" }),
3834
4122
  explorerUrl && /* @__PURE__ */ jsx(
3835
4123
  "a",
3836
4124
  {
@@ -3838,8 +4126,8 @@ function RampWidgetTemplate({
3838
4126
  target: "_blank",
3839
4127
  rel: "noopener noreferrer",
3840
4128
  className: "pollar-copy-btn",
3841
- "aria-label": "View on Stellar Expert",
3842
- title: "View on Stellar Expert",
4129
+ "aria-label": `View on ${EXPLORER_NAME[txChain ?? "STELLAR"]}`,
4130
+ title: `View on ${EXPLORER_NAME[txChain ?? "STELLAR"]}`,
3843
4131
  children: /* @__PURE__ */ jsxs("svg", { width: "13", height: "13", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: [
3844
4132
  /* @__PURE__ */ jsx(
3845
4133
  "path",
@@ -3934,7 +4222,7 @@ function RampWidgetTemplate({
3934
4222
  canComplete && /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-primary", disabled: completing, onClick: onCompleteWithdraw, children: completing ? /* @__PURE__ */ jsxs(Fragment, { children: [
3935
4223
  /* @__PURE__ */ jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3936
4224
  "Submitting\u2026"
3937
- ] }) : "I've completed KYC \u2014 withdraw" }),
4225
+ ] }) : "Check verification and continue" }),
3938
4226
  errorMsg && /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", style: { color: "var(--pollar-error-text)" }, children: errorMsg }),
3939
4227
  /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: txStatus === "completed" ? "Done" : "Close" })
3940
4228
  ] }),
@@ -4100,19 +4388,26 @@ var SHARED_FIELD_LABELS = {
4100
4388
  country_code: "Pa\xEDs de residencia"
4101
4389
  }
4102
4390
  };
4103
- var errorCode = (error) => error && typeof error === "object" && typeof error.code === "string" ? error.code : null;
4391
+ var errorCode = (error) => typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
4392
+ var errorBody = (error) => {
4393
+ const body = typeof error === "object" && error !== null && "body" in error ? error.body : void 0;
4394
+ return typeof body === "object" && body !== null ? body : void 0;
4395
+ };
4104
4396
  function missingFieldsOf(error) {
4105
- const body = error && typeof error === "object" ? error.body : void 0;
4106
- return Array.isArray(body?.missing) ? body.missing.filter((item) => typeof item === "string") : [];
4397
+ const body = errorBody(error);
4398
+ const missing = body && "missing" in body ? body.missing : void 0;
4399
+ return Array.isArray(missing) ? missing.filter((item) => typeof item === "string") : [];
4107
4400
  }
4108
4401
  function registrationFixOf(error) {
4109
- const body = error && typeof error === "object" ? error.body : void 0;
4402
+ const body = errorBody(error);
4403
+ const invalid = body && "invalid" in body ? body.invalid : void 0;
4404
+ const formList = body && "forms" in body ? body.forms : void 0;
4110
4405
  const strings = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
4111
- const forms = Array.isArray(body?.forms) ? body.forms.flatMap((item) => {
4112
- const form = item && typeof item === "object" ? item : {};
4113
- return typeof form.formId === "string" ? [{ formId: form.formId, keys: strings(form.keys) }] : [];
4406
+ const forms = Array.isArray(formList) ? formList.flatMap((item) => {
4407
+ if (typeof item !== "object" || item === null || !("formId" in item) || typeof item.formId !== "string") return [];
4408
+ return [{ formId: item.formId, keys: strings("keys" in item ? item.keys : void 0) }];
4114
4409
  }) : [];
4115
- return { invalid: strings(body?.invalid), forms };
4410
+ return { invalid: strings(invalid), forms };
4116
4411
  }
4117
4412
  function RegistryCheckModal({ optionId, progress, onClose, onApproved }) {
4118
4413
  const { getClient, styles } = usePollar();
@@ -4432,9 +4727,51 @@ function ProviderRegistrationModal({
4432
4727
  }
4433
4728
  ) });
4434
4729
  }
4730
+
4731
+ // src/components/ramp-widget/ramp-quotes.ts
4732
+ function unavailableRampMessage(country, unavailable) {
4733
+ if (!unavailable.length) return `No ramp providers available for ${country} yet.`;
4734
+ return unavailable.map(
4735
+ ({ provider, code }) => code === "SDK_RAMPS_PROVIDER_NOT_CONFIGURED" ? `${provider} is not configured correctly. Contact the app administrator to enable this provider.` : `${provider} is temporarily unavailable. Please try again later.`
4736
+ ).join(" ");
4737
+ }
4738
+ async function quoteRampOptions(client, query, routes) {
4739
+ const matching = routes.filter(
4740
+ (route) => route.country === query.country && route.direction === query.direction && route.fiatCurrency === query.currency
4741
+ );
4742
+ const responses = await Promise.allSettled([
4743
+ client.getRampsQuote(query),
4744
+ ...matching.map((route) => client.getRampsQuote({ ...query, routeId: route.routeId, chain: route.asset.chain }))
4745
+ ]);
4746
+ const results = responses.flatMap((response) => response.status === "fulfilled" ? [response.value] : []);
4747
+ if (!results.length) throw responses[0].reason;
4748
+ const quotes = /* @__PURE__ */ new Map();
4749
+ const requirements = /* @__PURE__ */ new Map();
4750
+ const unavailable = /* @__PURE__ */ new Map();
4751
+ for (const result of results) {
4752
+ for (const quote of result.quotes) {
4753
+ const key = JSON.stringify([quote.provider, quote.route?.routeId, quote.route?.asset.chain, quote.rail, quote.protocol]);
4754
+ if (!quotes.has(key)) quotes.set(key, quote);
4755
+ }
4756
+ for (const requirement of result.requirementsRequired ?? [])
4757
+ requirements.set(`${requirement.rampProviderId}:${requirement.corridorId}`, requirement);
4758
+ for (const item of result.unavailable ?? []) unavailable.set(item.provider, item);
4759
+ }
4760
+ const failed = responses.find((response) => response.status === "rejected");
4761
+ if (!quotes.size && !requirements.size && failed?.status === "rejected") throw failed.reason;
4762
+ return {
4763
+ quotes: [...quotes.values()],
4764
+ requirementsRequired: [...requirements.values()],
4765
+ unavailable: [...unavailable.values()]
4766
+ };
4767
+ }
4435
4768
  var TERMINAL = ["completed", "failed"];
4769
+ var BANK_TYPES = ["CLABE", "PIX", "PSE", "ACH", "BREB"];
4770
+ function isBankType(value) {
4771
+ return BANK_TYPES.some((type) => type === value);
4772
+ }
4436
4773
  function requiredFieldsOf(quote) {
4437
- return quote.requiredFields ?? [];
4774
+ return "requiredFields" in quote ? quote.requiredFields : [];
4438
4775
  }
4439
4776
  function flowStepsOf(quotes, selected) {
4440
4777
  const needsDetails = selected != null ? requiredFieldsOf(selected).length > 0 : quotes.some((q) => requiredFieldsOf(q).length > 0);
@@ -4448,7 +4785,8 @@ var STEP_LABEL = {
4448
4785
  status: "Complete"
4449
4786
  };
4450
4787
  function brokenLimitOf(amount, quote) {
4451
- const { minAmount, maxAmount } = quote;
4788
+ const minAmount = "minAmount" in quote ? quote.minAmount : void 0;
4789
+ const maxAmount = "maxAmount" in quote ? quote.maxAmount : void 0;
4452
4790
  if (!Number.isFinite(amount)) return null;
4453
4791
  if (minAmount != null && amount < minAmount) return { limit: "min", value: minAmount };
4454
4792
  if (maxAmount != null && amount > maxAmount) return { limit: "max", value: maxAmount };
@@ -4472,19 +4810,58 @@ var RAMP_ERROR_MESSAGES = {
4472
4810
  SDK_RAMPS_ONCHAIN_SUBMIT_FAILED: "The network rejected the transaction, so nothing was sent. The wallet may need XLM for the fee."
4473
4811
  };
4474
4812
  function rampErrorMessage(e, fallback) {
4475
- const body = e?.body;
4476
- const code = e?.code;
4813
+ const code = typeof e === "object" && e !== null && "code" in e ? e.code : void 0;
4477
4814
  if (typeof code !== "string") return e instanceof Error ? e.message : fallback;
4478
- const limit = body?.limit;
4479
- const value = body?.limitAmount;
4480
- const currency = body?.limitCurrency;
4815
+ const raw = typeof e === "object" && e !== null && "body" in e ? e.body : void 0;
4816
+ const body = typeof raw === "object" && raw !== null ? raw : {};
4817
+ const limit = "limit" in body ? body.limit : void 0;
4818
+ const value = "limitAmount" in body ? body.limitAmount : void 0;
4819
+ const currency = "limitCurrency" in body ? body.limitCurrency : void 0;
4481
4820
  if ((limit === "min" || limit === "max") && typeof value === "number" && typeof currency === "string") {
4482
4821
  return limitMessage(limit, value, currency);
4483
4822
  }
4484
4823
  return RAMP_ERROR_MESSAGES[code] ?? (e instanceof Error ? e.message : fallback);
4485
4824
  }
4825
+ function readRampStorage(key) {
4826
+ try {
4827
+ return typeof window === "undefined" ? null : window.sessionStorage.getItem(key);
4828
+ } catch {
4829
+ return null;
4830
+ }
4831
+ }
4832
+ function writeRampStorage(key, value) {
4833
+ try {
4834
+ if (typeof window === "undefined") return;
4835
+ if (value === null) window.sessionStorage.removeItem(key);
4836
+ else window.sessionStorage.setItem(key, value);
4837
+ } catch {
4838
+ }
4839
+ }
4840
+ function savedRampsOf(key) {
4841
+ try {
4842
+ const value = JSON.parse(readRampStorage(`${key}:saved`) ?? "[]");
4843
+ return Array.isArray(value) ? value.filter((item) => typeof item?.txId === "string" && typeof item?.provider === "string") : [];
4844
+ } catch {
4845
+ return [];
4846
+ }
4847
+ }
4848
+ function hiddenRampsOf(key) {
4849
+ try {
4850
+ const value = JSON.parse(readRampStorage(`${key}:hidden`) ?? "[]");
4851
+ return Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
4852
+ } catch {
4853
+ return [];
4854
+ }
4855
+ }
4856
+ function explorerTxUrl2(chain, hash, network) {
4857
+ if (!hash) return null;
4858
+ const testnet = network === "testnet";
4859
+ if (chain === "SOLANA") return `https://explorer.solana.com/tx/${hash}${testnet ? "?cluster=devnet" : ""}`;
4860
+ if (chain === "POLYGON") return `https://${testnet ? "amoy." : ""}polygonscan.com/tx/${hash}`;
4861
+ return `https://stellar.expert/explorer/${testnet ? "testnet" : "public"}/tx/${hash}`;
4862
+ }
4486
4863
  function RampWidget({ onClose }) {
4487
- const { getClient, signTx, wallet, styles, network } = usePollar();
4864
+ const { getClient, wallet, styles, network } = usePollar();
4488
4865
  const walletAddress = wallet?.address ?? "";
4489
4866
  const client = getClient();
4490
4867
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
@@ -4495,7 +4872,7 @@ function RampWidget({ onClose }) {
4495
4872
  const [country, setCountry] = useState("");
4496
4873
  const [fieldValues, setFieldValues] = useState({});
4497
4874
  const setFieldValue = (key, value) => setFieldValues((v) => ({ ...v, [key]: value }));
4498
- const [countries, setCountries] = useState([]);
4875
+ const [legacyCountries, setCountries] = useState([]);
4499
4876
  const [countriesLoading, setCountriesLoading] = useState(true);
4500
4877
  const [refreshing, setRefreshing] = useState(false);
4501
4878
  const [quotes, setQuotes] = useState([]);
@@ -4520,21 +4897,129 @@ function RampWidget({ onClose }) {
4520
4897
  const [onboardingStatus, setOnboardingStatus] = useState(void 0);
4521
4898
  const [txStatus, setTxStatus] = useState(null);
4522
4899
  const [stellarTxHash, setStellarTxHash] = useState(null);
4900
+ const [txHash, setTxHash] = useState(null);
4901
+ const [txChain, setTxChain] = useState(null);
4902
+ const [fundsInTransit, setFundsInTransit] = useState(false);
4903
+ const [retrying, setRetrying] = useState(false);
4523
4904
  const [depositInstructions, setDepositInstructions] = useState(null);
4905
+ const [workflow, setWorkflow] = useState(null);
4906
+ const workflowRef = useRef(null);
4907
+ const storageKey = `pollar:ramp:${client.apiKey}:${walletAddress}`;
4908
+ const [savedRamps, setSavedRamps] = useState([]);
4909
+ const [legacySignature, setLegacySignature] = useState(void 0);
4910
+ function acceptWorkflow(incoming) {
4911
+ if (hiddenRampsOf(storageKey).includes(incoming.txId)) return false;
4912
+ const next = mergeRampSnapshot(workflowRef.current, incoming);
4913
+ workflowRef.current = next;
4914
+ setWorkflow(next);
4915
+ if (next === incoming) {
4916
+ const pending = savedRampsOf(storageKey).filter((item) => item.txId !== incoming.txId);
4917
+ if (!["completed", "failed", "refunded"].includes(incoming.lifecycleState ?? incoming.status))
4918
+ pending.push({
4919
+ txId: incoming.txId,
4920
+ provider: "provider" in incoming && typeof incoming.provider === "string" ? incoming.provider : provider || "Ramp"
4921
+ });
4922
+ writeRampStorage(`${storageKey}:saved`, JSON.stringify(pending));
4923
+ writeRampStorage(storageKey, incoming.txId);
4924
+ writeRampStorage(`${storageKey}:selection`, null);
4925
+ setSavedRamps(pending);
4926
+ }
4927
+ return next === incoming;
4928
+ }
4929
+ const [routes, setRoutes] = useState([]);
4930
+ const countries = mergeRampCountries(legacyCountries, routes);
4931
+ useEffect(() => {
4932
+ if (!country && countries[0]) {
4933
+ setCountry(countries[0].code);
4934
+ setCurrency(countries[0].currency ?? "");
4935
+ }
4936
+ }, [countries, country]);
4937
+ useEffect(() => {
4938
+ let active = true;
4939
+ void client.getRampRoutes().then((result) => {
4940
+ if (active) setRoutes(result.routes);
4941
+ }).catch(() => {
4942
+ });
4943
+ return () => {
4944
+ active = false;
4945
+ };
4946
+ }, [client]);
4524
4947
  const [completing, setCompleting] = useState(false);
4948
+ const completionLocked = useRef(false);
4525
4949
  const [errorMsg, setErrorMsg] = useState(null);
4950
+ useEffect(() => {
4951
+ if (typeof window === "undefined" || !walletAddress) return;
4952
+ setSavedRamps(savedRampsOf(storageKey));
4953
+ if (readRampStorage(`${storageKey}:selection`) === "1") return;
4954
+ const hidden = hiddenRampsOf(storageKey);
4955
+ const savedId = [new URLSearchParams(window.location.search).get("rampTxId"), readRampStorage(storageKey)].find(
4956
+ (id) => id && !hidden.includes(id)
4957
+ );
4958
+ if (!savedId) return;
4959
+ let active = true;
4960
+ void client.getRampTransaction(savedId).then(async (result) => {
4961
+ if (!active || result.transactionVersion === void 0) return;
4962
+ setDirection(result.direction);
4963
+ await applyResult(result);
4964
+ }).catch(() => {
4965
+ if (active)
4966
+ setErrorMsg("Could not restore the saved ramp. Retry opening the modal; do not create a replacement order.");
4967
+ });
4968
+ return () => {
4969
+ active = false;
4970
+ };
4971
+ }, [client, storageKey, walletAddress]);
4972
+ async function resumeSavedRamp(id) {
4973
+ if (completionLocked.current) return;
4974
+ setIsLoading(true);
4975
+ setErrorMsg(null);
4976
+ try {
4977
+ const result = await client.getRampTransaction(id);
4978
+ if (result.transactionVersion === void 0) throw new Error("This saved ramp cannot be restored.");
4979
+ setDirection(result.direction);
4980
+ await applyResult(result);
4981
+ } catch (error) {
4982
+ setErrorMsg(rampErrorMessage(error, "Could not resume the saved ramp."));
4983
+ } finally {
4984
+ setIsLoading(false);
4985
+ }
4986
+ }
4526
4987
  const directionRef = useRef(direction);
4527
4988
  directionRef.current = direction;
4989
+ function applySettlement(tx) {
4990
+ if (tx.stellarTxHash) setStellarTxHash(tx.stellarTxHash);
4991
+ if (tx.txHash) setTxHash(tx.txHash);
4992
+ if (tx.chain) setTxChain(tx.chain);
4993
+ setFundsInTransit(tx.fundsInTransit === true);
4994
+ }
4995
+ async function handleRetryTransit() {
4996
+ if (!txId || retrying) return;
4997
+ setRetrying(true);
4998
+ setErrorMsg(null);
4999
+ try {
5000
+ const tx = await client.retryRampTransaction(txId);
5001
+ setTxStatus(tx.status);
5002
+ applySettlement(tx);
5003
+ } catch (e) {
5004
+ setErrorMsg(e instanceof Error && e.message ? e.message : "Could not retry the transfer. Try again in a moment.");
5005
+ } finally {
5006
+ setRetrying(false);
5007
+ }
5008
+ }
5009
+ const workflowTerminal = !!workflow && ["completed", "failed", "refunded"].includes(workflow.lifecycleState ?? workflow.status);
4528
5010
  useEffect(() => {
4529
5011
  if (step !== "status" || !txId) return;
4530
- if (txStatus && TERMINAL.includes(txStatus)) return;
5012
+ if (workflowTerminal || txStatus && TERMINAL.includes(txStatus)) return;
4531
5013
  let active = true;
4532
5014
  const id = setInterval(async () => {
4533
5015
  try {
4534
5016
  const tx = await client.getRampTransaction(txId);
4535
5017
  if (!active) return;
5018
+ if (tx.transactionVersion !== void 0 && !acceptWorkflow(tx)) return;
4536
5019
  setTxStatus(tx.status);
4537
- if (tx.stellarTxHash) setStellarTxHash(tx.stellarTxHash);
5020
+ applySettlement(tx);
5021
+ const pending = "pendingSignature" in tx ? tx.pendingSignature : void 0;
5022
+ if (pending) setLegacySignature(pending);
4538
5023
  if (tx.kycUrl) setKycUrl(tx.kycUrl);
4539
5024
  if (tx.depositInstructions) setDepositInstructions(tx.depositInstructions);
4540
5025
  if (TERMINAL.includes(tx.status)) clearInterval(id);
@@ -4545,9 +5030,9 @@ function RampWidget({ onClose }) {
4545
5030
  active = false;
4546
5031
  clearInterval(id);
4547
5032
  };
4548
- }, [step, txId, txStatus, client]);
5033
+ }, [step, txId, txStatus, workflowTerminal, client]);
4549
5034
  useEffect(() => {
4550
- if (step !== "status" || !kycPending || kycApproved) return;
5035
+ if (workflow || step !== "status" || !kycPending || kycApproved || provider.toLowerCase() !== "abroad") return;
4551
5036
  if (onboardingStatus === "awaiting_provider") return;
4552
5037
  let active = true;
4553
5038
  const check = async () => {
@@ -4563,14 +5048,15 @@ function RampWidget({ onClose }) {
4563
5048
  active = false;
4564
5049
  clearInterval(id);
4565
5050
  };
4566
- }, [step, kycPending, kycApproved, onboardingStatus, client]);
5051
+ }, [step, kycPending, kycApproved, onboardingStatus, workflow, client, provider]);
4567
5052
  async function loadCountries(resetSelection) {
4568
5053
  setCountriesLoading(true);
4569
5054
  try {
4570
5055
  const { countries: list } = await client.getRampCountries();
4571
5056
  setCountries(list);
4572
- const first = list[0];
4573
- const stillValid = list.some((c) => c.code === country);
5057
+ const available = mergeRampCountries(list, routes);
5058
+ const first = available[0];
5059
+ const stillValid = available.some((c) => c.code === country);
4574
5060
  if (first && (resetSelection || !stillValid)) {
4575
5061
  setCountry(first.code);
4576
5062
  if (first.currency) setCurrency(first.currency);
@@ -4595,13 +5081,24 @@ function RampWidget({ onClose }) {
4595
5081
  try {
4596
5082
  await loadCountries(false);
4597
5083
  if (step === "select_route") {
4598
- const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
5084
+ const result = await quoteRampOptions(
5085
+ client,
5086
+ {
5087
+ country,
5088
+ amount: Number(amount),
5089
+ amountExact: amount,
5090
+ currency,
5091
+ direction
5092
+ },
5093
+ routes
5094
+ );
4599
5095
  setQuotes(result.quotes ?? []);
4600
5096
  setRequirementsRequired(result.requirementsRequired ?? []);
4601
5097
  } else if (step === "status" && txId) {
4602
5098
  const tx = await client.getRampTransaction(txId);
5099
+ if (tx.transactionVersion !== void 0 && !acceptWorkflow(tx)) return;
4603
5100
  setTxStatus(tx.status);
4604
- if (tx.stellarTxHash) setStellarTxHash(tx.stellarTxHash);
5101
+ applySettlement(tx);
4605
5102
  if (tx.kycUrl) setKycUrl(tx.kycUrl);
4606
5103
  if (tx.depositInstructions) setDepositInstructions(tx.depositInstructions);
4607
5104
  }
@@ -4611,25 +5108,50 @@ function RampWidget({ onClose }) {
4611
5108
  }
4612
5109
  }
4613
5110
  function resetToInput({ keepMessage = false } = {}) {
5111
+ writeRampStorage(storageKey, null);
5112
+ writeRampStorage(`${storageKey}:selection`, "1");
4614
5113
  setStep("input");
4615
5114
  setQuotes([]);
4616
5115
  setRequirementsRequired([]);
4617
5116
  setSelectedQuote(null);
4618
5117
  setFieldValues({});
5118
+ setWorkflow(null);
5119
+ workflowRef.current = null;
5120
+ setLegacySignature(void 0);
4619
5121
  setTxId(null);
4620
5122
  setProvider("");
4621
5123
  setKycUrl(null);
4622
5124
  setTosUrl(null);
4623
5125
  setTxStatus(null);
4624
5126
  setStellarTxHash(null);
5127
+ setTxHash(null);
5128
+ setTxChain(null);
5129
+ setFundsInTransit(false);
4625
5130
  setDepositInstructions(null);
4626
5131
  setKycPending(false);
4627
5132
  setKycApproved(false);
4628
5133
  setNoticeMsg(null);
4629
5134
  if (!keepMessage) setErrorMsg(null);
4630
5135
  }
5136
+ function dismissSavedRamp(id) {
5137
+ if (completionLocked.current) return;
5138
+ const pending = savedRampsOf(storageKey).filter((ramp) => ramp.txId !== id);
5139
+ writeRampStorage(`${storageKey}:saved`, JSON.stringify(pending));
5140
+ writeRampStorage(`${storageKey}:hidden`, JSON.stringify([.../* @__PURE__ */ new Set([...hiddenRampsOf(storageKey), id])]));
5141
+ if (readRampStorage(storageKey) === id) writeRampStorage(storageKey, null);
5142
+ setSavedRamps(pending);
5143
+ if (txId === id) resetToInput();
5144
+ setNoticeMsg(
5145
+ "Removed from this tab\u2019s pending list. The operation may still be active at the provider; no payment was cancelled or refunded."
5146
+ );
5147
+ }
4631
5148
  async function resumeWithSignature(id, ps) {
4632
- const outcome = await signTx(ps.unsignedXdr);
5149
+ const outcome = await client.signTx(ps.unsignedXdr, { skipSponsorship: ps.action === "sep10" });
5150
+ if (outcome.status === "cosign_pending") {
5151
+ setErrorMsg("Approve the signature on your device, then resume.");
5152
+ setStep("error");
5153
+ return;
5154
+ }
4633
5155
  if (outcome.status !== "signed") {
4634
5156
  setErrorMsg(outcome.message ?? outcome.details ?? "Signing was cancelled.");
4635
5157
  setStep("error");
@@ -4642,12 +5164,10 @@ function RampWidget({ onClose }) {
4642
5164
  await applyResult(result);
4643
5165
  }
4644
5166
  async function applyResult(result) {
5167
+ if ("transactionVersion" in result && result.transactionVersion !== void 0 && !acceptWorkflow(result)) return;
4645
5168
  setTxId(result.txId);
4646
5169
  setProvider(result.provider);
4647
- if (result.pendingSignature) {
4648
- await resumeWithSignature(result.txId, result.pendingSignature);
4649
- return;
4650
- }
5170
+ setLegacySignature(result.pendingSignature);
4651
5171
  setKycUrl(result.kycUrl ?? null);
4652
5172
  setTosUrl(result.tosUrl ?? null);
4653
5173
  setOnboardingStatus(result.onboardingStatus);
@@ -4659,14 +5179,21 @@ function RampWidget({ onClose }) {
4659
5179
  setStep("status");
4660
5180
  }
4661
5181
  async function loadQuotes() {
4662
- const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
5182
+ const result = await quoteRampOptions(
5183
+ client,
5184
+ {
5185
+ country,
5186
+ amount: Number(amount),
5187
+ amountExact: amount,
5188
+ currency,
5189
+ direction
5190
+ },
5191
+ routes
5192
+ );
4663
5193
  const list = result.quotes ?? [];
4664
5194
  const locked = result.requirementsRequired ?? [];
4665
5195
  if (list.length === 0 && locked.length === 0) {
4666
- const down = (result.unavailable ?? []).map((u) => u.provider);
4667
- setErrorMsg(
4668
- down.length > 0 ? `${down.join(", ")} ${down.length === 1 ? "is" : "are"} temporarily unavailable. Please try again later.` : `No ramp providers available for ${country} yet.`
4669
- );
5196
+ setErrorMsg(unavailableRampMessage(country, result.unavailable ?? []));
4670
5197
  setStep("error");
4671
5198
  return false;
4672
5199
  }
@@ -4712,19 +5239,40 @@ function RampWidget({ onClose }) {
4712
5239
  setIsLoading(true);
4713
5240
  setErrorMsg(null);
4714
5241
  try {
4715
- const base = { quoteId: quote.quoteId, amount: Number(amount), currency, country };
4716
- if (walletAddress) base.walletAddress = walletAddress;
4717
- const STANDARD_BODY_KEYS = /* @__PURE__ */ new Set(["email", "fullName", "taxId", "qrCode"]);
5242
+ const body = {
5243
+ quoteId: quote.quoteId,
5244
+ amount: quote.fiatAmount ?? Number(amount),
5245
+ ...quote.terms ? { amountExact: quote.terms.fiatAmount } : {},
5246
+ currency,
5247
+ country,
5248
+ ...walletAddress ? { walletAddress } : {}
5249
+ };
4718
5250
  const extraFields = {};
4719
5251
  for (const f of requiredFieldsOf(quote)) {
4720
5252
  const val = (fieldValues[f.key] ?? "").trim();
4721
5253
  if (!val) continue;
4722
- if (f.bankType) base.bankDetails = { type: f.bankType, value: val };
4723
- else if (STANDARD_BODY_KEYS.has(f.key)) base[f.key] = val;
5254
+ if (f.bankType) {
5255
+ if (isBankType(f.bankType)) body.bankDetails = { type: f.bankType, value: val };
5256
+ } else if (f.key === "email" || f.key === "fullName" || f.key === "taxId" || f.key === "qrCode") body[f.key] = val;
4724
5257
  else extraFields[f.key] = val;
4725
5258
  }
4726
- if (Object.keys(extraFields).length > 0) base.fields = extraFields;
4727
- const result = direction === "onramp" ? await client.createOnRamp(base) : await client.createOffRamp(base);
5259
+ if (quote.route)
5260
+ body.fields = Object.fromEntries(
5261
+ requiredFieldsOf(quote).map((field) => [field.key, (fieldValues[field.key] ?? "").trim()]).filter(([, value]) => value)
5262
+ );
5263
+ else if (Object.keys(extraFields).length > 0) body.fields = extraFields;
5264
+ const onrampBody = {
5265
+ quoteId: body.quoteId,
5266
+ amount: body.amount,
5267
+ currency: body.currency,
5268
+ country: body.country,
5269
+ ...body.amountExact !== void 0 ? { amountExact: body.amountExact } : {},
5270
+ ...body.walletAddress !== void 0 ? { walletAddress: body.walletAddress } : {},
5271
+ ...body.email !== void 0 ? { email: body.email } : {},
5272
+ ...body.fullName !== void 0 ? { fullName: body.fullName } : {},
5273
+ ...body.fields !== void 0 ? { fields: body.fields } : {}
5274
+ };
5275
+ const result = direction === "onramp" ? await client.createOnRamp(onrampBody) : await client.createOffRamp(body);
4728
5276
  await applyResult(result);
4729
5277
  } catch (e) {
4730
5278
  const requirement = requiredRampKyc(e);
@@ -4774,8 +5322,24 @@ function RampWidget({ onClose }) {
4774
5322
  function handleOpenTos() {
4775
5323
  if (tosUrl) window.open(tosUrl, "_blank", "noopener,noreferrer");
4776
5324
  }
5325
+ async function handleLegacySignature() {
5326
+ if (!txId || !legacySignature || completionLocked.current) return;
5327
+ completionLocked.current = true;
5328
+ setCompleting(true);
5329
+ setErrorMsg(null);
5330
+ try {
5331
+ await resumeWithSignature(txId, legacySignature);
5332
+ } catch (e) {
5333
+ setErrorMsg(rampErrorMessage(e, "Failed to submit the signature."));
5334
+ setStep("error");
5335
+ } finally {
5336
+ completionLocked.current = false;
5337
+ setCompleting(false);
5338
+ }
5339
+ }
4777
5340
  async function handleCompleteWithdraw() {
4778
- if (!txId) return;
5341
+ if (!txId || completionLocked.current) return;
5342
+ completionLocked.current = true;
4779
5343
  setCompleting(true);
4780
5344
  setErrorMsg(null);
4781
5345
  try {
@@ -4792,11 +5356,12 @@ function RampWidget({ onClose }) {
4792
5356
  msg.includes("KYC") ? "Finish KYC at the provider first, then try again." : msg || "Failed to complete the withdrawal."
4793
5357
  );
4794
5358
  } finally {
5359
+ completionLocked.current = false;
4795
5360
  setCompleting(false);
4796
5361
  }
4797
5362
  }
4798
5363
  const kycBlocking = kycPending && !kycApproved;
4799
- const canComplete = direction === "offramp" && step === "status" && txStatus !== "completed" && !stellarTxHash && !kycPending;
5364
+ const canComplete = !workflow && !legacySignature && direction === "offramp" && step === "status" && txStatus !== "completed" && txStatus !== "failed" && !stellarTxHash && !txHash && !kycPending;
4800
5365
  const flowSteps = flowStepsOf(quotes, selectedQuote);
4801
5366
  const flowStepIndex = flowSteps.indexOf(STEP_LABEL[step] ?? "");
4802
5367
  if (pendingKyc?.type === "FORM") {
@@ -4854,6 +5419,20 @@ function RampWidget({ onClose }) {
4854
5419
  return /* @__PURE__ */ jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsx(
4855
5420
  RampWidgetTemplate,
4856
5421
  {
5422
+ workflowContent: workflow ? /* @__PURE__ */ jsx(
5423
+ RampWorkflow,
5424
+ {
5425
+ client,
5426
+ snapshot: workflow,
5427
+ onBusyChange: (busy) => {
5428
+ completionLocked.current = busy;
5429
+ setCompleting(busy);
5430
+ },
5431
+ onChange: (next) => {
5432
+ if (acceptWorkflow(next)) setTxStatus(next.status);
5433
+ }
5434
+ }
5435
+ ) : legacySignature && step === "status" && txStatus !== "completed" && txStatus !== "failed" ? /* @__PURE__ */ jsx("button", { type: "button", disabled: completing, onClick: () => void handleLegacySignature(), children: "Authorize wallet request" }) : void 0,
4857
5436
  theme,
4858
5437
  accentColor,
4859
5438
  styleOverrides,
@@ -4881,13 +5460,27 @@ function RampWidget({ onClose }) {
4881
5460
  onboardingStatus: onboardingStatus ?? null,
4882
5461
  kycJustApproved: kycPending && kycApproved,
4883
5462
  stellarTxHash,
4884
- explorerUrl: stellarTxHash ? `https://stellar.expert/explorer/${network === "mainnet" ? "public" : "testnet"}/tx/${stellarTxHash}` : null,
5463
+ txHash,
5464
+ txChain,
5465
+ explorerUrl: explorerTxUrl2(txChain ?? "STELLAR", txHash ?? stellarTxHash, network),
5466
+ fundsInTransit,
5467
+ retrying,
5468
+ onRetryTransit: handleRetryTransit,
4885
5469
  depositInstructions,
4886
5470
  canComplete,
4887
5471
  completing,
5472
+ savedRamps,
5473
+ onResumeRamp: resumeSavedRamp,
5474
+ onDismissRamp: dismissSavedRamp,
5475
+ onDismissCurrentRamp: workflow ? () => dismissSavedRamp(workflow.txId) : void 0,
5476
+ onExploreRoutes: workflow ? () => {
5477
+ if (!completionLocked.current) resetToInput();
5478
+ } : void 0,
4888
5479
  errorMsg,
4889
5480
  noticeMsg,
4890
- onDirectionChange: setDirection,
5481
+ onDirectionChange: (next) => {
5482
+ setDirection(next);
5483
+ },
4891
5484
  onAmountChange: (next) => {
4892
5485
  setAmount(next);
4893
5486
  setErrorMsg(null);
@@ -4920,49 +5513,6 @@ function walletNotReadyReason(wallet, chain) {
4920
5513
  }
4921
5514
  return null;
4922
5515
  }
4923
-
4924
- // src/lib/qr-code/index.tsx
4925
- var import_ErrorCorrectLevel = __toESM(require_ErrorCorrectLevel());
4926
- var import_QRCode = __toESM(require_QRCode());
4927
- var QRCodeSvg = forwardRef(function QRCodeSvg2({ bgColor, bgD, fgD, fgColor, size, title, viewBoxSize, xmlns = "http://www.w3.org/2000/svg", ...props }, ref) {
4928
- return /* @__PURE__ */ jsxs("svg", { ...props, height: size, ref, viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`, width: size, xmlns, children: [
4929
- title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
4930
- /* @__PURE__ */ jsx("path", { d: bgD, fill: bgColor }),
4931
- /* @__PURE__ */ jsx("path", { d: fgD, fill: fgColor })
4932
- ] });
4933
- });
4934
- QRCodeSvg.displayName = "QRCodeSvg";
4935
- function bytesToBinaryString(bytes) {
4936
- return bytes.map((b) => String.fromCharCode(b & 255)).join("");
4937
- }
4938
- function encodeStringToUtf8Bytes(input) {
4939
- return Array.from(new TextEncoder().encode(input));
4940
- }
4941
- var QRCode = forwardRef(function QRCode2({ bgColor = "#FFFFFF", fgColor = "#000000", level = "L", size = 256, value, ...props }, ref) {
4942
- const qrcode = new import_QRCode.default(-1, import_ErrorCorrectLevel.default[level]);
4943
- const utf8Bytes = encodeStringToUtf8Bytes(value);
4944
- const binaryString = bytesToBinaryString(utf8Bytes);
4945
- qrcode.addData(binaryString, "Byte");
4946
- qrcode.make();
4947
- const cells = qrcode.modules;
4948
- return /* @__PURE__ */ jsx(
4949
- QRCodeSvg,
4950
- {
4951
- ...props,
4952
- bgColor,
4953
- bgD: cells.map(
4954
- (row, rowIndex) => row.map((cell, cellIndex) => !cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
4955
- ).join(" "),
4956
- fgColor,
4957
- fgD: cells.map(
4958
- (row, rowIndex) => row.map((cell, cellIndex) => cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
4959
- ).join(" "),
4960
- ref,
4961
- size,
4962
- viewBoxSize: cells.length
4963
- }
4964
- );
4965
- });
4966
5516
  function PollarLogo(props) {
4967
5517
  return /* @__PURE__ */ jsxs("svg", { width: "45", height: "48", viewBox: "0 0 45 48", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: [
4968
5518
  /* @__PURE__ */ jsx(
@@ -4988,7 +5538,7 @@ function PollarLogo(props) {
4988
5538
  )
4989
5539
  ] });
4990
5540
  }
4991
- var CHAIN_NAME = {
5541
+ var CHAIN_NAME2 = {
4992
5542
  STELLAR: "Stellar",
4993
5543
  POLYGON: "Polygon",
4994
5544
  SOLANA: "Solana"
@@ -5003,11 +5553,13 @@ function ReceiveModalTemplate({
5003
5553
  onSelectChain,
5004
5554
  copied,
5005
5555
  notReadyReason,
5556
+ recoveryLocked,
5557
+ onSetupRecovery,
5006
5558
  onCopy,
5007
5559
  onClose
5008
5560
  }) {
5009
5561
  const cssVars = buildModalCssVars(theme, accentColor, styleOverrides);
5010
- const chainName = selectedChain ? CHAIN_NAME[selectedChain] ?? selectedChain : "wallet";
5562
+ const chainName = selectedChain ? CHAIN_NAME2[selectedChain] ?? selectedChain : "wallet";
5011
5563
  return /* @__PURE__ */ jsxs(
5012
5564
  "div",
5013
5565
  {
@@ -5021,7 +5573,10 @@ function ReceiveModalTemplate({
5021
5573
  /* @__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" }) }) }) })
5022
5574
  ] }),
5023
5575
  chains && onSelectChain && /* @__PURE__ */ jsx(ChainSelect, { value: selectedChain, options: chains, onChange: onSelectChain }),
5024
- walletAddress ? /* @__PURE__ */ jsxs(Fragment, { children: [
5576
+ recoveryLocked ? /* @__PURE__ */ jsxs("div", { className: "pollar-receive-locked", role: "alert", children: [
5577
+ /* @__PURE__ */ jsx("p", { children: "Set up a way to recover this wallet before receiving funds. Without one, losing this device means losing whatever you receive, and nobody can get it back." }),
5578
+ onSetupRecovery && /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-receive-copy-btn", onClick: onSetupRecovery, children: "Set up recovery" })
5579
+ ] }) : walletAddress ? /* @__PURE__ */ jsxs(Fragment, { children: [
5025
5580
  /* @__PURE__ */ jsxs("div", { className: "pollar-receive-qr", children: [
5026
5581
  /* @__PURE__ */ jsxs("div", { className: "pollar-receive-network-badge", children: [
5027
5582
  /* @__PURE__ */ jsx("span", { className: "pollar-receive-network-dot", "aria-hidden": true }),
@@ -5089,13 +5644,13 @@ function ReceiveModalTemplate({
5089
5644
  ] })
5090
5645
  ] })
5091
5646
  ] }) : /* @__PURE__ */ jsx("div", { className: "pollar-modal-empty", children: "No wallet connected." }),
5092
- /* @__PURE__ */ jsx(PollarModalFooter, { shield: true })
5647
+ /* @__PURE__ */ jsx(PollarModalFooter, {})
5093
5648
  ]
5094
5649
  }
5095
5650
  );
5096
5651
  }
5097
5652
  function ReceiveModal({ onClose }) {
5098
- const { wallet, wallets, styles } = usePollar();
5653
+ const { wallet, wallets, styles, recoveryLocked, openRecoveryModal } = usePollar();
5099
5654
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
5100
5655
  const [copied, setCopied] = useState(false);
5101
5656
  const copyTimerRef = useRef(null);
@@ -5137,6 +5692,11 @@ function ReceiveModal({ onClose }) {
5137
5692
  setSelectedChain(chain);
5138
5693
  },
5139
5694
  notReadyReason,
5695
+ recoveryLocked,
5696
+ onSetupRecovery: () => {
5697
+ onClose();
5698
+ openRecoveryModal();
5699
+ },
5140
5700
  copied,
5141
5701
  onCopy: handleCopy,
5142
5702
  onClose
@@ -5152,6 +5712,7 @@ var STATUS_MESSAGES = {
5152
5712
  submitting: "Submitting transaction\u2026",
5153
5713
  submitted: "Submitted \u2014 waiting for confirmation\u2026",
5154
5714
  "signing-submitting": "Signing and submitting transaction\u2026",
5715
+ awaiting_cosign: "Waiting for your approval on this device\u2026",
5155
5716
  "building-signing-submitting": "Processing transaction\u2026",
5156
5717
  success: "Transaction sent successfully",
5157
5718
  error: "Transaction failed"
@@ -5187,14 +5748,22 @@ function TxStatusView({
5187
5748
  }) {
5188
5749
  const buildData = "buildData" in transaction ? transaction.buildData : null;
5189
5750
  const hash = transaction.step === "success" ? transaction.hash : null;
5190
- const errorDetails = transaction.step === "error" ? transaction.details ?? null : null;
5751
+ const errorDetails = transaction.step === "error" ? transactionErrorMessage(transaction.code, transaction.details) : null;
5191
5752
  const isBuilt = transaction.step === "built";
5192
5753
  const isInFlight = IN_FLIGHT_STEPS.has(transaction.step);
5193
5754
  const isSuccess = transaction.step === "success";
5194
5755
  const isError = transaction.step === "error";
5195
5756
  const showDetails = buildData !== null && SHOW_DETAILS_STEPS.has(transaction.step);
5196
- const walletImg = walletType === WalletType.FREIGHTER ? LOGO_FREIGHTER : walletType === WalletType.ALBEDO ? LOGO_ALBEDO : LOGO_POLLAR;
5757
+ const walletImg = walletType === WalletType.FREIGHTER ? LOGO_FREIGHTER : walletType === WalletType.ALBEDO ? LOGO_ALBEDO : null;
5197
5758
  const walletAlt = walletType === WalletType.FREIGHTER ? "Freighter" : walletType === WalletType.ALBEDO ? "Albedo" : "Pollar";
5759
+ const statusBanner = /* @__PURE__ */ jsx(
5760
+ ModalStatusBanner,
5761
+ {
5762
+ message: STATUS_MESSAGES[transaction.step],
5763
+ status: isError ? "ERROR" : isInFlight ? "LOADING" : isSuccess ? "SUCCESS" : "NONE"
5764
+ }
5765
+ );
5766
+ const showSpinner = isInFlight || isSuccess || isError;
5198
5767
  return /* @__PURE__ */ jsxs(Fragment, { children: [
5199
5768
  showDetails && buildData && /* @__PURE__ */ jsxs(Fragment, { children: [
5200
5769
  /* @__PURE__ */ jsxs("div", { className: "pollar-tx-summary", children: [
@@ -5235,7 +5804,7 @@ function TxStatusView({
5235
5804
  /* @__PURE__ */ jsx("pre", { className: "pollar-tx-error-details-content", children: errorDetails })
5236
5805
  ] }),
5237
5806
  isBuilt && /* @__PURE__ */ jsx("button", { className: "pollar-btn-primary pollar-tx-sign-btn", onClick: onSignAndSend, children: "Sign & Send" }),
5238
- (isInFlight || isSuccess || isError) && /* @__PURE__ */ jsxs("div", { className: "pollar-tx-wallet-spinner", children: [
5807
+ showSpinner && /* @__PURE__ */ jsxs("div", { className: "pollar-tx-wallet-spinner", children: [
5239
5808
  /* @__PURE__ */ jsxs("div", { className: "pollar-tx-spinner-ring", children: [
5240
5809
  /* @__PURE__ */ jsxs(
5241
5810
  "svg",
@@ -5265,8 +5834,12 @@ function TxStatusView({
5265
5834
  ]
5266
5835
  }
5267
5836
  ),
5268
- /* @__PURE__ */ jsx("div", { className: "pollar-tx-wallet-icon", children: /* @__PURE__ */ jsx("img", { src: walletImg, alt: walletAlt, className: "pollar-tx-wallet-img" }) })
5837
+ /* @__PURE__ */ jsx("div", { className: "pollar-tx-wallet-icon", children: walletImg ? /* @__PURE__ */ jsx("img", { src: walletImg, alt: walletAlt, className: "pollar-tx-wallet-img" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
5838
+ /* @__PURE__ */ jsx("img", { src: LOGO_POLLAR_MARK_ON_LIGHT, alt: walletAlt, className: "pollar-tx-wallet-img pollar-logo-on-light" }),
5839
+ /* @__PURE__ */ jsx("img", { src: LOGO_POLLAR_MARK_ON_DARK, alt: walletAlt, className: "pollar-tx-wallet-img pollar-logo-on-dark" })
5840
+ ] }) })
5269
5841
  ] }),
5842
+ statusBanner,
5270
5843
  isInFlight && /* @__PURE__ */ jsx("p", { className: "pollar-tx-spinner-label", children: walletType === WalletType.FREIGHTER ? "Waiting for Freighter\u2026" : walletType === WalletType.ALBEDO ? "Waiting for Albedo\u2026" : "Signing and sending\u2026" }),
5271
5844
  isError && onRetry && "buildData" in transaction && transaction.buildData && /* @__PURE__ */ jsx("button", { className: "pollar-btn-secondary pollar-tx-retry-btn", onClick: onRetry, children: "Try again" })
5272
5845
  ] }),
@@ -5331,13 +5904,7 @@ function TxStatusView({
5331
5904
  ] })
5332
5905
  ] }),
5333
5906
  isSuccess && /* @__PURE__ */ jsx("button", { className: "pollar-btn-primary pollar-tx-sign-btn", onClick: onDone, children: "Done" }),
5334
- /* @__PURE__ */ jsx(
5335
- ModalStatusBanner,
5336
- {
5337
- message: STATUS_MESSAGES[transaction.step],
5338
- status: isError ? "ERROR" : isInFlight ? "LOADING" : isSuccess ? "SUCCESS" : "NONE"
5339
- }
5340
- )
5907
+ !showSpinner && statusBanner
5341
5908
  ] });
5342
5909
  }
5343
5910
  function formatBalance(balance) {
@@ -5354,6 +5921,11 @@ function chainLabel(chain) {
5354
5921
  if (chain === "POLYGON") return "Polygon";
5355
5922
  return "network";
5356
5923
  }
5924
+ function destinationPlaceholder(chain) {
5925
+ if (chain === "SOLANA") return "Solana address";
5926
+ if (chain === "POLYGON") return "0x\u2026";
5927
+ return "G\u2026";
5928
+ }
5357
5929
  function AssetIcon({ code }) {
5358
5930
  return /* @__PURE__ */ jsx("span", { className: "pollar-send-asset-icon", "aria-hidden": true, children: code.slice(0, 2).toUpperCase() });
5359
5931
  }
@@ -5471,6 +6043,10 @@ function SendModalTemplate({
5471
6043
  onMax,
5472
6044
  onPaste,
5473
6045
  onDestinationChange,
6046
+ memo = "",
6047
+ memoType = "text",
6048
+ onMemoChange,
6049
+ onMemoTypeChange,
5474
6050
  onSubmit,
5475
6051
  onSignAndSend,
5476
6052
  onToggleXdr,
@@ -5578,7 +6154,7 @@ function SendModalTemplate({
5578
6154
  {
5579
6155
  className: "pollar-input pollar-send-destination-input",
5580
6156
  type: "text",
5581
- placeholder: "G\u2026",
6157
+ placeholder: destinationPlaceholder(selectedChain),
5582
6158
  value: destination,
5583
6159
  onChange: (e) => onDestinationChange(e.target.value),
5584
6160
  "aria-label": "Destination wallet"
@@ -5587,6 +6163,39 @@ function SendModalTemplate({
5587
6163
  /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-send-inline-action", onClick: onPaste, children: "Paste" })
5588
6164
  ] })
5589
6165
  ] }),
6166
+ selectedChain === "STELLAR" && onMemoChange && /* @__PURE__ */ jsxs("div", { className: "pollar-send-field", children: [
6167
+ /* @__PURE__ */ jsxs("label", { className: "pollar-send-label", htmlFor: "pollar-send-memo", children: [
6168
+ "Memo ",
6169
+ /* @__PURE__ */ jsx("span", { className: "pollar-send-label-optional", children: "(optional)" })
6170
+ ] }),
6171
+ /* @__PURE__ */ jsxs("div", { className: "pollar-send-memo-row", children: [
6172
+ /* @__PURE__ */ jsxs(
6173
+ "select",
6174
+ {
6175
+ className: "pollar-input pollar-send-memo-type",
6176
+ value: memoType,
6177
+ onChange: (e) => onMemoTypeChange?.(e.target.value),
6178
+ "aria-label": "Memo type",
6179
+ children: [
6180
+ /* @__PURE__ */ jsx("option", { value: "text", children: "Text" }),
6181
+ /* @__PURE__ */ jsx("option", { value: "id", children: "ID" })
6182
+ ]
6183
+ }
6184
+ ),
6185
+ /* @__PURE__ */ jsx(
6186
+ "input",
6187
+ {
6188
+ id: "pollar-send-memo",
6189
+ className: "pollar-input pollar-send-memo-input",
6190
+ type: "text",
6191
+ inputMode: memoType === "id" ? "numeric" : "text",
6192
+ placeholder: memoType === "id" ? "123456789" : "Up to 28 characters",
6193
+ value: memo,
6194
+ onChange: (e) => onMemoChange(e.target.value)
6195
+ }
6196
+ )
6197
+ ] })
6198
+ ] }),
5590
6199
  formError && /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: formError }),
5591
6200
  /* @__PURE__ */ jsx("div", { className: "pollar-modal-actions", children: /* @__PURE__ */ jsx("button", { className: "pollar-btn-primary", onClick: onSubmit, disabled: !canSubmit, children: "Continue" }) })
5592
6201
  ] }),
@@ -5610,6 +6219,21 @@ function SendModalTemplate({
5610
6219
  }
5611
6220
  );
5612
6221
  }
6222
+ var MAX_MEMO_ID = 18446744073709551615n;
6223
+ var SOLANA_FEE_MARGIN_LAMPORTS = 10000n;
6224
+ function solanaMaxAfterFee(available) {
6225
+ const lamports = BigInt(toBaseUnits(available, 9)) - SOLANA_FEE_MARGIN_LAMPORTS;
6226
+ return fromBaseUnits((lamports > 0n ? lamports : 0n).toString(), 9);
6227
+ }
6228
+ function parseMemo(type, raw) {
6229
+ const value = type === "id" ? raw.trim() : raw;
6230
+ if (value.trim() === "") return void 0;
6231
+ if (type === "text") {
6232
+ return new TextEncoder().encode(value).length > 28 ? "The memo text can be at most 28 bytes." : { type, value };
6233
+ }
6234
+ if (!/^\d+$/.test(value) || BigInt(value) > MAX_MEMO_ID) return "The memo ID must be a whole number up to 2^64 - 1.";
6235
+ return { type, value };
6236
+ }
5613
6237
  function assetParam(record) {
5614
6238
  if (record.type === "native") return { type: "native" };
5615
6239
  if (record.type === "credit_alphanum4") {
@@ -5634,6 +6258,8 @@ function SendModal({ onClose }) {
5634
6258
  const [step, setStep] = useState("form");
5635
6259
  const [amount, setAmount] = useState("");
5636
6260
  const [destination, setDestination] = useState("");
6261
+ const [memo, setMemo] = useState("");
6262
+ const [memoType, setMemoType] = useState("text");
5637
6263
  const [selectedAsset, setSelectedAsset] = useState(null);
5638
6264
  const [showXdr, setShowXdr] = useState(false);
5639
6265
  const [copied, setCopied] = useState(false);
@@ -5644,9 +6270,10 @@ function SendModal({ onClose }) {
5644
6270
  useEffect(() => {
5645
6271
  if (selectedChain === null && chains.length > 0) setSelectedChain(chains[0]);
5646
6272
  }, [chains, selectedChain]);
5647
- const canSendOnChain = selectedChain === "STELLAR" || selectedChain === "SOLANA";
6273
+ const canSendOnChain = selectedChain === "STELLAR" || selectedChain === "SOLANA" || selectedChain === "POLYGON";
5648
6274
  const notReadyReason = walletNotReadyReason(wallet, selectedChain);
5649
- const isBaseUnitChain = selectedChain === "SOLANA";
6275
+ const isBaseUnitChain = selectedChain === "SOLANA" || selectedChain === "POLYGON";
6276
+ const nativeDecimals = selectedChain === "POLYGON" ? 18 : 9;
5650
6277
  useEffect(() => {
5651
6278
  void refreshWalletBalance();
5652
6279
  }, [refreshWalletBalance]);
@@ -5674,7 +6301,7 @@ function SendModal({ onClose }) {
5674
6301
  const hash = transaction.step === "success" ? transaction.hash : null;
5675
6302
  const buildData = "buildData" in transaction ? transaction.buildData : null;
5676
6303
  const explorerNetwork = buildData?.summary.network?.toLowerCase().includes("testnet") ? "testnet" : buildData ? "public" : network === "testnet" ? "testnet" : "public";
5677
- const explorerUrl = !hash ? null : selectedChain === "SOLANA" ? `https://explorer.solana.com/tx/${hash}${explorerNetwork === "testnet" ? "?cluster=devnet" : ""}` : `https://stellar.expert/explorer/${explorerNetwork}/tx/${hash}`;
6304
+ const explorerUrl = !hash ? null : selectedChain === "SOLANA" ? `https://explorer.solana.com/tx/${hash}${explorerNetwork === "testnet" ? "?cluster=devnet" : ""}` : selectedChain === "POLYGON" ? `https://${explorerNetwork === "testnet" ? "amoy." : ""}polygonscan.com/tx/${hash}` : `https://stellar.expert/explorer/${explorerNetwork}/tx/${hash}`;
5678
6305
  const IN_FLIGHT_STEPS3 = [
5679
6306
  "building",
5680
6307
  "signing",
@@ -5713,33 +6340,42 @@ function SendModal({ onClose }) {
5713
6340
  setFormError("Enter a destination address");
5714
6341
  return;
5715
6342
  }
6343
+ const parsedMemo = selectedChain === "STELLAR" ? parseMemo(memoType, memo) : void 0;
6344
+ if (typeof parsedMemo === "string") {
6345
+ setFormError(parsedMemo);
6346
+ return;
6347
+ }
5716
6348
  const isNativeAsset = selectedAsset.type === "native";
5717
6349
  if (isBaseUnitChain && !isNativeAsset && (!selectedAsset.issuer || selectedAsset.decimals === void 0)) {
5718
- setFormError("This token is missing its mint or decimals and cannot be sent.");
6350
+ setFormError("This token is missing its contract or decimals and cannot be sent.");
5719
6351
  return;
5720
6352
  }
5721
6353
  let sendAmount = amount;
5722
6354
  if (isBaseUnitChain) {
5723
6355
  try {
5724
- sendAmount = toBaseUnits(amount, selectedAsset.decimals ?? 9);
6356
+ sendAmount = toBaseUnits(amount, selectedAsset.decimals ?? nativeDecimals);
5725
6357
  } catch (e) {
5726
6358
  setFormError(e instanceof Error ? e.message : "Invalid amount");
5727
6359
  return;
5728
6360
  }
5729
6361
  }
5730
6362
  setStep("tx");
5731
- if (selectedChain === "SOLANA") {
6363
+ if (selectedChain === "SOLANA" || selectedChain === "POLYGON") {
5732
6364
  await sendPayment({
5733
- chain: "SOLANA",
6365
+ chain: selectedChain,
5734
6366
  destination: destination.trim(),
5735
6367
  amount: sendAmount,
5736
- // Native SOL carries no mint; an SPL token's mint rides in `issuer`,
5737
- // which the guard above proved is present.
6368
+ // A native coin carries no token id; an SPL mint or ERC-20 contract rides
6369
+ // in `issuer`, which the guard above proved is present.
5738
6370
  ...isNativeAsset ? {} : { mint: selectedAsset.issuer }
5739
6371
  });
5740
6372
  return;
5741
6373
  }
5742
- await buildTx("payment", { destination: destination.trim(), amount, asset: assetParam(selectedAsset) });
6374
+ await buildTx(
6375
+ "payment",
6376
+ { destination: destination.trim(), amount, asset: assetParam(selectedAsset) },
6377
+ parsedMemo ? { memo: parsedMemo } : void 0
6378
+ );
5743
6379
  }
5744
6380
  function handleSignAndSend() {
5745
6381
  if (transaction.step === "built") {
@@ -5748,7 +6384,15 @@ function SendModal({ onClose }) {
5748
6384
  }
5749
6385
  function handleMax() {
5750
6386
  if (selectedAsset?.available !== null && selectedAsset?.available !== void 0) {
5751
- setAmount(selectedAsset.available);
6387
+ const paysOwnSolFee = selectedChain === "SOLANA" && selectedAsset.type === "native" && selectedAsset.sponsored !== true;
6388
+ let max = selectedAsset.available;
6389
+ if (paysOwnSolFee) {
6390
+ try {
6391
+ max = solanaMaxAfterFee(selectedAsset.available);
6392
+ } catch {
6393
+ }
6394
+ }
6395
+ setAmount(max);
5752
6396
  setFormError("");
5753
6397
  }
5754
6398
  }
@@ -5824,6 +6468,10 @@ function SendModal({ onClose }) {
5824
6468
  onMax: handleMax,
5825
6469
  onPaste: handlePaste,
5826
6470
  onDestinationChange: setDestination,
6471
+ memo,
6472
+ memoType,
6473
+ onMemoChange: setMemo,
6474
+ onMemoTypeChange: setMemoType,
5827
6475
  onSubmit: () => void handleSubmit(),
5828
6476
  onSignAndSend: handleSignAndSend,
5829
6477
  onToggleXdr: () => setShowXdr((v) => !v),
@@ -6231,7 +6879,7 @@ function SwapModalTemplate({
6231
6879
  onDone
6232
6880
  }
6233
6881
  ),
6234
- /* @__PURE__ */ jsx(PollarModalFooter, { shield: true })
6882
+ /* @__PURE__ */ jsx(PollarModalFooter, {})
6235
6883
  ]
6236
6884
  }
6237
6885
  );
@@ -6943,9 +7591,12 @@ function EarnModal({ onClose }) {
6943
7591
  var nonEmpty2 = (value) => typeof value === "string" && !!value.trim();
6944
7592
  function requiredCardStep(error) {
6945
7593
  if (!error || typeof error !== "object") return null;
6946
- const { code, body } = error;
6947
- if (code !== "SDK_CARDS_REQUIREMENT_REQUIRED" || !body || typeof body !== "object") return null;
6948
- const { cardProviderId, requirementType, optionId } = body;
7594
+ if (!("code" in error) || error.code !== "SDK_CARDS_REQUIREMENT_REQUIRED") return null;
7595
+ const body = "body" in error ? error.body : void 0;
7596
+ if (typeof body !== "object" || body === null) return null;
7597
+ const cardProviderId = "cardProviderId" in body ? body.cardProviderId : void 0;
7598
+ const requirementType = "requirementType" in body ? body.requirementType : void 0;
7599
+ const optionId = "optionId" in body ? body.optionId : void 0;
6949
7600
  if (!nonEmpty2(cardProviderId) || !nonEmpty2(optionId)) return null;
6950
7601
  if (requirementType === "KYC" || requirementType === "FORM" || requirementType === "REGISTRY_CHECK" || requirementType === "PROVIDER_REGISTRATION") {
6951
7602
  return { cardProviderId, type: requirementType, optionId };
@@ -7599,15 +8250,17 @@ function CardModal({ onClose }) {
7599
8250
  transactions === null && /* @__PURE__ */ jsx("p", { className: "pollar-card-note", children: "Loading..." }),
7600
8251
  transactions?.length === 0 && /* @__PURE__ */ jsx("p", { className: "pollar-card-note", children: "No movements yet." }),
7601
8252
  transactions?.map((tx) => {
7602
- const purchase = tx.purchase;
7603
- const local = purchase?.localAmount && purchase.localCurrency && purchase.localCurrency !== "USD" ? money(String(purchase.localAmount), purchase.localCurrency) : null;
8253
+ const localAmount = typeof tx.purchase?.localAmount === "number" ? tx.purchase.localAmount : null;
8254
+ const localCurrency = typeof tx.purchase?.localCurrency === "string" ? tx.purchase.localCurrency : null;
8255
+ const purchaseStatus = typeof tx.purchase?.status === "string" ? tx.purchase.status : null;
8256
+ const local = localAmount && localCurrency && localCurrency !== "USD" ? money(String(localAmount), localCurrency) : null;
7604
8257
  return /* @__PURE__ */ jsxs("div", { className: "pollar-card-tx", children: [
7605
8258
  /* @__PURE__ */ jsxs("div", { children: [
7606
8259
  tx.merchantName ?? TX_LABELS[tx.type] ?? tx.type,
7607
8260
  /* @__PURE__ */ jsxs("span", { className: "pollar-card-tx-meta", children: [
7608
8261
  new Date(tx.occurredAt).toLocaleDateString(),
7609
8262
  tx.merchantName ? ` \xB7 ${TX_LABELS[tx.type] ?? tx.type}` : "",
7610
- purchase?.status && purchase.status !== "completed" ? ` \xB7 ${purchase.status}` : ""
8263
+ purchaseStatus && purchaseStatus !== "completed" ? ` \xB7 ${purchaseStatus}` : ""
7611
8264
  ] })
7612
8265
  ] }),
7613
8266
  /* @__PURE__ */ jsxs("div", { className: "pollar-card-tx-amount", "data-direction": tx.direction ?? void 0, children: [
@@ -7977,6 +8630,843 @@ function SessionsModal({ onClose }) {
7977
8630
  }
7978
8631
  ) });
7979
8632
  }
8633
+ function useCosign() {
8634
+ const { getClient, isAuthenticated } = usePollar();
8635
+ const [state, setState] = useState({ pending: [] });
8636
+ const [loaded, setLoaded] = useState(false);
8637
+ useEffect(() => getClient().onCosignStateChange(setState), [getClient]);
8638
+ const refresh = useCallback(async () => {
8639
+ if (!isAuthenticated) return;
8640
+ try {
8641
+ await getClient().cosign.pending();
8642
+ } finally {
8643
+ setLoaded(true);
8644
+ }
8645
+ }, [getClient, isAuthenticated]);
8646
+ useEffect(() => {
8647
+ void refresh();
8648
+ }, [refresh]);
8649
+ return {
8650
+ pending: state.pending,
8651
+ ...state.lastResolved ? { lastResolved: state.lastResolved } : {},
8652
+ loaded,
8653
+ refresh,
8654
+ approve: useCallback((request) => getClient().cosign.approve(request), [getClient]),
8655
+ reject: useCallback((request) => getClient().cosign.reject(request), [getClient]),
8656
+ verify: useCallback((request) => getClient().cosign.verify(request), [getClient]),
8657
+ inspectQuorum: useCallback(() => getClient().cosign.inspectQuorum(), [getClient]),
8658
+ devices: useCallback(() => getClient().cosign.devices(), [getClient]),
8659
+ revokeDevice: useCallback(
8660
+ (deviceId, options) => getClient().cosign.revokeDevice(deviceId, options),
8661
+ [getClient]
8662
+ ),
8663
+ switchCustody: useCallback((to) => getClient().cosign.switchCustody(to), [getClient])
8664
+ };
8665
+ }
8666
+ var OPERATION_LABELS = {
8667
+ payment: "Send",
8668
+ path_payment_strict_send: "Swap",
8669
+ change_trust: "Add asset",
8670
+ create_account: "Create account",
8671
+ invoke_contract: "Contract call",
8672
+ manage_data: "Manage data"
8673
+ };
8674
+ function labelOf(request) {
8675
+ const op = request.operation ?? "";
8676
+ return OPERATION_LABELS[op] ?? (op ? op.replace(/_/g, " ") : "Transaction");
8677
+ }
8678
+ function expiresIn(request) {
8679
+ const ms = new Date(request.expiresAt).getTime() - Date.now();
8680
+ if (!Number.isFinite(ms) || ms <= 0) return "expired";
8681
+ const h = Math.floor(ms / 36e5);
8682
+ const m = Math.floor(ms % 36e5 / 6e4);
8683
+ return h > 0 ? `${h}h ${m}m` : `${m}m`;
8684
+ }
8685
+ function readableAmount(details) {
8686
+ const { amount, decimals, symbol } = details;
8687
+ if (typeof amount !== "string" || typeof decimals !== "number" || typeof symbol !== "string") return null;
8688
+ try {
8689
+ return { amount: fromBaseUnits(amount, decimals), symbol };
8690
+ } catch {
8691
+ return null;
8692
+ }
8693
+ }
8694
+ function detailRows(request) {
8695
+ const details = request.details ?? {};
8696
+ const rows = [];
8697
+ const push = (label, value) => {
8698
+ if (value === void 0 || value === null || value === "") return;
8699
+ rows.push([
8700
+ label,
8701
+ typeof value === "string" && value.length > 24 && /^[GC][A-Z2-7]{55}$/.test(value) ? cropAddress(value) : String(value)
8702
+ ]);
8703
+ };
8704
+ push("Destination", details.destination);
8705
+ const readable = readableAmount(details);
8706
+ push("Amount", readable ? `${readable.amount} ${readable.symbol}` : details.amount);
8707
+ push("Asset", details.asset ?? details.assetCode ?? readable?.symbol);
8708
+ push("Network", request.network);
8709
+ return rows;
8710
+ }
8711
+ function CloseIcon2() {
8712
+ return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "m3 3 8 8M11 3l-8 8", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
8713
+ }
8714
+ function CosignModalTemplate({
8715
+ theme,
8716
+ accentColor,
8717
+ styleOverrides,
8718
+ pending,
8719
+ current,
8720
+ verification,
8721
+ busy,
8722
+ error,
8723
+ approveBlocked = false,
8724
+ lastResolved,
8725
+ explorerUrl,
8726
+ onSelect,
8727
+ onApprove,
8728
+ onReject,
8729
+ onClose
8730
+ }) {
8731
+ const cssVars = buildModalCssVars(theme, accentColor, styleOverrides);
8732
+ const verified = verification?.ok === true;
8733
+ const canVote = !!current && verified && !busy && !approveBlocked;
8734
+ return /* @__PURE__ */ jsxs(
8735
+ "div",
8736
+ {
8737
+ className: "pollar-modal-card pollar-cosign-modal",
8738
+ "data-theme": theme,
8739
+ style: cssVars,
8740
+ onClick: (e) => e.stopPropagation(),
8741
+ children: [
8742
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header", children: [
8743
+ /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: "Confirm with your device" }),
8744
+ /* @__PURE__ */ jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx(CloseIcon2, {}) }) })
8745
+ ] }),
8746
+ pending.length === 0 && /* @__PURE__ */ jsx("div", { className: "pollar-cosign-empty", children: lastResolved ? /* @__PURE__ */ jsxs(Fragment, { children: [
8747
+ lastResolved.status === "APPROVED" && /* @__PURE__ */ jsxs("svg", { className: "pollar-cosign-approved-icon", width: "44", height: "44", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: [
8748
+ /* @__PURE__ */ jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }),
8749
+ /* @__PURE__ */ jsx(
8750
+ "path",
8751
+ {
8752
+ d: "M3.5 7l2.5 2.5 4.5-5",
8753
+ stroke: "white",
8754
+ strokeWidth: "1.2",
8755
+ strokeLinecap: "round",
8756
+ strokeLinejoin: "round"
8757
+ }
8758
+ )
8759
+ ] }),
8760
+ /* @__PURE__ */ jsx("p", { className: "pollar-cosign-empty-title", children: lastResolved.status === "APPROVED" ? "Approved" : lastResolved.status === "REJECTED" ? "Rejected" : lastResolved.status.toLowerCase() }),
8761
+ /* @__PURE__ */ jsx("p", { className: "pollar-cosign-muted", children: lastResolved.submit?.hash ? /* @__PURE__ */ jsxs(Fragment, { children: [
8762
+ "Sent. Transaction",
8763
+ " ",
8764
+ explorerUrl ? /* @__PURE__ */ jsx("a", { className: "pollar-cosign-tx-link", href: explorerUrl, target: "_blank", rel: "noopener noreferrer", children: cropAddress(lastResolved.submit.hash) }) : cropAddress(lastResolved.submit.hash)
8765
+ ] }) : lastResolved.signedXdr ? "Signed. The app will send it." : "Nothing was signed." })
8766
+ ] }) : /* @__PURE__ */ jsx("p", { className: "pollar-cosign-muted", children: "Nothing waits for your approval." }) }),
8767
+ pending.length > 1 && /* @__PURE__ */ jsx("ul", { className: "pollar-cosign-list", children: pending.map((r) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsxs(
8768
+ "button",
8769
+ {
8770
+ type: "button",
8771
+ className: `pollar-cosign-item${current?.id === r.id ? " pollar-cosign-item-active" : ""}`,
8772
+ onClick: () => onSelect(r),
8773
+ children: [
8774
+ /* @__PURE__ */ jsx("span", { children: labelOf(r) }),
8775
+ /* @__PURE__ */ jsx("span", { className: "pollar-cosign-muted", children: expiresIn(r) })
8776
+ ]
8777
+ }
8778
+ ) }, r.id)) }),
8779
+ current && /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-detail", children: [
8780
+ /* @__PURE__ */ jsx("p", { className: "pollar-cosign-detail-title", children: labelOf(current) }),
8781
+ current.summary && /* @__PURE__ */ jsx("p", { className: "pollar-cosign-muted", children: current.summary }),
8782
+ /* @__PURE__ */ jsxs("dl", { className: "pollar-cosign-rows", children: [
8783
+ detailRows(current).map(([k, v]) => /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-row", children: [
8784
+ /* @__PURE__ */ jsx("dt", { children: k }),
8785
+ /* @__PURE__ */ jsx("dd", { children: v })
8786
+ ] }, k)),
8787
+ /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-row", children: [
8788
+ /* @__PURE__ */ jsx("dt", { children: "Expires in" }),
8789
+ /* @__PURE__ */ jsx("dd", { children: expiresIn(current) })
8790
+ ] })
8791
+ ] }),
8792
+ /* @__PURE__ */ jsxs(
8793
+ "div",
8794
+ {
8795
+ className: `pollar-cosign-verify pollar-cosign-verify-${verification ? verification.ok ? "ok" : "bad" : "pending"}`,
8796
+ children: [
8797
+ !verification && "Checking the transaction against Turnkey...",
8798
+ verification?.ok && "Verified: this is exactly what will be signed.",
8799
+ verification && !verification.ok && `Not verified: ${verification.reason}`
8800
+ ]
8801
+ }
8802
+ ),
8803
+ error && /* @__PURE__ */ jsx("div", { className: "pollar-cosign-error", children: error }),
8804
+ /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-actions", children: [
8805
+ /* @__PURE__ */ jsx(
8806
+ "button",
8807
+ {
8808
+ type: "button",
8809
+ className: "pollar-cosign-btn pollar-cosign-btn-secondary",
8810
+ disabled: !!busy,
8811
+ onClick: () => onReject(current),
8812
+ children: busy === "rejecting" ? "Rejecting..." : "Reject"
8813
+ }
8814
+ ),
8815
+ /* @__PURE__ */ jsx(
8816
+ "button",
8817
+ {
8818
+ type: "button",
8819
+ className: "pollar-cosign-btn pollar-cosign-btn-primary",
8820
+ disabled: !canVote,
8821
+ onClick: () => onApprove(current),
8822
+ children: busy === "approving" ? "Approving..." : "Approve"
8823
+ }
8824
+ )
8825
+ ] })
8826
+ ] }),
8827
+ /* @__PURE__ */ jsx(PollarModalFooter, {})
8828
+ ]
8829
+ }
8830
+ );
8831
+ }
8832
+ function CosignModal({ onClose }) {
8833
+ const { getClient, styles, network } = usePollar();
8834
+ const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
8835
+ const { pending, lastResolved, approve, reject, verify, inspectQuorum } = useCosign();
8836
+ const [selectedId, setSelectedId] = useState(null);
8837
+ const [verification, setVerification] = useState(null);
8838
+ const [busy, setBusy] = useState(null);
8839
+ const [error, setError] = useState(null);
8840
+ const [approveFailedFor, setApproveFailedFor] = useState(null);
8841
+ const [quorumFailure, setQuorumFailure] = useState(null);
8842
+ useEffect(() => {
8843
+ let cancelled = false;
8844
+ inspectQuorum().then((q) => {
8845
+ if (!cancelled && !q.ok) setQuorumFailure(q.reason);
8846
+ }).catch(() => void 0);
8847
+ return () => {
8848
+ cancelled = true;
8849
+ };
8850
+ }, [inspectQuorum]);
8851
+ const current = pending.find((r) => r.id === selectedId) ?? pending[0] ?? null;
8852
+ const currentRef = useRef(current);
8853
+ currentRef.current = current;
8854
+ const currentId = current?.id ?? null;
8855
+ useEffect(() => {
8856
+ const request = currentRef.current;
8857
+ if (!request) return;
8858
+ setVerification(null);
8859
+ setError(null);
8860
+ setApproveFailedFor(null);
8861
+ let cancelled = false;
8862
+ verify(request).then((v) => {
8863
+ if (!cancelled) setVerification(v);
8864
+ }).catch((err) => {
8865
+ if (!cancelled) setVerification({ ok: false, reason: err instanceof Error ? err.message : String(err) });
8866
+ });
8867
+ return () => {
8868
+ cancelled = true;
8869
+ };
8870
+ }, [currentId, verify]);
8871
+ const onCloseRef = useRef(onClose);
8872
+ onCloseRef.current = onClose;
8873
+ useEffect(() => {
8874
+ return getClient().onAuthStateChange((authState) => {
8875
+ if (authState.step === "idle") onCloseRef.current();
8876
+ });
8877
+ }, [getClient]);
8878
+ const run = useCallback(
8879
+ async (kind, request, action) => {
8880
+ setBusy(kind);
8881
+ setError(null);
8882
+ try {
8883
+ await action(request);
8884
+ } catch (err) {
8885
+ setError(transactionErrorMessage(isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null));
8886
+ if (kind === "approving") setApproveFailedFor(request.id);
8887
+ } finally {
8888
+ setBusy(null);
8889
+ }
8890
+ },
8891
+ []
8892
+ );
8893
+ return /* @__PURE__ */ jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsx(
8894
+ CosignModalTemplate,
8895
+ {
8896
+ theme,
8897
+ accentColor,
8898
+ styleOverrides,
8899
+ pending,
8900
+ current,
8901
+ verification: current ? quorumFailure ? { ok: false, reason: quorumFailure } : verification : null,
8902
+ busy,
8903
+ error,
8904
+ approveBlocked: !!current && approveFailedFor === current.id,
8905
+ ...lastResolved ? { lastResolved } : {},
8906
+ explorerUrl: lastResolved?.submit?.hash ? explorerTxUrl(lastResolved.network, lastResolved.submit.hash, network) : null,
8907
+ onSelect: (r) => setSelectedId(r.id),
8908
+ onApprove: (r) => void run("approving", r, approve),
8909
+ onReject: (r) => void run("rejecting", r, reject),
8910
+ onClose
8911
+ }
8912
+ ) });
8913
+ }
8914
+ var CHAIN_NAME3 = { STELLAR: "Stellar", SOLANA: "Solana", POLYGON: "Polygon" };
8915
+ var short = (address) => `${address.slice(0, 6)}...${address.slice(-4)}`;
8916
+ var SELF_CUSTODY_RISKS = "In a self-custody wallet only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign.";
8917
+ function CustodyMigrationModalTemplate(p) {
8918
+ const cssVars = buildModalCssVars(p.theme, p.accentColor, p.styleOverrides);
8919
+ const [pick, setPick] = useState(p.state.choice ? null : p.state.toCustody);
8920
+ const [risksAccepted, setRisksAccepted] = useState(false);
8921
+ const custody = p.state.choice ? pick : p.state.toCustody;
8922
+ const canMove = !!custody && (custody !== "SELF_CUSTODY" || risksAccepted) && !p.busy;
8923
+ const moving = p.state.wallets.filter((w) => w.required);
8924
+ const changing = moving.filter((w) => w.addressChanges);
8925
+ const failed = moving.some((w) => w.status === "FAILED");
8926
+ return /* @__PURE__ */ jsxs("div", { className: "pollar-modal-card pollar-recovery-modal", "data-theme": p.theme, style: cssVars, children: [
8927
+ /* @__PURE__ */ jsx("div", { className: "pollar-modal-header", children: /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: "Move your wallets to this device" }) }),
8928
+ /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-body", children: [
8929
+ /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Until now Pollar held the keys of your wallets. This app now keeps wallets on its users' devices, so they move here, with everything they hold." }),
8930
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
8931
+ /* @__PURE__ */ jsx("h3", { children: "What moves" }),
8932
+ moving.map((w) => /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item", children: [
8933
+ /* @__PURE__ */ jsxs("span", { children: [
8934
+ CHAIN_NAME3[w.chain] ?? w.chain,
8935
+ " \xB7 ",
8936
+ short(w.address)
8937
+ ] }),
8938
+ /* @__PURE__ */ jsx("span", { className: "pollar-recovery-tag", children: w.addressChanges ? "New address" : "Same address" })
8939
+ ] }, w.chain))
8940
+ ] }),
8941
+ changing.length > 0 && /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: [
8942
+ "Your ",
8943
+ changing.map((w) => CHAIN_NAME3[w.chain] ?? w.chain).join(" and "),
8944
+ " address changes: every token and the balance go to a new address on this device. Stop sharing the old one; anything sent there later needs help from support to recover."
8945
+ ] }),
8946
+ p.state.choice && /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
8947
+ /* @__PURE__ */ jsx("h3", { children: "Choose how your wallet signs" }),
8948
+ /* @__PURE__ */ jsxs("label", { className: "pollar-recovery-check", children: [
8949
+ /* @__PURE__ */ jsx("input", { type: "radio", name: "custody", checked: pick === "COSIGNED", onChange: () => setPick("COSIGNED") }),
8950
+ " ",
8951
+ "Co-signed: you and the app approve every signature together, and the wallet can be recovered with help."
8952
+ ] }),
8953
+ /* @__PURE__ */ jsxs("label", { className: "pollar-recovery-check", children: [
8954
+ /* @__PURE__ */ jsx("input", { type: "radio", name: "custody", checked: pick === "SELF_CUSTODY", onChange: () => setPick("SELF_CUSTODY") }),
8955
+ " ",
8956
+ "Self-custody: only your device signs. Nobody else can move or recover your funds."
8957
+ ] })
8958
+ ] }),
8959
+ custody === "SELF_CUSTODY" && /* @__PURE__ */ jsxs(Fragment, { children: [
8960
+ /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: SELF_CUSTODY_RISKS }),
8961
+ /* @__PURE__ */ jsxs("label", { className: "pollar-recovery-check", children: [
8962
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: risksAccepted, onChange: (e) => setRisksAccepted(e.target.checked) }),
8963
+ " I understand and accept these risks"
8964
+ ] })
8965
+ ] }),
8966
+ failed && !p.error && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-error", role: "alert", children: "The last attempt did not finish. What did not move is still where it was; try again." }),
8967
+ p.blockedReasons && /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: [
8968
+ "A wallet cannot move while something still uses it: ",
8969
+ p.blockedReasons.join(", "),
8970
+ ". Let it finish and try again."
8971
+ ] }),
8972
+ /* @__PURE__ */ jsx(
8973
+ "button",
8974
+ {
8975
+ type: "button",
8976
+ className: "pollar-recovery-btn pollar-recovery-btn-primary",
8977
+ onClick: () => custody && p.onMove(p.state.choice ? custody : void 0),
8978
+ disabled: !canMove,
8979
+ children: p.busy ? "Moving your wallets..." : "Move my wallets"
8980
+ }
8981
+ ),
8982
+ p.blockedReasons && /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-recovery-btn pollar-recovery-btn-quiet", onClick: p.onLater, disabled: p.busy, children: "Continue for now" }),
8983
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-recovery-btn pollar-recovery-btn-quiet", onClick: p.onLogout, disabled: p.busy, children: "Log out" }),
8984
+ p.error && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-error", role: "alert", children: p.error })
8985
+ ] }),
8986
+ /* @__PURE__ */ jsx(PollarModalFooter, {})
8987
+ ] });
8988
+ }
8989
+ function CustodyMigrationModal({ state, onMoved, onLater }) {
8990
+ const { getClient, styles } = usePollar();
8991
+ const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
8992
+ const [busy, setBusy] = useState(false);
8993
+ const [error, setError] = useState(null);
8994
+ const [blockedReasons, setBlockedReasons] = useState(null);
8995
+ async function move(custody) {
8996
+ setBusy(true);
8997
+ setError(null);
8998
+ setBlockedReasons(null);
8999
+ try {
9000
+ await getClient().cosign.migrateWallets(custody);
9001
+ onMoved();
9002
+ } catch (err) {
9003
+ if (isPollarApiError(err) && err.code === "SDK_CUSTODY_MIGRATION_BLOCKED") {
9004
+ const reasons = err.body.reasons;
9005
+ setBlockedReasons(Array.isArray(reasons) ? reasons.map(String) : []);
9006
+ return;
9007
+ }
9008
+ setError(transactionErrorMessage(isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null));
9009
+ } finally {
9010
+ setBusy(false);
9011
+ }
9012
+ }
9013
+ return /* @__PURE__ */ jsx("div", { className: "pollar-overlay", style: overlayStyle, children: /* @__PURE__ */ jsx(
9014
+ CustodyMigrationModalTemplate,
9015
+ {
9016
+ theme,
9017
+ accentColor,
9018
+ styleOverrides,
9019
+ state,
9020
+ busy,
9021
+ error,
9022
+ blockedReasons,
9023
+ onMove: (custody) => void move(custody),
9024
+ onLater,
9025
+ onLogout: () => void getClient().logout()
9026
+ }
9027
+ ) });
9028
+ }
9029
+ function CloseIcon3() {
9030
+ return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "m3 3 8 8M11 3l-8 8", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
9031
+ }
9032
+ var shortKey = (key) => `${key.slice(0, 6)}...${key.slice(-4)}`;
9033
+ function Button(props) {
9034
+ const running = props.busy === props.id;
9035
+ return /* @__PURE__ */ jsx(
9036
+ "button",
9037
+ {
9038
+ type: "button",
9039
+ className: `pollar-recovery-btn pollar-recovery-btn-${props.kind ?? "quiet"}`,
9040
+ onClick: props.onClick,
9041
+ disabled: props.disabled || props.busy !== null,
9042
+ children: running ? props.busyLabel ?? "Working..." : props.label
9043
+ }
9044
+ );
9045
+ }
9046
+ function CodeForm(props) {
9047
+ const [code, setCode] = useState("");
9048
+ return /* @__PURE__ */ jsxs(
9049
+ "form",
9050
+ {
9051
+ className: "pollar-recovery-row",
9052
+ onSubmit: (e) => {
9053
+ e.preventDefault();
9054
+ if (code.trim()) props.onSubmit(code);
9055
+ },
9056
+ children: [
9057
+ /* @__PURE__ */ jsx(
9058
+ "input",
9059
+ {
9060
+ className: "pollar-recovery-input",
9061
+ value: code,
9062
+ onChange: (e) => setCode(e.target.value),
9063
+ placeholder: "Code from the email",
9064
+ autoComplete: "one-time-code",
9065
+ "aria-label": "Code from the email"
9066
+ }
9067
+ ),
9068
+ /* @__PURE__ */ jsx(
9069
+ Button,
9070
+ {
9071
+ busy: props.busy,
9072
+ id: props.id,
9073
+ label: props.label,
9074
+ kind: "primary",
9075
+ onClick: () => code.trim() && props.onSubmit(code)
9076
+ }
9077
+ )
9078
+ ]
9079
+ }
9080
+ );
9081
+ }
9082
+ function RecoveryModalTemplate(p) {
9083
+ const cssVars = buildModalCssVars(p.theme, p.accentColor, p.styleOverrides);
9084
+ const [exportAccepted, setExportAccepted] = useState(false);
9085
+ const [risksAccepted, setRisksAccepted] = useState(false);
9086
+ const state = p.state;
9087
+ const locked = !!state && state.required && !state.configured;
9088
+ const risksPending = locked && p.selfCustodyRisks && !risksAccepted;
9089
+ const lastMethod = !!state && state.passkeys.length + (state.email.verified ? 1 : 0) + (state.devices.length >= 2 ? 1 : 0) <= 1;
9090
+ let body;
9091
+ if (!state) {
9092
+ body = /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Loading your wallet's recovery methods..." });
9093
+ } else if (!state.required) {
9094
+ body = /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "This wallet doesn't need recovery methods: it isn't secured by your device." });
9095
+ } else if (!p.thisDeviceEnrolled && p.view !== "recover-email") {
9096
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9097
+ /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "status", children: "This device isn't part of your wallet yet. Get back in with one of your recovery methods, or ask a device that already has the wallet to add this one." }),
9098
+ state.passkeys.length > 0 && p.passkeyAvailable && /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "recover-passkey", label: "Use a passkey", kind: "primary", onClick: p.onRecoverWithPasskey }),
9099
+ state.email.verified && /* @__PURE__ */ jsx(
9100
+ Button,
9101
+ {
9102
+ busy: p.busy,
9103
+ id: "recover-send",
9104
+ label: `Email a code to ${state.email.address ?? "your email"}`,
9105
+ onClick: () => p.onView("recover-email")
9106
+ }
9107
+ ),
9108
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "enroll", label: "Ask another device to add this one", onClick: p.onRequestEnrollment })
9109
+ ] });
9110
+ } else if (risksPending && p.view === "overview") {
9111
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9112
+ /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "This is a self-custody wallet: only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign." }),
9113
+ /* @__PURE__ */ jsx(
9114
+ Button,
9115
+ {
9116
+ busy: p.busy,
9117
+ id: "accept-risks",
9118
+ label: "I understand and accept these risks",
9119
+ kind: "primary",
9120
+ onClick: () => setRisksAccepted(true)
9121
+ }
9122
+ )
9123
+ ] });
9124
+ } else if (p.view === "recover-email") {
9125
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9126
+ /* @__PURE__ */ jsxs("p", { className: "pollar-recovery-muted", children: [
9127
+ "We'll email a code to ",
9128
+ state.email.address,
9129
+ ". The code goes straight to the wallet provider, encrypted on this device."
9130
+ ] }),
9131
+ !p.codeSent ? /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "recover-send", label: "Send code", kind: "primary", onClick: p.onSendRecoveryCode }) : /* @__PURE__ */ jsx(CodeForm, { busy: p.busy, id: "recover-code", label: "Recover", onSubmit: p.onRecoverWithCode }),
9132
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9133
+ ] });
9134
+ } else if (p.view === "email") {
9135
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9136
+ /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Confirm an email you control. If you lose this device, a code sent there lets you add a new one. Anyone who controls this inbox could do the same, so use one that is well protected." }),
9137
+ !p.codeSent ? /* @__PURE__ */ jsxs(
9138
+ "form",
9139
+ {
9140
+ className: "pollar-recovery-row",
9141
+ onSubmit: (e) => {
9142
+ e.preventDefault();
9143
+ p.onSendEmailCode();
9144
+ },
9145
+ children: [
9146
+ /* @__PURE__ */ jsx(
9147
+ "input",
9148
+ {
9149
+ className: "pollar-recovery-input",
9150
+ type: "email",
9151
+ value: p.emailDraft,
9152
+ onChange: (e) => p.onEmailDraft(e.target.value),
9153
+ placeholder: "you@example.com",
9154
+ "aria-label": "Email"
9155
+ }
9156
+ ),
9157
+ /* @__PURE__ */ jsx(
9158
+ Button,
9159
+ {
9160
+ busy: p.busy,
9161
+ id: "email-send",
9162
+ label: "Send code",
9163
+ kind: "primary",
9164
+ onClick: p.onSendEmailCode,
9165
+ disabled: !p.emailDraft.trim()
9166
+ }
9167
+ )
9168
+ ]
9169
+ }
9170
+ ) : /* @__PURE__ */ jsx(CodeForm, { busy: p.busy, id: "email-confirm", label: "Confirm", onSubmit: p.onConfirmEmail }),
9171
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9172
+ ] });
9173
+ } else if (p.view === "export") {
9174
+ body = p.phrase ? /* @__PURE__ */ jsxs(Fragment, { children: [
9175
+ /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "Anyone with these words controls every account in this wallet. Write them down offline and never share them." }),
9176
+ /* @__PURE__ */ jsx("pre", { className: "pollar-recovery-phrase", children: p.phrase }),
9177
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "hide", label: "Hide", kind: "primary", onClick: () => p.onView("overview") })
9178
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
9179
+ /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Your recovery phrase lets you take this wallet to another app. It is decrypted only on this device; the app and Pollar never see it." }),
9180
+ /* @__PURE__ */ jsxs("label", { className: "pollar-recovery-check", children: [
9181
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: exportAccepted, onChange: (e) => setExportAccepted(e.target.checked) }),
9182
+ " I understand that whoever sees the phrase controls the wallet"
9183
+ ] }),
9184
+ /* @__PURE__ */ jsx(
9185
+ Button,
9186
+ {
9187
+ busy: p.busy,
9188
+ id: "export",
9189
+ label: "Show recovery phrase",
9190
+ kind: "danger",
9191
+ onClick: p.onExport,
9192
+ disabled: !exportAccepted
9193
+ }
9194
+ ),
9195
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9196
+ ] });
9197
+ } else {
9198
+ const enrollments = p.devices?.enrollments ?? [];
9199
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9200
+ locked ? /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "Your wallet is locked until you add a way to recover it. Without one, losing this device means losing the funds, and nobody can get them back, not the app and not Pollar. Add a passkey, a second device or your email." }) : /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-ok", role: "status", children: "Your wallet can be recovered if you lose this device." }),
9201
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
9202
+ /* @__PURE__ */ jsx("h3", { children: "Passkeys" }),
9203
+ state.passkeys.length === 0 && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "No passkey yet." }),
9204
+ state.passkeys.map((pk) => /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item", children: [
9205
+ /* @__PURE__ */ jsx("span", { children: pk.name }),
9206
+ /* @__PURE__ */ jsx(
9207
+ Button,
9208
+ {
9209
+ busy: p.busy,
9210
+ id: `passkey:${pk.authenticatorId}`,
9211
+ label: "Remove",
9212
+ kind: "danger",
9213
+ onClick: () => p.onRemovePasskey(pk.authenticatorId),
9214
+ disabled: lastMethod
9215
+ }
9216
+ )
9217
+ ] }, pk.authenticatorId)),
9218
+ p.passkeyAvailable ? /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "passkey", label: "Add a passkey", onClick: p.onAddPasskey }) : /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Passkeys aren't available on this device." })
9219
+ ] }),
9220
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
9221
+ /* @__PURE__ */ jsx("h3", { children: "Email" }),
9222
+ state.email.verified ? /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item", children: [
9223
+ /* @__PURE__ */ jsx("span", { children: state.email.address }),
9224
+ /* @__PURE__ */ jsx("span", { className: "pollar-recovery-tag", children: "Confirmed" })
9225
+ ] }) : /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "email", label: "Confirm an email", onClick: () => p.onView("email") })
9226
+ ] }),
9227
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
9228
+ /* @__PURE__ */ jsx("h3", { children: "Devices" }),
9229
+ (p.devices?.devices ?? []).map((d) => /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item", children: [
9230
+ /* @__PURE__ */ jsx("span", { children: d.label ?? shortKey(d.publicKey) }),
9231
+ d.publicKey.toLowerCase() === p.thisDeviceKey && /* @__PURE__ */ jsx("span", { className: "pollar-recovery-tag", children: "This device" })
9232
+ ] }, d.id)),
9233
+ enrollments.map((e) => /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item pollar-recovery-item-pending", children: [
9234
+ /* @__PURE__ */ jsxs("span", { children: [
9235
+ e.label ?? "A device",
9236
+ " wants to join (",
9237
+ shortKey(e.publicKey),
9238
+ ")"
9239
+ ] }),
9240
+ /* @__PURE__ */ jsxs("span", { className: "pollar-recovery-actions", children: [
9241
+ /* @__PURE__ */ jsx(
9242
+ Button,
9243
+ {
9244
+ busy: p.busy,
9245
+ id: `approve:${e.id}`,
9246
+ label: "Approve",
9247
+ kind: "primary",
9248
+ onClick: () => p.onApproveDevice(e.id)
9249
+ }
9250
+ ),
9251
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: `reject:${e.id}`, label: "Reject", onClick: () => p.onRejectDevice(e.id) })
9252
+ ] })
9253
+ ] }, e.id)),
9254
+ /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: 'To add another device, sign in there and choose "Ask another device to add this one"; it shows up here to approve. Only approve a device you are holding.' })
9255
+ ] }),
9256
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
9257
+ /* @__PURE__ */ jsx("h3", { children: "Recovery phrase" }),
9258
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "export", label: "Export recovery phrase", onClick: () => p.onView("export") })
9259
+ ] })
9260
+ ] });
9261
+ }
9262
+ return /* @__PURE__ */ jsxs(
9263
+ "div",
9264
+ {
9265
+ className: "pollar-modal-card pollar-recovery-modal",
9266
+ "data-theme": p.theme,
9267
+ style: cssVars,
9268
+ onClick: (e) => e.stopPropagation(),
9269
+ children: [
9270
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header", children: [
9271
+ /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: "Wallet recovery" }),
9272
+ /* @__PURE__ */ jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: p.onClose, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx(CloseIcon3, {}) }) })
9273
+ ] }),
9274
+ /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-body", children: [
9275
+ body,
9276
+ p.notice && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-notice", role: "status", children: p.notice }),
9277
+ p.error && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-error", role: "alert", children: p.error })
9278
+ ] }),
9279
+ /* @__PURE__ */ jsx(PollarModalFooter, {})
9280
+ ]
9281
+ }
9282
+ );
9283
+ }
9284
+ function errorText2(err) {
9285
+ return transactionErrorMessage(isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null);
9286
+ }
9287
+ function RecoveryModal({ onClose }) {
9288
+ const { getClient, styles, refreshRecovery, appConfig } = usePollar();
9289
+ const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
9290
+ const recovery = getClient().cosign.recovery;
9291
+ const [state, setState] = useState(null);
9292
+ const [devices, setDevices] = useState(null);
9293
+ const [thisDeviceKey, setThisDeviceKey] = useState(null);
9294
+ const [view, setView] = useState("overview");
9295
+ const [busy, setBusy] = useState(null);
9296
+ const [error, setError] = useState(null);
9297
+ const [notice, setNotice] = useState(null);
9298
+ const [emailDraft, setEmailDraft] = useState("");
9299
+ const [codeSent, setCodeSent] = useState(false);
9300
+ const [phrase, setPhrase] = useState(null);
9301
+ const load = useCallback(async () => {
9302
+ const client = getClient();
9303
+ const [nextState, nextDevices, key] = await Promise.all([
9304
+ client.cosign.recovery.status(),
9305
+ client.cosign.devices(),
9306
+ client.cosign.publicKeyHex()
9307
+ ]);
9308
+ setState(nextState);
9309
+ setDevices(nextDevices);
9310
+ setThisDeviceKey(key.toLowerCase());
9311
+ if (nextState.email.address) setEmailDraft((d) => d || nextState.email.address);
9312
+ refreshRecovery();
9313
+ }, [getClient, refreshRecovery]);
9314
+ useEffect(() => {
9315
+ load().catch((err) => setError(errorText2(err)));
9316
+ }, [load]);
9317
+ const onCloseRef = useRef(onClose);
9318
+ onCloseRef.current = onClose;
9319
+ useEffect(
9320
+ () => getClient().onAuthStateChange((authState) => {
9321
+ if (authState.step === "idle") onCloseRef.current();
9322
+ }),
9323
+ [getClient]
9324
+ );
9325
+ const run = useCallback(
9326
+ async (key, action, after) => {
9327
+ setBusy(key);
9328
+ setError(null);
9329
+ setNotice(null);
9330
+ try {
9331
+ await action();
9332
+ await load();
9333
+ if (after?.notice) setNotice(after.notice);
9334
+ if (after?.view) setView(after.view);
9335
+ } catch (err) {
9336
+ setError(errorText2(err));
9337
+ } finally {
9338
+ setBusy(null);
9339
+ }
9340
+ },
9341
+ [load]
9342
+ );
9343
+ const enrolled = !!thisDeviceKey && !!devices?.devices.some((d) => d.publicKey.toLowerCase() === thisDeviceKey);
9344
+ return /* @__PURE__ */ jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsx(
9345
+ RecoveryModalTemplate,
9346
+ {
9347
+ theme,
9348
+ accentColor,
9349
+ styleOverrides,
9350
+ state,
9351
+ devices,
9352
+ thisDeviceKey,
9353
+ thisDeviceEnrolled: enrolled,
9354
+ passkeyAvailable: recovery.passkeyAvailable,
9355
+ selfCustodyRisks: appConfig.application?.walletCustodyMode === "SELF_CUSTODY" && devices?.custody === "SELF_CUSTODY",
9356
+ view,
9357
+ busy,
9358
+ error,
9359
+ notice,
9360
+ emailDraft,
9361
+ codeSent,
9362
+ phrase,
9363
+ onView: (next) => {
9364
+ setError(null);
9365
+ setNotice(null);
9366
+ setCodeSent(false);
9367
+ if (next !== "export") setPhrase(null);
9368
+ setView(next);
9369
+ },
9370
+ onEmailDraft: setEmailDraft,
9371
+ onAddPasskey: () => void run("passkey", () => recovery.addPasskey(), { notice: "Passkey added." }),
9372
+ onRemovePasskey: (id) => void run(`passkey:${id}`, () => recovery.removePasskey(id)),
9373
+ onSendEmailCode: () => void run("email-send", async () => {
9374
+ await recovery.startEmail(emailDraft.trim());
9375
+ setCodeSent(true);
9376
+ }),
9377
+ onConfirmEmail: (code) => void run("email-confirm", () => recovery.confirmEmail(code.trim()), { notice: "Email confirmed.", view: "overview" }),
9378
+ onApproveDevice: (id) => void run(`approve:${id}`, () => recovery.approveDevice(id), { notice: "Device added." }),
9379
+ onRejectDevice: (id) => void run(`reject:${id}`, () => recovery.rejectDevice(id)),
9380
+ onRequestEnrollment: () => void run("enroll", () => recovery.requestDeviceEnrollment(), {
9381
+ notice: "Request sent. Approve it from a device that already has this wallet within 15 minutes."
9382
+ }),
9383
+ onRecoverWithPasskey: () => void run("recover-passkey", () => recovery.recoverWithPasskey(), {
9384
+ notice: "This device is part of your wallet again."
9385
+ }),
9386
+ onSendRecoveryCode: () => void run("recover-send", async () => {
9387
+ await recovery.startEmailRecovery();
9388
+ setCodeSent(true);
9389
+ }),
9390
+ onRecoverWithCode: (code) => void run("recover-code", () => recovery.recoverWithEmailCode(code.trim()), {
9391
+ notice: "This device is part of your wallet again.",
9392
+ view: "overview"
9393
+ }),
9394
+ onExport: () => void run("export", async () => {
9395
+ setPhrase(await recovery.exportWallet());
9396
+ }),
9397
+ onClose
9398
+ }
9399
+ ) });
9400
+ }
9401
+ var OPERATION_LABELS2 = {
9402
+ payment: "Send",
9403
+ path_payment_strict_send: "Swap",
9404
+ change_trust: "Add asset",
9405
+ create_account: "Create account",
9406
+ invoke_contract: "Contract call",
9407
+ manage_data: "Manage data"
9408
+ };
9409
+ function labelOf2(confirmation) {
9410
+ const op = confirmation.operation ?? "";
9411
+ return OPERATION_LABELS2[op] ?? (op ? op.replace(/_/g, " ") : "Transaction");
9412
+ }
9413
+ function detailRows2(confirmation) {
9414
+ const details = confirmation.details ?? {};
9415
+ const rows = [];
9416
+ const push = (label, value) => {
9417
+ if (value === void 0 || value === null || value === "") return;
9418
+ const text = typeof value === "object" ? JSON.stringify(value) : String(value);
9419
+ rows.push([label, /^[GC][A-Z2-7]{55}$/.test(text) ? cropAddress(text) : text]);
9420
+ };
9421
+ push("Destination", details.destination);
9422
+ push("Amount", details.amount);
9423
+ push("Asset", details.asset ?? details.assetCode ?? details.mint);
9424
+ push("Network", confirmation.network);
9425
+ return rows;
9426
+ }
9427
+ function CloseIcon4() {
9428
+ return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "m3 3 8 8M11 3l-8 8", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
9429
+ }
9430
+ function ConfirmModal({ confirmation }) {
9431
+ const { getClient, styles } = usePollar();
9432
+ const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
9433
+ const cssVars = buildModalCssVars(theme, accentColor, styleOverrides);
9434
+ const rejectRef = useRef(confirmation.reject);
9435
+ rejectRef.current = confirmation.reject;
9436
+ useEffect(() => {
9437
+ return getClient().onAuthStateChange((authState) => {
9438
+ if (authState.step === "idle") rejectRef.current();
9439
+ });
9440
+ }, [getClient]);
9441
+ return /* @__PURE__ */ jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: confirmation.reject, children: /* @__PURE__ */ jsxs(
9442
+ "div",
9443
+ {
9444
+ className: "pollar-modal-card pollar-cosign-modal",
9445
+ "data-theme": theme,
9446
+ style: cssVars,
9447
+ onClick: (e) => e.stopPropagation(),
9448
+ children: [
9449
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header", children: [
9450
+ /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: "Confirm transaction" }),
9451
+ /* @__PURE__ */ jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: confirmation.reject, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx(CloseIcon4, {}) }) })
9452
+ ] }),
9453
+ /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-detail", children: [
9454
+ /* @__PURE__ */ jsx("p", { className: "pollar-cosign-detail-title", children: labelOf2(confirmation) }),
9455
+ /* @__PURE__ */ jsx("p", { className: "pollar-cosign-muted", children: "The app asks you to sign this transaction with your wallet." }),
9456
+ /* @__PURE__ */ jsx("dl", { className: "pollar-cosign-rows", children: detailRows2(confirmation).map(([k, v]) => /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-row", children: [
9457
+ /* @__PURE__ */ jsx("dt", { children: k }),
9458
+ /* @__PURE__ */ jsx("dd", { children: v })
9459
+ ] }, k)) }),
9460
+ /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-actions", children: [
9461
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-cosign-btn pollar-cosign-btn-secondary", onClick: confirmation.reject, children: "Cancel" }),
9462
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-cosign-btn pollar-cosign-btn-primary", onClick: confirmation.confirm, children: "Confirm" })
9463
+ ] })
9464
+ ] }),
9465
+ /* @__PURE__ */ jsx(PollarModalFooter, {})
9466
+ ]
9467
+ }
9468
+ ) });
9469
+ }
7980
9470
  function TransactionModalTemplate({
7981
9471
  theme,
7982
9472
  accentColor,
@@ -8086,12 +9576,6 @@ function TransactionModal({ onClose }) {
8086
9576
  ) });
8087
9577
  }
8088
9578
  var PAGE_SIZE = 10;
8089
- function explorerUrlFor(chain, hash, network) {
8090
- if (chain === "SOLANA") {
8091
- return `https://explorer.solana.com/tx/${hash}${network === "testnet" ? "?cluster=devnet" : ""}`;
8092
- }
8093
- return `https://stellar.expert/explorer/${network === "testnet" ? "testnet" : "public"}/tx/${hash}`;
8094
- }
8095
9579
  function StatusBadge({ status }) {
8096
9580
  return /* @__PURE__ */ jsx("span", { className: "pollar-hist-item-badge", "data-status": status, children: status });
8097
9581
  }
@@ -8101,6 +9585,25 @@ function formatDate(iso) {
8101
9585
  function truncateMiddle(value, head = 4, tail = 4) {
8102
9586
  return value.length <= head + tail + 1 ? value : `${value.slice(0, head)}\u2026${value.slice(-tail)}`;
8103
9587
  }
9588
+ function formatFee(fee) {
9589
+ if (fee.unit === "lamports") {
9590
+ try {
9591
+ return `${fromBaseUnits(fee.amount, 9)} SOL`;
9592
+ } catch {
9593
+ }
9594
+ }
9595
+ return `${fee.amount} ${fee.unit}`;
9596
+ }
9597
+ function formatAmount4(details) {
9598
+ const { amount, decimals, symbol } = details ?? {};
9599
+ if (typeof amount !== "string" || typeof decimals !== "number") return null;
9600
+ try {
9601
+ const value = fromBaseUnits(amount, decimals);
9602
+ return typeof symbol === "string" ? `${value} ${symbol}` : value;
9603
+ } catch {
9604
+ return null;
9605
+ }
9606
+ }
8104
9607
  var STELLAR_ADDRESS = /\b[GC][A-Z2-7]{55}\b/g;
8105
9608
  function AddressChip({ value, label }) {
8106
9609
  return /* @__PURE__ */ jsxs("span", { className: "pollar-hist-item-issuer", children: [
@@ -8208,7 +9711,7 @@ function TxHistoryModalTemplate({
8208
9711
  txHistory.step === "loaded" && records.length === 0 && /* @__PURE__ */ jsx("div", { className: "pollar-modal-empty", children: "No transactions yet." }),
8209
9712
  records.map((record) => {
8210
9713
  const hash = typeof record.hash === "string" && record.hash.length > 0 ? record.hash : void 0;
8211
- const explorerUrl = hash ? explorerUrlFor(record.chain, hash, record.network) : void 0;
9714
+ const explorerUrl = hash ? explorerTxUrl(record.chain, hash, record.network) : void 0;
8212
9715
  const asset = typeof record.details?.asset === "string" ? record.details.asset : void 0;
8213
9716
  const colon = asset ? asset.indexOf(":") : -1;
8214
9717
  const trustline = record.operation === "change_trust" && asset && colon > 0 ? {
@@ -8238,15 +9741,17 @@ function TxHistoryModalTemplate({
8238
9741
  ] }),
8239
9742
  /* @__PURE__ */ jsxs("span", { className: "pollar-hist-item-meta", children: [
8240
9743
  /* @__PURE__ */ jsx("span", { children: record.operation }),
9744
+ record.chain !== "STELLAR" && formatAmount4(record.details) && /* @__PURE__ */ jsxs("span", { children: [
9745
+ "\xB7 ",
9746
+ formatAmount4(record.details)
9747
+ ] }),
8241
9748
  typeof record.details?.sponsored === "boolean" && /* @__PURE__ */ jsxs("span", { children: [
8242
9749
  "\xB7 ",
8243
9750
  record.details.sponsored ? "Sponsored" : "Self-paid"
8244
9751
  ] }),
8245
9752
  record.fee && /* @__PURE__ */ jsxs("span", { children: [
8246
9753
  "\xB7 ",
8247
- record.fee.amount,
8248
- " ",
8249
- record.fee.unit
9754
+ formatFee(record.fee)
8250
9755
  ] })
8251
9756
  ] }),
8252
9757
  /* @__PURE__ */ jsxs("div", { className: "pollar-hist-item-footer", children: [
@@ -8557,6 +10062,48 @@ var browserPasskeySigner = async ({ credentialId, challenge }) => {
8557
10062
  signature: response.signature
8558
10063
  };
8559
10064
  };
10065
+ var browserRecoveryPasskey = {
10066
+ async create({ challenge, userName }) {
10067
+ const rpId = window.location.hostname;
10068
+ const response = await startRegistration({
10069
+ optionsJSON: {
10070
+ challenge,
10071
+ rp: { id: rpId, name: rpId },
10072
+ user: { id: randomUserId(), name: userName, displayName: userName },
10073
+ pubKeyCredParams: [
10074
+ { type: "public-key", alg: -7 },
10075
+ { type: "public-key", alg: -257 }
10076
+ ],
10077
+ authenticatorSelection: { residentKey: "preferred", userVerification: "preferred" },
10078
+ attestation: "none",
10079
+ timeout: 6e4
10080
+ }
10081
+ });
10082
+ return {
10083
+ credentialId: response.id,
10084
+ clientDataJson: response.response.clientDataJSON,
10085
+ attestationObject: response.response.attestationObject,
10086
+ transports: response.response.transports ?? []
10087
+ };
10088
+ },
10089
+ async get({ challenge, allowCredentials }) {
10090
+ const rpId = window.location.hostname;
10091
+ const response = await startAuthentication({
10092
+ optionsJSON: {
10093
+ challenge,
10094
+ rpId,
10095
+ allowCredentials: allowCredentials.map((id) => ({ id, type: "public-key" })),
10096
+ userVerification: "preferred"
10097
+ }
10098
+ });
10099
+ return {
10100
+ credentialId: response.id,
10101
+ authenticatorData: response.response.authenticatorData,
10102
+ clientDataJson: response.response.clientDataJSON,
10103
+ signature: response.response.signature
10104
+ };
10105
+ }
10106
+ };
8560
10107
  var DEFAULT_APP_CONFIG = {
8561
10108
  application: { name: "", network: "testnet", chains: [] },
8562
10109
  styles: {}
@@ -8581,7 +10128,11 @@ function PollarProvider({
8581
10128
  }) {
8582
10129
  const [pollarClient] = useState(() => {
8583
10130
  if (isPollarClient(client)) {
8584
- client.setPasskeyDefaults({ passkey: browserPasskeyCeremony, passkeySign: browserPasskeySigner });
10131
+ client.setPasskeyDefaults({
10132
+ passkey: browserPasskeyCeremony,
10133
+ passkeySign: browserPasskeySigner,
10134
+ recoveryPasskey: browserRecoveryPasskey
10135
+ });
8585
10136
  return client;
8586
10137
  }
8587
10138
  const alreadyBuilt = clientByConfig.get(client);
@@ -8589,7 +10140,8 @@ function PollarProvider({
8589
10140
  const built = new PollarClient({
8590
10141
  ...client,
8591
10142
  passkey: client.passkey ?? browserPasskeyCeremony,
8592
- passkeySign: client.passkeySign ?? browserPasskeySigner
10143
+ passkeySign: client.passkeySign ?? browserPasskeySigner,
10144
+ recoveryPasskey: client.recoveryPasskey ?? browserRecoveryPasskey
8593
10145
  });
8594
10146
  clientByConfig.set(client, built);
8595
10147
  return built;
@@ -8617,6 +10169,9 @@ function PollarProvider({
8617
10169
  const [transaction, setTransaction] = useState({ step: "idle" });
8618
10170
  const [txHistory, setTxHistory] = useState({ step: "idle" });
8619
10171
  const [sessions, setSessions] = useState({ step: "idle" });
10172
+ const [cosign, setCosign] = useState({ pending: [] });
10173
+ const [confirmations, setConfirmations] = useState([]);
10174
+ const firstConfirmation = confirmations[0] ?? null;
8620
10175
  const [walletBalance, setWalletBalance] = useState({ step: "idle" });
8621
10176
  const [enabledAssets, setEnabledAssets] = useState({ step: "idle" });
8622
10177
  const [resolvedConfig, setResolvedConfig] = useState(() => appConfigProp ?? DEFAULT_APP_CONFIG);
@@ -8634,6 +10189,12 @@ function PollarProvider({
8634
10189
  useEffect(() => {
8635
10190
  return pollarClient.onTxHistoryStateChange(setTxHistory);
8636
10191
  }, [pollarClient]);
10192
+ useEffect(() => {
10193
+ return pollarClient.onCosignStateChange(setCosign);
10194
+ }, [pollarClient]);
10195
+ useEffect(() => {
10196
+ return pollarClient.onConfirmationRequest(setConfirmations);
10197
+ }, [pollarClient]);
8637
10198
  useEffect(() => {
8638
10199
  return pollarClient.onSessionsStateChange(setSessions);
8639
10200
  }, [pollarClient]);
@@ -8734,6 +10295,13 @@ function PollarProvider({
8734
10295
  const [cardModalKey, setCardModalKey] = useState(0);
8735
10296
  const [receiveModalOpen, setReceiveModalOpen] = useState(false);
8736
10297
  const [sessionsModalOpen, setSessionsModalOpen] = useState(false);
10298
+ const [cosignModalOpen, setCosignModalOpen] = useState(false);
10299
+ const [recoveryModalOpen, setRecoveryModalOpen] = useState(false);
10300
+ const cosignPendingCount = useRef(0);
10301
+ useEffect(() => {
10302
+ if (cosign.pending.length > cosignPendingCount.current) setCosignModalOpen(true);
10303
+ cosignPendingCount.current = cosign.pending.length;
10304
+ }, [cosign.pending.length]);
8737
10305
  const [distributionRulesModalOpen, setDistributionRulesModalOpen] = useState(false);
8738
10306
  useEffect(
8739
10307
  () => pollarClient.onAuthStateChange((authState) => {
@@ -8750,6 +10318,8 @@ function PollarProvider({
8750
10318
  setCardModalOpen(false);
8751
10319
  setReceiveModalOpen(false);
8752
10320
  setSessionsModalOpen(false);
10321
+ setCosignModalOpen(false);
10322
+ setRecoveryModalOpen(false);
8753
10323
  setDistributionRulesModalOpen(false);
8754
10324
  }),
8755
10325
  [pollarClient]
@@ -8758,6 +10328,45 @@ function PollarProvider({
8758
10328
  const walletProvisioning = sessionState?.wallet?.provisioning;
8759
10329
  const hasSession = sessionState !== null;
8760
10330
  const getClient = useCallback(() => pollarClient, [pollarClient]);
10331
+ const [recovery, setRecovery] = useState(null);
10332
+ const [recoveryTick, setRecoveryTick] = useState(0);
10333
+ const refreshRecovery = useCallback(() => setRecoveryTick((t) => t + 1), []);
10334
+ useEffect(() => {
10335
+ if (!hasSession) {
10336
+ setRecovery(null);
10337
+ return;
10338
+ }
10339
+ let cancelled = false;
10340
+ pollarClient.cosign.recovery.status().then((next) => {
10341
+ if (!cancelled) setRecovery(next);
10342
+ }).catch(() => {
10343
+ if (!cancelled) setRecovery(null);
10344
+ });
10345
+ return () => {
10346
+ cancelled = true;
10347
+ };
10348
+ }, [pollarClient, hasSession, walletAddress, recoveryTick]);
10349
+ const recoveryLocked = !!recovery && recovery.required && !recovery.configured;
10350
+ const [custodyMigration, setCustodyMigration] = useState(null);
10351
+ const [custodyMigrationTick, setCustodyMigrationTick] = useState(0);
10352
+ const refreshCustodyMigration = useCallback(() => setCustodyMigrationTick((t) => t + 1), []);
10353
+ const [custodyMigrationLater, setCustodyMigrationLater] = useState(false);
10354
+ useEffect(() => {
10355
+ if (!hasSession) {
10356
+ setCustodyMigration(null);
10357
+ setCustodyMigrationLater(false);
10358
+ return;
10359
+ }
10360
+ let cancelled = false;
10361
+ pollarClient.cosign.custodyMigration().then((next) => {
10362
+ if (!cancelled) setCustodyMigration(next);
10363
+ }).catch(() => {
10364
+ if (!cancelled) setCustodyMigration(null);
10365
+ });
10366
+ return () => {
10367
+ cancelled = true;
10368
+ };
10369
+ }, [pollarClient, hasSession, walletAddress, custodyMigrationTick]);
8761
10370
  const refreshWalletBalance = useCallback(() => pollarClient.refreshBalance(), [pollarClient, walletAddress]);
8762
10371
  const refreshAssets = useCallback(() => pollarClient.refreshAssets(), [pollarClient, walletAddress]);
8763
10372
  const contextValue = useMemo(() => {
@@ -8768,7 +10377,7 @@ function PollarProvider({
8768
10377
  wallets: pollarClient.getWallets(),
8769
10378
  isAuthenticated: hasSession,
8770
10379
  hasWallet: pollarClient.hasWallet(),
8771
- createWallet: () => pollarClient.createWallet(),
10380
+ createWallet: (options) => pollarClient.createWallet(options),
8772
10381
  verified,
8773
10382
  // client
8774
10383
  getClient,
@@ -8834,6 +10443,16 @@ function PollarProvider({
8834
10443
  // sessions
8835
10444
  sessions,
8836
10445
  openSessionsModal: () => setSessionsModalOpen(true),
10446
+ // cosign
10447
+ cosign,
10448
+ openCosignModal: () => setCosignModalOpen(true),
10449
+ recovery,
10450
+ recoveryLocked,
10451
+ refreshRecovery,
10452
+ openRecoveryModal: () => setRecoveryModalOpen(true),
10453
+ custodyMigration,
10454
+ refreshCustodyMigration,
10455
+ confirmations,
8837
10456
  // distribution
8838
10457
  openDistributionRulesModal: () => setDistributionRulesModalOpen(true),
8839
10458
  // network
@@ -8856,6 +10475,11 @@ function PollarProvider({
8856
10475
  }, [
8857
10476
  walletAddress,
8858
10477
  walletProvisioning,
10478
+ recovery,
10479
+ recoveryLocked,
10480
+ refreshRecovery,
10481
+ custodyMigration,
10482
+ refreshCustodyMigration,
8859
10483
  hasSession,
8860
10484
  verified,
8861
10485
  pollarClient,
@@ -8863,6 +10487,8 @@ function PollarProvider({
8863
10487
  transaction,
8864
10488
  txHistory,
8865
10489
  sessions,
10490
+ cosign,
10491
+ confirmations,
8866
10492
  walletBalance,
8867
10493
  refreshWalletBalance,
8868
10494
  enabledAssets,
@@ -8910,6 +10536,25 @@ function PollarProvider({
8910
10536
  /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setCardModalOpen(false), children: /* @__PURE__ */ jsx(CardModal, { onClose: () => setCardModalOpen(false) }) }, `${cardModalKey}:${walletAddress}`),
8911
10537
  receiveModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setReceiveModalOpen(false), children: /* @__PURE__ */ jsx(ReceiveModal, { onClose: () => setReceiveModalOpen(false) }) }),
8912
10538
  sessionsModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setSessionsModalOpen(false), children: /* @__PURE__ */ jsx(SessionsModal, { onClose: () => setSessionsModalOpen(false) }) }),
10539
+ cosignModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setCosignModalOpen(false), children: /* @__PURE__ */ jsx(CosignModal, { onClose: () => setCosignModalOpen(false) }) }),
10540
+ custodyMigration?.required && !custodyMigrationLater && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: refreshCustodyMigration, children: /* @__PURE__ */ jsx(
10541
+ CustodyMigrationModal,
10542
+ {
10543
+ state: custodyMigration,
10544
+ onMoved: () => {
10545
+ refreshCustodyMigration();
10546
+ refreshRecovery();
10547
+ setRecoveryModalOpen(true);
10548
+ },
10549
+ onLater: () => {
10550
+ setCustodyMigrationLater(true);
10551
+ refreshCustodyMigration();
10552
+ refreshRecovery();
10553
+ }
10554
+ }
10555
+ ) }),
10556
+ recoveryModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setRecoveryModalOpen(false), children: /* @__PURE__ */ jsx(RecoveryModal, { onClose: () => setRecoveryModalOpen(false) }) }),
10557
+ firstConfirmation && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: firstConfirmation.reject, children: /* @__PURE__ */ jsx(ConfirmModal, { confirmation: firstConfirmation }, firstConfirmation.id) }),
8913
10558
  distributionRulesModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setDistributionRulesModalOpen(false), children: /* @__PURE__ */ jsx(DistributionRulesModal, { onClose: () => setDistributionRulesModalOpen(false) }) })
8914
10559
  ] });
8915
10560
  }
@@ -8959,6 +10604,8 @@ function WalletButtonTemplate({
8959
10604
  isInProgress,
8960
10605
  showCreateAccount,
8961
10606
  notReadyReason,
10607
+ recoveryLocked,
10608
+ onRecovery,
8962
10609
  creatingAccount,
8963
10610
  onToggleOpen,
8964
10611
  onCreateAccount,
@@ -8976,8 +10623,11 @@ function WalletButtonTemplate({
8976
10623
  signedIn,
8977
10624
  creatingWallet,
8978
10625
  createWalletError,
10626
+ custodyChoice,
8979
10627
  onCreateWallet
8980
10628
  }) {
10629
+ const [selfCustodyNotice, setSelfCustodyNotice] = useState(false);
10630
+ const [risksAccepted, setRisksAccepted] = useState(false);
8981
10631
  if (!walletAddress && signedIn) {
8982
10632
  return /* @__PURE__ */ jsxs("div", { className: "pollar-wallet-wrapper", ref: wrapperRef, children: [
8983
10633
  /* @__PURE__ */ jsxs("button", { className: "pollar-wallet-btn", style: { backgroundColor: accentColor }, onClick: onToggleOpen, children: [
@@ -9001,19 +10651,84 @@ function WalletButtonTemplate({
9001
10651
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "status", children: createWalletError }),
9002
10652
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
9003
10653
  ] }),
9004
- onCreateWallet && /* @__PURE__ */ jsxs(Fragment, { children: [
10654
+ onCreateWallet && !custodyChoice && /* @__PURE__ */ jsxs(Fragment, { children: [
9005
10655
  /* @__PURE__ */ jsx(
9006
10656
  "button",
9007
10657
  {
9008
10658
  className: "pollar-wallet-dropdown-item",
9009
10659
  style: { color: itemColor },
9010
- onClick: onCreateWallet,
10660
+ onClick: () => onCreateWallet(),
9011
10661
  disabled: creatingWallet,
9012
10662
  children: creatingWallet ? "Creating wallet\u2026" : "Create wallet"
9013
10663
  }
9014
10664
  ),
9015
10665
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
9016
10666
  ] }),
10667
+ onCreateWallet && custodyChoice && !selfCustodyNotice && /* @__PURE__ */ jsxs(Fragment, { children: [
10668
+ /* @__PURE__ */ jsx(
10669
+ "button",
10670
+ {
10671
+ className: "pollar-wallet-dropdown-item",
10672
+ style: { color: itemColor },
10673
+ onClick: () => onCreateWallet("COSIGNED"),
10674
+ disabled: creatingWallet,
10675
+ children: creatingWallet ? "Creating wallet\u2026" : "Create co-signed wallet"
10676
+ }
10677
+ ),
10678
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: "You and the app approve every signature together. Your wallet can be recovered with help." }),
10679
+ /* @__PURE__ */ jsx(
10680
+ "button",
10681
+ {
10682
+ className: "pollar-wallet-dropdown-item",
10683
+ style: { color: itemColor },
10684
+ onClick: () => setSelfCustodyNotice(true),
10685
+ disabled: creatingWallet,
10686
+ children: "Create self-custody wallet"
10687
+ }
10688
+ ),
10689
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: "Only your device signs. Nobody else can move or recover your funds." }),
10690
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
10691
+ ] }),
10692
+ onCreateWallet && custodyChoice && selfCustodyNotice && /* @__PURE__ */ jsxs(Fragment, { children: [
10693
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "alert", children: "In a self-custody wallet only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign." }),
10694
+ /* @__PURE__ */ jsxs("label", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: [
10695
+ /* @__PURE__ */ jsx(
10696
+ "input",
10697
+ {
10698
+ type: "checkbox",
10699
+ checked: risksAccepted,
10700
+ onChange: (e) => setRisksAccepted(e.target.checked),
10701
+ disabled: creatingWallet
10702
+ }
10703
+ ),
10704
+ " ",
10705
+ "I understand and accept these risks"
10706
+ ] }),
10707
+ /* @__PURE__ */ jsx(
10708
+ "button",
10709
+ {
10710
+ className: "pollar-wallet-dropdown-item",
10711
+ style: { color: itemColor },
10712
+ onClick: () => onCreateWallet("SELF_CUSTODY"),
10713
+ disabled: creatingWallet || !risksAccepted,
10714
+ children: creatingWallet ? "Creating wallet\u2026" : "Create self-custody wallet"
10715
+ }
10716
+ ),
10717
+ /* @__PURE__ */ jsx(
10718
+ "button",
10719
+ {
10720
+ className: "pollar-wallet-dropdown-item",
10721
+ style: { color: itemColor },
10722
+ onClick: () => {
10723
+ setSelfCustodyNotice(false);
10724
+ setRisksAccepted(false);
10725
+ },
10726
+ disabled: creatingWallet,
10727
+ children: "Back"
10728
+ }
10729
+ ),
10730
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
10731
+ ] }),
9017
10732
  /* @__PURE__ */ jsx("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onSessions, children: "Active sessions" }),
9018
10733
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" }),
9019
10734
  /* @__PURE__ */ jsx("button", { className: "pollar-wallet-dropdown-item pollar-danger", onClick: onLogout, children: "Log out" })
@@ -9069,6 +10784,10 @@ function WalletButtonTemplate({
9069
10784
  }
9070
10785
  ),
9071
10786
  open && /* @__PURE__ */ jsxs("div", { className: "pollar-wallet-dropdown", style: { backgroundColor: dropdownBg, borderColor: dropdownBorder }, children: [
10787
+ recoveryLocked && /* @__PURE__ */ jsxs(Fragment, { children: [
10788
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice pollar-danger", role: "alert", children: "Locked: add a way to recover this wallet before sending or receiving. If you lose this device without one, the funds are lost." }),
10789
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
10790
+ ] }),
9072
10791
  notReadyReason && /* @__PURE__ */ jsxs(Fragment, { children: [
9073
10792
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "status", children: notReadyReason }),
9074
10793
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
@@ -9301,6 +11020,31 @@ function WalletButtonTemplate({
9301
11020
  ),
9302
11021
  "Active sessions"
9303
11022
  ] }),
11023
+ onRecovery && /* @__PURE__ */ jsxs(
11024
+ "button",
11025
+ {
11026
+ className: `pollar-wallet-dropdown-item${recoveryLocked ? " pollar-danger" : ""}`,
11027
+ style: recoveryLocked ? void 0 : { color: itemColor },
11028
+ onClick: onRecovery,
11029
+ children: [
11030
+ /* @__PURE__ */ jsx(
11031
+ "svg",
11032
+ {
11033
+ width: "14",
11034
+ height: "14",
11035
+ viewBox: "0 0 24 24",
11036
+ fill: "none",
11037
+ stroke: "currentColor",
11038
+ strokeWidth: "2",
11039
+ strokeLinecap: "round",
11040
+ strokeLinejoin: "round",
11041
+ children: /* @__PURE__ */ jsx("path", { d: "M12 2 4 5v6c0 5 3.4 9.3 8 11 4.6-1.7 8-6 8-11V5l-8-3z" })
11042
+ }
11043
+ ),
11044
+ recoveryLocked ? "Set up wallet recovery" : "Wallet recovery"
11045
+ ]
11046
+ }
11047
+ ),
9304
11048
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" }),
9305
11049
  /* @__PURE__ */ jsxs("button", { className: "pollar-wallet-dropdown-item pollar-danger", onClick: onLogout, children: [
9306
11050
  /* @__PURE__ */ jsxs(
@@ -9343,7 +11087,10 @@ function WalletButton() {
9343
11087
  tx: transaction,
9344
11088
  isAuthenticated,
9345
11089
  createWallet,
9346
- appConfig
11090
+ appConfig,
11091
+ recovery,
11092
+ recoveryLocked,
11093
+ openRecoveryModal
9347
11094
  } = usePollar();
9348
11095
  const { primaryChain, primaryAddress } = useChains();
9349
11096
  const walletAddress = primaryAddress || (wallet?.address ?? "");
@@ -9406,12 +11153,12 @@ function WalletButton() {
9406
11153
  setCreating(false);
9407
11154
  }
9408
11155
  }
9409
- async function handleCreateWallet() {
11156
+ async function handleCreateWallet(custody) {
9410
11157
  if (creatingWallet) return;
9411
11158
  setCreatingWallet(true);
9412
11159
  setCreateWalletError(null);
9413
11160
  try {
9414
- await createWallet();
11161
+ await createWallet(custody ? { custody } : void 0);
9415
11162
  setOpen(false);
9416
11163
  } catch (err) {
9417
11164
  setCreateWalletError(
@@ -9441,6 +11188,10 @@ function WalletButton() {
9441
11188
  setOpen(false);
9442
11189
  openSessionsModal();
9443
11190
  }
11191
+ function handleRecovery() {
11192
+ setOpen(false);
11193
+ openRecoveryModal();
11194
+ }
9444
11195
  function handleKyc() {
9445
11196
  setOpen(false);
9446
11197
  openKycModal();
@@ -9469,6 +11220,7 @@ function WalletButton() {
9469
11220
  signedIn: isAuthenticated,
9470
11221
  creatingWallet,
9471
11222
  createWalletError,
11223
+ custodyChoice: appConfig.application?.walletCustodyMode === "USER_CHOICE",
9472
11224
  ...appConfig.application?.walletProvisioning === "NONE" ? {} : { onCreateWallet: handleCreateWallet },
9473
11225
  notReadyReason,
9474
11226
  creatingAccount: creating,
@@ -9480,6 +11232,8 @@ function WalletButton() {
9480
11232
  onSend: handleSend,
9481
11233
  onReceive: handleReceive,
9482
11234
  onSessions: handleSessions,
11235
+ recoveryLocked,
11236
+ ...recovery?.required ? { onRecovery: handleRecovery } : {},
9483
11237
  onKyc: handleKyc,
9484
11238
  onRamp: handleRamp,
9485
11239
  onDistributionRules: handleDistributionRules,
@@ -9489,6 +11243,6 @@ function WalletButton() {
9489
11243
  );
9490
11244
  }
9491
11245
 
9492
- export { CardModal, ChainSelect, DistributionRulesModal, DistributionRulesModalTemplate, EarnModal, EnabledAssetsModal, EnabledAssetsModalTemplate, KycModal, KycModalTemplate, KycStatus, LoginModalTemplate, PollarProvider, ProviderRegistrationModal, RampWidget, RampWidgetTemplate, ReceiveModal, ReceiveModalTemplate, RegistryCheckModal, RequirementFormModal, RouteDisplay, SendModal, SendModalTemplate, SessionsModal, SessionsModalTemplate, SwapModal, SwapModalTemplate, TransactionModalTemplate, TxHistoryModalTemplate, TxStatusView, WalletBalanceModal, WalletBalanceModalTemplate, WalletButton, WalletButtonTemplate, addressForChain, browserPasskeyCeremony, browserPasskeySigner, buildModalCssVars, chainsOf, createPollarAdapterHook, modalChrome, readableTextOn, resolveChain, useChains, usePollar, walletNotReadyReason };
11246
+ export { CardModal, ChainSelect, CosignModal, CosignModalTemplate, CustodyMigrationModal, CustodyMigrationModalTemplate, DistributionRulesModal, DistributionRulesModalTemplate, EarnModal, EnabledAssetsModal, EnabledAssetsModalTemplate, KycModal, KycModalTemplate, KycStatus, LoginModalTemplate, PollarProvider, ProviderRegistrationModal, RampWidget, RampWidgetTemplate, RampWorkflow, ReceiveModal, ReceiveModalTemplate, RecoveryModal, RecoveryModalTemplate, RegistryCheckModal, RequirementFormModal, RouteDisplay, SendModal, SendModalTemplate, SessionsModal, SessionsModalTemplate, SwapModal, SwapModalTemplate, TransactionModalTemplate, TxHistoryModalTemplate, TxStatusView, WalletBalanceModal, WalletBalanceModalTemplate, WalletButton, WalletButtonTemplate, addressForChain, browserPasskeyCeremony, browserPasskeySigner, browserRecoveryPasskey, buildModalCssVars, chainsOf, createPollarAdapterHook, modalChrome, readableTextOn, resolveChain, useChains, useCosign, usePollar, walletNotReadyReason };
9493
11247
  //# sourceMappingURL=index.mjs.map
9494
11248
  //# sourceMappingURL=index.mjs.map