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