@pollar/react 0.11.5 → 0.12.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { AUTH_ERROR_CODES, transactionErrorMessage, WalletType, cosignGroupOf, cosignStepKeyOf, stepInGroup, isPollarApiError, isPollarClient, PollarClient, isInteractiveAuthAdapter, toBaseUnits } from '@pollar/core';
2
+ import { AUTH_ERROR_CODES, describeRampAction, mergeRampCountries, transactionErrorMessage, WalletType, isPollarApiError, isPollarClient, PollarClient, isInteractiveAuthAdapter, mergeRampSnapshot, toBaseUnits, fromBaseUnits } from '@pollar/core';
3
3
  import { forwardRef, createContext, useState, useCallback, useEffect, useMemo, useRef, useContext, Component } from 'react';
4
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
5
5
  import { startAuthentication, startRegistration } from '@simplewebauthn/browser';
@@ -1036,7 +1036,7 @@ var PollarModalFooter = () => {
1036
1036
  /* @__PURE__ */ jsx("img", { src: LOGO_POLLAR_WORDMARK_ON_DARK, alt: "Pollar", className: "pollar-footer-logo pollar-logo-on-dark" }),
1037
1037
  /* @__PURE__ */ jsxs("span", { className: "pollar-footer-version", children: [
1038
1038
  "v",
1039
- "0.11.5"
1039
+ "0.12.0-rc.2"
1040
1040
  ] })
1041
1041
  ] });
1042
1042
  };
@@ -1913,7 +1913,7 @@ function KycStatus({ status, label, className }) {
1913
1913
 
1914
1914
  // src/components/kyc-modal/kyc-messages.ts
1915
1915
  function kycErrorMessage(error, phase) {
1916
- const code = error?.code;
1916
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
1917
1917
  switch (code) {
1918
1918
  case "SDK_KYC_NOT_CONFIGURED":
1919
1919
  return "Identity verification is not available for this app yet.";
@@ -2158,7 +2158,13 @@ function KycModal({
2158
2158
  finish("approved");
2159
2159
  return;
2160
2160
  }
2161
- setSession(result);
2161
+ const { sessionId } = result;
2162
+ if (sessionId === void 0) throw new Error("The KYC provider answered without a session.");
2163
+ setSession({
2164
+ sessionId,
2165
+ ...result.kycUrl !== void 0 ? { kycUrl: result.kycUrl } : {},
2166
+ ...result.fields !== void 0 ? { fields: result.fields } : {}
2167
+ });
2162
2168
  setStep("verifying");
2163
2169
  } catch (e) {
2164
2170
  const code = isPollarApiError(e) ? e.code : void 0;
@@ -2345,10 +2351,14 @@ function fieldErrorMessage(code, language) {
2345
2351
  }
2346
2352
  function fieldErrorsOf(error) {
2347
2353
  if (!error || typeof error !== "object") return null;
2348
- const { code, body } = error;
2349
- if (code !== "KYC_FORM_INVALID_ANSWERS" || !Array.isArray(body?.errors)) return null;
2354
+ if (!("code" in error) || error.code !== "KYC_FORM_INVALID_ANSWERS") return null;
2355
+ const body = "body" in error ? error.body : void 0;
2356
+ const list = typeof body === "object" && body !== null && "errors" in body ? body.errors : void 0;
2357
+ if (!Array.isArray(list)) return null;
2358
+ const items = list;
2350
2359
  const errors = {};
2351
- for (const item of body.errors) {
2360
+ for (const item of items) {
2361
+ if (typeof item !== "object" || item === null || !("key" in item) || !("code" in item)) continue;
2352
2362
  if (typeof item.key === "string" && typeof item.code === "string") errors[item.key] = item.code;
2353
2363
  }
2354
2364
  return Object.keys(errors).length ? errors : null;
@@ -3398,13 +3408,200 @@ function LoginModal({ onClose }) {
3398
3408
  ) });
3399
3409
  }
3400
3410
 
3411
+ // src/lib/qr-code/index.tsx
3412
+ var import_ErrorCorrectLevel = __toESM(require_ErrorCorrectLevel());
3413
+ var import_QRCode = __toESM(require_QRCode());
3414
+ var QRCodeSvg = forwardRef(function QRCodeSvg2({ bgColor, bgD, fgD, fgColor, size, title, viewBoxSize, xmlns = "http://www.w3.org/2000/svg", ...props }, ref) {
3415
+ return /* @__PURE__ */ jsxs("svg", { ...props, height: size, ref, viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`, width: size, xmlns, children: [
3416
+ title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
3417
+ /* @__PURE__ */ jsx("path", { d: bgD, fill: bgColor }),
3418
+ /* @__PURE__ */ jsx("path", { d: fgD, fill: fgColor })
3419
+ ] });
3420
+ });
3421
+ QRCodeSvg.displayName = "QRCodeSvg";
3422
+ function bytesToBinaryString(bytes) {
3423
+ return bytes.map((b) => String.fromCharCode(b & 255)).join("");
3424
+ }
3425
+ function encodeStringToUtf8Bytes(input) {
3426
+ return Array.from(new TextEncoder().encode(input));
3427
+ }
3428
+ var QRCode = forwardRef(function QRCode2({ bgColor = "#FFFFFF", fgColor = "#000000", level = "L", size = 256, value, ...props }, ref) {
3429
+ const qrcode = new import_QRCode.default(-1, import_ErrorCorrectLevel.default[level]);
3430
+ const utf8Bytes = encodeStringToUtf8Bytes(value);
3431
+ const binaryString = bytesToBinaryString(utf8Bytes);
3432
+ qrcode.addData(binaryString, "Byte");
3433
+ qrcode.make();
3434
+ const cells = qrcode.modules;
3435
+ return /* @__PURE__ */ jsx(
3436
+ QRCodeSvg,
3437
+ {
3438
+ ...props,
3439
+ bgColor,
3440
+ bgD: cells.map(
3441
+ (row, rowIndex) => row.map((cell, cellIndex) => !cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
3442
+ ).join(" "),
3443
+ fgColor,
3444
+ fgD: cells.map(
3445
+ (row, rowIndex) => row.map((cell, cellIndex) => cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
3446
+ ).join(" "),
3447
+ ref,
3448
+ size,
3449
+ viewBoxSize: cells.length
3450
+ }
3451
+ );
3452
+ });
3453
+ function RampWorkflow({
3454
+ client,
3455
+ snapshot,
3456
+ onChange,
3457
+ onBusyChange
3458
+ }) {
3459
+ const model = describeRampAction(snapshot);
3460
+ const locked = useRef(false);
3461
+ useEffect(() => {
3462
+ setFields({});
3463
+ setError(null);
3464
+ }, [model.action?.actionId]);
3465
+ const [busy, setBusy] = useState(false);
3466
+ const [error, setError] = useState(null);
3467
+ const [fields, setFields] = useState({});
3468
+ const fieldsComplete = model.action?.kind !== "collect_information" || model.action.fields.every((field) => {
3469
+ const value = (fields[field.key] ?? "").trim();
3470
+ if (!value) return field.optional;
3471
+ if (field.type === "email") return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
3472
+ return field.type !== "select" || field.options?.some((option) => option.value === value);
3473
+ });
3474
+ async function continueAction() {
3475
+ if (!model.action || locked.current || !fieldsComplete) return;
3476
+ locked.current = true;
3477
+ setBusy(true);
3478
+ onBusyChange?.(true);
3479
+ setError(null);
3480
+ try {
3481
+ onChange(
3482
+ model.canSign ? await client.signRampAction(snapshot.txId, snapshot) : await client.continueRamp(snapshot.txId, {
3483
+ actionId: model.action.actionId,
3484
+ transactionVersion: snapshot.transactionVersion,
3485
+ ...model.action.kind === "collect_information" ? { fields } : {}
3486
+ })
3487
+ );
3488
+ } catch (e) {
3489
+ setError(e instanceof Error ? e.message : "Unable to continue.");
3490
+ } finally {
3491
+ locked.current = false;
3492
+ setBusy(false);
3493
+ onBusyChange?.(false);
3494
+ }
3495
+ }
3496
+ return /* @__PURE__ */ jsxs(
3497
+ "section",
3498
+ {
3499
+ className: "pollar-ramp-payment pollar-ramp-workflow",
3500
+ "aria-label": "Ramp progress",
3501
+ "aria-live": "polite",
3502
+ "aria-busy": busy,
3503
+ children: [
3504
+ /* @__PURE__ */ jsx("h3", { className: "pollar-ramp-payment-title", children: model.title }),
3505
+ snapshot.terms && /* @__PURE__ */ jsxs("p", { className: "pollar-ramp-terms", children: [
3506
+ snapshot.terms.fiatAmount,
3507
+ " ",
3508
+ snapshot.terms.fiatCurrency,
3509
+ " \xB7 ",
3510
+ snapshot.terms.cryptoAmount,
3511
+ " ",
3512
+ snapshot.terms.assetCode,
3513
+ /* @__PURE__ */ jsx("br", {}),
3514
+ "Includes ",
3515
+ snapshot.terms.feeAmount,
3516
+ " ",
3517
+ snapshot.terms.feeCurrency,
3518
+ " fee"
3519
+ ] }),
3520
+ model.details.map((item, index) => /* @__PURE__ */ jsxs("p", { className: "pollar-ramp-workflow-detail", children: [
3521
+ /* @__PURE__ */ jsxs("strong", { children: [
3522
+ item.label,
3523
+ ": "
3524
+ ] }),
3525
+ item.value
3526
+ ] }, index)),
3527
+ model.links.map((link, index) => /* @__PURE__ */ jsx("p", { children: /* @__PURE__ */ jsx("a", { className: "pollar-btn-primary pollar-ramp-hosted-link", href: link.url, target: "_blank", rel: "noopener noreferrer", children: link.label }) }, index)),
3528
+ model.qr && /* @__PURE__ */ jsx(QRCode, { value: model.qr, size: 180 }),
3529
+ model.qr && /* @__PURE__ */ jsx(
3530
+ "button",
3531
+ {
3532
+ className: "pollar-btn-secondary",
3533
+ type: "button",
3534
+ onClick: () => void navigator.clipboard.writeText(model.qr).catch(() => setError("Unable to copy. Select the payment code to copy it manually.")),
3535
+ children: "Copy payment code"
3536
+ }
3537
+ ),
3538
+ model.action?.kind === "collect_information" && model.action.fields.map((field) => /* @__PURE__ */ jsxs("label", { className: "pollar-ramp-field", children: [
3539
+ /* @__PURE__ */ jsxs("span", { className: "pollar-ramp-label", children: [
3540
+ field.label,
3541
+ field.optional ? " (optional)" : ""
3542
+ ] }),
3543
+ field.type === "select" ? /* @__PURE__ */ jsxs(
3544
+ "select",
3545
+ {
3546
+ className: "pollar-ramp-input",
3547
+ disabled: busy,
3548
+ value: fields[field.key] ?? "",
3549
+ onChange: (e) => setFields((previous) => ({ ...previous, [field.key]: e.target.value })),
3550
+ children: [
3551
+ /* @__PURE__ */ jsx("option", { value: "", children: "Choose" }),
3552
+ field.options?.map((option) => /* @__PURE__ */ jsx("option", { value: option.value, children: option.label }, option.value))
3553
+ ]
3554
+ }
3555
+ ) : /* @__PURE__ */ jsx(
3556
+ "input",
3557
+ {
3558
+ className: "pollar-ramp-input",
3559
+ disabled: busy,
3560
+ type: field.type,
3561
+ placeholder: field.placeholder,
3562
+ value: fields[field.key] ?? "",
3563
+ onChange: (e) => setFields((previous) => ({ ...previous, [field.key]: e.target.value }))
3564
+ }
3565
+ ),
3566
+ field.hint && /* @__PURE__ */ jsx("small", { className: "pollar-ramp-field-hint", children: field.hint })
3567
+ ] }, field.key)),
3568
+ (model.canContinue || model.canSign) && /* @__PURE__ */ jsx(
3569
+ "button",
3570
+ {
3571
+ className: "pollar-btn-primary",
3572
+ type: "button",
3573
+ disabled: busy || !fieldsComplete,
3574
+ onClick: () => void continueAction(),
3575
+ children: busy ? model.canSign ? "Waiting for your wallet\u2026" : "Continuing\u2026" : model.canSign ? "Authorize" : "Continue"
3576
+ }
3577
+ ),
3578
+ snapshot.milestones?.map((milestone, index) => /* @__PURE__ */ jsxs("p", { children: [
3579
+ milestone.kind.replace(/_/g, " "),
3580
+ " \xB7 ",
3581
+ milestone.verifiedAt
3582
+ ] }, index)),
3583
+ error && /* @__PURE__ */ jsx("p", { className: "pollar-ramp-field-error", role: "alert", children: error }),
3584
+ /* @__PURE__ */ jsxs("small", { className: "pollar-ramp-reference", children: [
3585
+ "Reference: ",
3586
+ snapshot.txId
3587
+ ] })
3588
+ ]
3589
+ }
3590
+ );
3591
+ }
3592
+
3401
3593
  // src/components/ramp-widget/ramp-kyc.ts
3402
3594
  var nonEmpty = (value) => typeof value === "string" && !!value.trim();
3403
3595
  function requiredRampKyc(error) {
3404
3596
  if (!error || typeof error !== "object") return null;
3405
- const { code, body } = error;
3406
- if (code !== "SDK_RAMPS_KYC_REQUIRED" || !body || typeof body !== "object") return null;
3407
- const { rampProviderId, corridorId, requirementType, optionId, kycProviderId } = body;
3597
+ if (!("code" in error) || error.code !== "SDK_RAMPS_KYC_REQUIRED") return null;
3598
+ const body = "body" in error ? error.body : void 0;
3599
+ if (typeof body !== "object" || body === null) return null;
3600
+ const rampProviderId = "rampProviderId" in body ? body.rampProviderId : void 0;
3601
+ const corridorId = "corridorId" in body ? body.corridorId : void 0;
3602
+ const requirementType = "requirementType" in body ? body.requirementType : void 0;
3603
+ const optionId = "optionId" in body ? body.optionId : void 0;
3604
+ const kycProviderId = "kycProviderId" in body ? body.kycProviderId : void 0;
3408
3605
  if (!nonEmpty(rampProviderId) || !nonEmpty(corridorId)) return null;
3409
3606
  if ((requirementType === "FORM" || requirementType === "REGISTRY_CHECK" || requirementType === "PROVIDER_REGISTRATION") && nonEmpty(optionId)) {
3410
3607
  return { rampProviderId, corridorId, type: requirementType, optionId };
@@ -3471,13 +3668,14 @@ function RouteDisplay({ quote, busy = false, disabled = false, onSelect }) {
3471
3668
  children: [
3472
3669
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-route-left", children: [
3473
3670
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-provider", children: quote.provider }),
3474
- /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-meta", children: busy ? "Starting\u2026" : `${RAIL_LABELS[quote.rail] ?? quote.rail} \xB7 ${quote.protocol} \xB7 ${quote.estimatedTime}` })
3671
+ /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-meta", children: busy ? "Starting\u2026" : [
3672
+ quote.route ? `${quote.route.asset.code} on ${quote.route.asset.chain}` : null,
3673
+ RAIL_LABELS[quote.rail] ?? quote.rail,
3674
+ quote.estimatedTime
3675
+ ].filter(Boolean).join(" \xB7 ") })
3475
3676
  ] }),
3476
3677
  /* @__PURE__ */ jsx("div", { className: "pollar-ramp-route-right", children: busy ? /* @__PURE__ */ jsx("span", { className: "pollar-spinner pollar-spinner-sm" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
3477
- /* @__PURE__ */ jsxs("span", { className: "pollar-ramp-route-fee", children: [
3478
- quote.fee,
3479
- "% fee"
3480
- ] }),
3678
+ /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-fee", children: quote.terms ? `${quote.terms.fiatAmount} ${quote.terms.fiatCurrency} \xB7 ${quote.terms.cryptoAmount} ${quote.terms.assetCode} \xB7 Includes ${quote.terms.feeAmount} ${quote.terms.feeCurrency}` : `${quote.fee}% fee` }),
3481
3679
  quote.recommended && /* @__PURE__ */ jsx("span", { className: "pollar-ramp-route-badge", children: "Best rate" })
3482
3680
  ] }) })
3483
3681
  ]
@@ -3557,6 +3755,8 @@ function displayValue(field) {
3557
3755
  return Number.isNaN(at.getTime()) ? field.value : at.toLocaleString();
3558
3756
  }
3559
3757
  function RampWidgetTemplate({
3758
+ workflowContent,
3759
+ routeSelector,
3560
3760
  theme,
3561
3761
  accentColor,
3562
3762
  styleOverrides,
@@ -3593,6 +3793,11 @@ function RampWidgetTemplate({
3593
3793
  depositInstructions,
3594
3794
  canComplete,
3595
3795
  completing,
3796
+ savedRamps = [],
3797
+ onResumeRamp,
3798
+ onDismissRamp,
3799
+ onDismissCurrentRamp,
3800
+ onExploreRoutes,
3596
3801
  errorMsg,
3597
3802
  noticeMsg,
3598
3803
  onDirectionChange,
@@ -3631,7 +3836,7 @@ function RampWidgetTemplate({
3631
3836
  // money, not who you are. Say which, rather than asserting the Bridge case
3632
3837
  // over every provider.
3633
3838
  contact: `${provider || "This provider"} needs ${listOf(requiredFields.filter((f) => !f.optional).map((f) => f.label.toLowerCase()))} to continue`,
3634
- status: `Finish the flow at ${provider || "the provider"} to continue`,
3839
+ status: workflowContent ? "Follow the next step below to continue" : `Finish the flow at ${provider || "the provider"} to continue`,
3635
3840
  error: "Please try again"
3636
3841
  };
3637
3842
  return /* @__PURE__ */ jsxs("div", { className: "pollar-modal-card pollar-ramp-modal", style: cssVars, onClick: (e) => e.stopPropagation(), children: [
@@ -3685,7 +3890,39 @@ function RampWidgetTemplate({
3685
3890
  }
3686
3891
  )
3687
3892
  ] }),
3893
+ step === "input" && savedRamps.length > 0 && /* @__PURE__ */ jsxs("section", { className: "pollar-ramp-payment pollar-ramp-saved", "aria-label": "Pending operations", children: [
3894
+ /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", children: "Your pending operations are saved. Starting another route does not cancel them." }),
3895
+ savedRamps.map((ramp) => /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-saved-row", children: [
3896
+ /* @__PURE__ */ jsxs(
3897
+ "button",
3898
+ {
3899
+ type: "button",
3900
+ className: "pollar-btn-secondary",
3901
+ disabled: isLoading,
3902
+ onClick: () => onResumeRamp?.(ramp.txId),
3903
+ children: [
3904
+ "Resume ",
3905
+ ramp.provider,
3906
+ " \xB7 ",
3907
+ ramp.txId.slice(-8)
3908
+ ]
3909
+ }
3910
+ ),
3911
+ onDismissRamp && /* @__PURE__ */ jsx(
3912
+ "button",
3913
+ {
3914
+ type: "button",
3915
+ className: "pollar-btn-secondary",
3916
+ disabled: isLoading,
3917
+ "aria-label": `Remove ${ramp.provider} ${ramp.txId.slice(-8)} from pending list`,
3918
+ onClick: () => onDismissRamp(ramp.txId),
3919
+ children: "Remove"
3920
+ }
3921
+ )
3922
+ ] }, ramp.txId))
3923
+ ] }),
3688
3924
  step === "input" && /* @__PURE__ */ jsxs(Fragment, { children: [
3925
+ noticeMsg && /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", role: "status", children: noticeMsg }),
3689
3926
  /* @__PURE__ */ jsxs("div", { className: "pollar-tabs", children: [
3690
3927
  /* @__PURE__ */ jsx(
3691
3928
  "button",
@@ -3709,25 +3946,36 @@ function RampWidgetTemplate({
3709
3946
  )
3710
3947
  ] }),
3711
3948
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
3712
- /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", children: "Country" }),
3949
+ /* @__PURE__ */ jsx("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-country", children: "Country" }),
3713
3950
  countriesLoading ? /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-input pollar-select-loading", children: [
3714
3951
  /* @__PURE__ */ jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3715
3952
  /* @__PURE__ */ jsx("span", { children: "Loading countries\u2026" })
3716
- ] }) : countries.length === 0 ? /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "No ramp providers available on this network yet." }) : /* @__PURE__ */ jsx("select", { className: "pollar-ramp-input", value: country, onChange: (e) => onCountryChange(e.target.value), children: countries.map((c) => /* @__PURE__ */ jsxs("option", { value: c.code, children: [
3717
- flagEmoji(c.code),
3718
- " ",
3719
- c.code,
3720
- c.currency ? ` \u2014 ${c.currency}` : ""
3721
- ] }, c.code)) })
3953
+ ] }) : countries.length === 0 ? /* @__PURE__ */ jsx("div", { className: "pollar-modal-error", children: "No ramp providers available on this network yet." }) : /* @__PURE__ */ jsx(
3954
+ "select",
3955
+ {
3956
+ id: "pollar-ramp-country",
3957
+ className: "pollar-ramp-input",
3958
+ value: country,
3959
+ onChange: (e) => onCountryChange(e.target.value),
3960
+ children: countries.map((c) => /* @__PURE__ */ jsxs("option", { value: c.code, children: [
3961
+ flagEmoji(c.code),
3962
+ " ",
3963
+ c.code,
3964
+ c.currency ? ` \u2014 ${c.currency}` : ""
3965
+ ] }, c.code))
3966
+ }
3967
+ )
3722
3968
  ] }),
3969
+ routeSelector,
3723
3970
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-field", children: [
3724
- /* @__PURE__ */ jsxs("label", { className: "pollar-ramp-label", children: [
3971
+ /* @__PURE__ */ jsxs("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-amount", children: [
3725
3972
  "Amount",
3726
3973
  currency ? ` (${currency})` : ""
3727
3974
  ] }),
3728
3975
  /* @__PURE__ */ jsx(
3729
3976
  "input",
3730
3977
  {
3978
+ id: "pollar-ramp-amount",
3731
3979
  type: "number",
3732
3980
  className: "pollar-ramp-input",
3733
3981
  placeholder: "0.00",
@@ -3833,7 +4081,16 @@ function RampWidgetTemplate({
3833
4081
  )
3834
4082
  ] })
3835
4083
  ] }),
3836
- step === "status" && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment", children: [
4084
+ step === "status" && workflowContent,
4085
+ step === "status" && onDismissCurrentRamp && txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-explore", children: [
4086
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", disabled: completing, onClick: onDismissCurrentRamp, children: "Remove from pending list" }),
4087
+ /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", children: "This only hides the operation in this tab. To request cancellation, open the provider link above when available or contact the provider\u2019s support. Payments and refunds must be confirmed by the provider." })
4088
+ ] }),
4089
+ step === "status" && onExploreRoutes && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-explore", children: [
4090
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", disabled: completing, onClick: onExploreRoutes, children: txStatus === "completed" || txStatus === "failed" ? "Start another operation" : "Try another route" }),
4091
+ txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", children: "Your current operation stays saved so you can resume it later." })
4092
+ ] }),
4093
+ step === "status" && !workflowContent && /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment", children: [
3837
4094
  /* @__PURE__ */ jsxs("div", { className: "pollar-ramp-payment-field", children: [
3838
4095
  /* @__PURE__ */ jsx("span", { className: "pollar-ramp-payment-label", children: "Provider" }),
3839
4096
  /* @__PURE__ */ jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsx("code", { children: provider }) })
@@ -3965,7 +4222,7 @@ function RampWidgetTemplate({
3965
4222
  canComplete && /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-primary", disabled: completing, onClick: onCompleteWithdraw, children: completing ? /* @__PURE__ */ jsxs(Fragment, { children: [
3966
4223
  /* @__PURE__ */ jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3967
4224
  "Submitting\u2026"
3968
- ] }) : "I've completed KYC \u2014 withdraw" }),
4225
+ ] }) : "Check verification and continue" }),
3969
4226
  errorMsg && /* @__PURE__ */ jsx("p", { className: "pollar-ramp-payment-note", style: { color: "var(--pollar-error-text)" }, children: errorMsg }),
3970
4227
  /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: txStatus === "completed" ? "Done" : "Close" })
3971
4228
  ] }),
@@ -4131,19 +4388,26 @@ var SHARED_FIELD_LABELS = {
4131
4388
  country_code: "Pa\xEDs de residencia"
4132
4389
  }
4133
4390
  };
4134
- var errorCode = (error) => error && typeof error === "object" && typeof error.code === "string" ? error.code : null;
4391
+ var errorCode = (error) => typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
4392
+ var errorBody = (error) => {
4393
+ const body = typeof error === "object" && error !== null && "body" in error ? error.body : void 0;
4394
+ return typeof body === "object" && body !== null ? body : void 0;
4395
+ };
4135
4396
  function missingFieldsOf(error) {
4136
- const body = error && typeof error === "object" ? error.body : void 0;
4137
- return Array.isArray(body?.missing) ? body.missing.filter((item) => typeof item === "string") : [];
4397
+ const body = errorBody(error);
4398
+ const missing = body && "missing" in body ? body.missing : void 0;
4399
+ return Array.isArray(missing) ? missing.filter((item) => typeof item === "string") : [];
4138
4400
  }
4139
4401
  function registrationFixOf(error) {
4140
- const body = error && typeof error === "object" ? error.body : void 0;
4402
+ const body = errorBody(error);
4403
+ const invalid = body && "invalid" in body ? body.invalid : void 0;
4404
+ const formList = body && "forms" in body ? body.forms : void 0;
4141
4405
  const strings = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
4142
- const forms = Array.isArray(body?.forms) ? body.forms.flatMap((item) => {
4143
- const form = item && typeof item === "object" ? item : {};
4144
- return typeof form.formId === "string" ? [{ formId: form.formId, keys: strings(form.keys) }] : [];
4406
+ const forms = Array.isArray(formList) ? formList.flatMap((item) => {
4407
+ if (typeof item !== "object" || item === null || !("formId" in item) || typeof item.formId !== "string") return [];
4408
+ return [{ formId: item.formId, keys: strings("keys" in item ? item.keys : void 0) }];
4145
4409
  }) : [];
4146
- return { invalid: strings(body?.invalid), forms };
4410
+ return { invalid: strings(invalid), forms };
4147
4411
  }
4148
4412
  function RegistryCheckModal({ optionId, progress, onClose, onApproved }) {
4149
4413
  const { getClient, styles } = usePollar();
@@ -4463,9 +4727,51 @@ function ProviderRegistrationModal({
4463
4727
  }
4464
4728
  ) });
4465
4729
  }
4730
+
4731
+ // src/components/ramp-widget/ramp-quotes.ts
4732
+ function unavailableRampMessage(country, unavailable) {
4733
+ if (!unavailable.length) return `No ramp providers available for ${country} yet.`;
4734
+ return unavailable.map(
4735
+ ({ provider, code }) => code === "SDK_RAMPS_PROVIDER_NOT_CONFIGURED" ? `${provider} is not configured correctly. Contact the app administrator to enable this provider.` : `${provider} is temporarily unavailable. Please try again later.`
4736
+ ).join(" ");
4737
+ }
4738
+ async function quoteRampOptions(client, query, routes) {
4739
+ const matching = routes.filter(
4740
+ (route) => route.country === query.country && route.direction === query.direction && route.fiatCurrency === query.currency
4741
+ );
4742
+ const responses = await Promise.allSettled([
4743
+ client.getRampsQuote(query),
4744
+ ...matching.map((route) => client.getRampsQuote({ ...query, routeId: route.routeId, chain: route.asset.chain }))
4745
+ ]);
4746
+ const results = responses.flatMap((response) => response.status === "fulfilled" ? [response.value] : []);
4747
+ if (!results.length) throw responses[0].reason;
4748
+ const quotes = /* @__PURE__ */ new Map();
4749
+ const requirements = /* @__PURE__ */ new Map();
4750
+ const unavailable = /* @__PURE__ */ new Map();
4751
+ for (const result of results) {
4752
+ for (const quote of result.quotes) {
4753
+ const key = JSON.stringify([quote.provider, quote.route?.routeId, quote.route?.asset.chain, quote.rail, quote.protocol]);
4754
+ if (!quotes.has(key)) quotes.set(key, quote);
4755
+ }
4756
+ for (const requirement of result.requirementsRequired ?? [])
4757
+ requirements.set(`${requirement.rampProviderId}:${requirement.corridorId}`, requirement);
4758
+ for (const item of result.unavailable ?? []) unavailable.set(item.provider, item);
4759
+ }
4760
+ const failed = responses.find((response) => response.status === "rejected");
4761
+ if (!quotes.size && !requirements.size && failed?.status === "rejected") throw failed.reason;
4762
+ return {
4763
+ quotes: [...quotes.values()],
4764
+ requirementsRequired: [...requirements.values()],
4765
+ unavailable: [...unavailable.values()]
4766
+ };
4767
+ }
4466
4768
  var TERMINAL = ["completed", "failed"];
4769
+ var BANK_TYPES = ["CLABE", "PIX", "PSE", "ACH", "BREB"];
4770
+ function isBankType(value) {
4771
+ return BANK_TYPES.some((type) => type === value);
4772
+ }
4467
4773
  function requiredFieldsOf(quote) {
4468
- return quote.requiredFields ?? [];
4774
+ return "requiredFields" in quote ? quote.requiredFields : [];
4469
4775
  }
4470
4776
  function flowStepsOf(quotes, selected) {
4471
4777
  const needsDetails = selected != null ? requiredFieldsOf(selected).length > 0 : quotes.some((q) => requiredFieldsOf(q).length > 0);
@@ -4479,7 +4785,8 @@ var STEP_LABEL = {
4479
4785
  status: "Complete"
4480
4786
  };
4481
4787
  function brokenLimitOf(amount, quote) {
4482
- const { minAmount, maxAmount } = quote;
4788
+ const minAmount = "minAmount" in quote ? quote.minAmount : void 0;
4789
+ const maxAmount = "maxAmount" in quote ? quote.maxAmount : void 0;
4483
4790
  if (!Number.isFinite(amount)) return null;
4484
4791
  if (minAmount != null && amount < minAmount) return { limit: "min", value: minAmount };
4485
4792
  if (maxAmount != null && amount > maxAmount) return { limit: "max", value: maxAmount };
@@ -4503,17 +4810,49 @@ var RAMP_ERROR_MESSAGES = {
4503
4810
  SDK_RAMPS_ONCHAIN_SUBMIT_FAILED: "The network rejected the transaction, so nothing was sent. The wallet may need XLM for the fee."
4504
4811
  };
4505
4812
  function rampErrorMessage(e, fallback) {
4506
- const body = e?.body;
4507
- const code = e?.code;
4813
+ const code = typeof e === "object" && e !== null && "code" in e ? e.code : void 0;
4508
4814
  if (typeof code !== "string") return e instanceof Error ? e.message : fallback;
4509
- const limit = body?.limit;
4510
- const value = body?.limitAmount;
4511
- const currency = body?.limitCurrency;
4815
+ const raw = typeof e === "object" && e !== null && "body" in e ? e.body : void 0;
4816
+ const body = typeof raw === "object" && raw !== null ? raw : {};
4817
+ const limit = "limit" in body ? body.limit : void 0;
4818
+ const value = "limitAmount" in body ? body.limitAmount : void 0;
4819
+ const currency = "limitCurrency" in body ? body.limitCurrency : void 0;
4512
4820
  if ((limit === "min" || limit === "max") && typeof value === "number" && typeof currency === "string") {
4513
4821
  return limitMessage(limit, value, currency);
4514
4822
  }
4515
4823
  return RAMP_ERROR_MESSAGES[code] ?? (e instanceof Error ? e.message : fallback);
4516
4824
  }
4825
+ function readRampStorage(key) {
4826
+ try {
4827
+ return typeof window === "undefined" ? null : window.sessionStorage.getItem(key);
4828
+ } catch {
4829
+ return null;
4830
+ }
4831
+ }
4832
+ function writeRampStorage(key, value) {
4833
+ try {
4834
+ if (typeof window === "undefined") return;
4835
+ if (value === null) window.sessionStorage.removeItem(key);
4836
+ else window.sessionStorage.setItem(key, value);
4837
+ } catch {
4838
+ }
4839
+ }
4840
+ function savedRampsOf(key) {
4841
+ try {
4842
+ const value = JSON.parse(readRampStorage(`${key}:saved`) ?? "[]");
4843
+ return Array.isArray(value) ? value.filter((item) => typeof item?.txId === "string" && typeof item?.provider === "string") : [];
4844
+ } catch {
4845
+ return [];
4846
+ }
4847
+ }
4848
+ function hiddenRampsOf(key) {
4849
+ try {
4850
+ const value = JSON.parse(readRampStorage(`${key}:hidden`) ?? "[]");
4851
+ return Array.isArray(value) ? value.filter((id) => typeof id === "string") : [];
4852
+ } catch {
4853
+ return [];
4854
+ }
4855
+ }
4517
4856
  function explorerTxUrl2(chain, hash, network) {
4518
4857
  if (!hash) return null;
4519
4858
  const testnet = network === "testnet";
@@ -4522,7 +4861,7 @@ function explorerTxUrl2(chain, hash, network) {
4522
4861
  return `https://stellar.expert/explorer/${testnet ? "testnet" : "public"}/tx/${hash}`;
4523
4862
  }
4524
4863
  function RampWidget({ onClose }) {
4525
- const { getClient, signTx, wallet, styles, network } = usePollar();
4864
+ const { getClient, wallet, styles, network } = usePollar();
4526
4865
  const walletAddress = wallet?.address ?? "";
4527
4866
  const client = getClient();
4528
4867
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
@@ -4533,7 +4872,7 @@ function RampWidget({ onClose }) {
4533
4872
  const [country, setCountry] = useState("");
4534
4873
  const [fieldValues, setFieldValues] = useState({});
4535
4874
  const setFieldValue = (key, value) => setFieldValues((v) => ({ ...v, [key]: value }));
4536
- const [countries, setCountries] = useState([]);
4875
+ const [legacyCountries, setCountries] = useState([]);
4537
4876
  const [countriesLoading, setCountriesLoading] = useState(true);
4538
4877
  const [refreshing, setRefreshing] = useState(false);
4539
4878
  const [quotes, setQuotes] = useState([]);
@@ -4563,9 +4902,88 @@ function RampWidget({ onClose }) {
4563
4902
  const [fundsInTransit, setFundsInTransit] = useState(false);
4564
4903
  const [retrying, setRetrying] = useState(false);
4565
4904
  const [depositInstructions, setDepositInstructions] = useState(null);
4905
+ const [workflow, setWorkflow] = useState(null);
4906
+ const workflowRef = useRef(null);
4907
+ const storageKey = `pollar:ramp:${client.apiKey}:${walletAddress}`;
4908
+ const [savedRamps, setSavedRamps] = useState([]);
4909
+ const [legacySignature, setLegacySignature] = useState(void 0);
4910
+ function acceptWorkflow(incoming) {
4911
+ if (hiddenRampsOf(storageKey).includes(incoming.txId)) return false;
4912
+ const next = mergeRampSnapshot(workflowRef.current, incoming);
4913
+ workflowRef.current = next;
4914
+ setWorkflow(next);
4915
+ if (next === incoming) {
4916
+ const pending = savedRampsOf(storageKey).filter((item) => item.txId !== incoming.txId);
4917
+ if (!["completed", "failed", "refunded"].includes(incoming.lifecycleState ?? incoming.status))
4918
+ pending.push({
4919
+ txId: incoming.txId,
4920
+ provider: "provider" in incoming && typeof incoming.provider === "string" ? incoming.provider : provider || "Ramp"
4921
+ });
4922
+ writeRampStorage(`${storageKey}:saved`, JSON.stringify(pending));
4923
+ writeRampStorage(storageKey, incoming.txId);
4924
+ writeRampStorage(`${storageKey}:selection`, null);
4925
+ setSavedRamps(pending);
4926
+ }
4927
+ return next === incoming;
4928
+ }
4929
+ const [routes, setRoutes] = useState([]);
4930
+ const countries = mergeRampCountries(legacyCountries, routes);
4931
+ useEffect(() => {
4932
+ if (!country && countries[0]) {
4933
+ setCountry(countries[0].code);
4934
+ setCurrency(countries[0].currency ?? "");
4935
+ }
4936
+ }, [countries, country]);
4937
+ useEffect(() => {
4938
+ let active = true;
4939
+ void client.getRampRoutes().then((result) => {
4940
+ if (active) setRoutes(result.routes);
4941
+ }).catch(() => {
4942
+ });
4943
+ return () => {
4944
+ active = false;
4945
+ };
4946
+ }, [client]);
4566
4947
  const [completing, setCompleting] = useState(false);
4948
+ const completionLocked = useRef(false);
4567
4949
  const [errorMsg, setErrorMsg] = useState(null);
4568
- const signingRef = useRef(false);
4950
+ useEffect(() => {
4951
+ if (typeof window === "undefined" || !walletAddress) return;
4952
+ setSavedRamps(savedRampsOf(storageKey));
4953
+ if (readRampStorage(`${storageKey}:selection`) === "1") return;
4954
+ const hidden = hiddenRampsOf(storageKey);
4955
+ const savedId = [new URLSearchParams(window.location.search).get("rampTxId"), readRampStorage(storageKey)].find(
4956
+ (id) => id && !hidden.includes(id)
4957
+ );
4958
+ if (!savedId) return;
4959
+ let active = true;
4960
+ void client.getRampTransaction(savedId).then(async (result) => {
4961
+ if (!active || result.transactionVersion === void 0) return;
4962
+ setDirection(result.direction);
4963
+ await applyResult(result);
4964
+ }).catch(() => {
4965
+ if (active)
4966
+ setErrorMsg("Could not restore the saved ramp. Retry opening the modal; do not create a replacement order.");
4967
+ });
4968
+ return () => {
4969
+ active = false;
4970
+ };
4971
+ }, [client, storageKey, walletAddress]);
4972
+ async function resumeSavedRamp(id) {
4973
+ if (completionLocked.current) return;
4974
+ setIsLoading(true);
4975
+ setErrorMsg(null);
4976
+ try {
4977
+ const result = await client.getRampTransaction(id);
4978
+ if (result.transactionVersion === void 0) throw new Error("This saved ramp cannot be restored.");
4979
+ setDirection(result.direction);
4980
+ await applyResult(result);
4981
+ } catch (error) {
4982
+ setErrorMsg(rampErrorMessage(error, "Could not resume the saved ramp."));
4983
+ } finally {
4984
+ setIsLoading(false);
4985
+ }
4986
+ }
4569
4987
  const directionRef = useRef(direction);
4570
4988
  directionRef.current = direction;
4571
4989
  function applySettlement(tx) {
@@ -4588,23 +5006,20 @@ function RampWidget({ onClose }) {
4588
5006
  setRetrying(false);
4589
5007
  }
4590
5008
  }
5009
+ const workflowTerminal = !!workflow && ["completed", "failed", "refunded"].includes(workflow.lifecycleState ?? workflow.status);
4591
5010
  useEffect(() => {
4592
5011
  if (step !== "status" || !txId) return;
4593
- if (txStatus && TERMINAL.includes(txStatus)) return;
5012
+ if (workflowTerminal || txStatus && TERMINAL.includes(txStatus)) return;
4594
5013
  let active = true;
4595
5014
  const id = setInterval(async () => {
4596
5015
  try {
4597
5016
  const tx = await client.getRampTransaction(txId);
4598
5017
  if (!active) return;
5018
+ if (tx.transactionVersion !== void 0 && !acceptWorkflow(tx)) return;
4599
5019
  setTxStatus(tx.status);
4600
5020
  applySettlement(tx);
4601
- const pending = tx.pendingSignature;
4602
- if (pending && !signingRef.current) {
4603
- signingRef.current = true;
4604
- void resumeRef.current(txId, pending).finally(() => {
4605
- signingRef.current = false;
4606
- });
4607
- }
5021
+ const pending = "pendingSignature" in tx ? tx.pendingSignature : void 0;
5022
+ if (pending) setLegacySignature(pending);
4608
5023
  if (tx.kycUrl) setKycUrl(tx.kycUrl);
4609
5024
  if (tx.depositInstructions) setDepositInstructions(tx.depositInstructions);
4610
5025
  if (TERMINAL.includes(tx.status)) clearInterval(id);
@@ -4615,9 +5030,9 @@ function RampWidget({ onClose }) {
4615
5030
  active = false;
4616
5031
  clearInterval(id);
4617
5032
  };
4618
- }, [step, txId, txStatus, client]);
5033
+ }, [step, txId, txStatus, workflowTerminal, client]);
4619
5034
  useEffect(() => {
4620
- if (step !== "status" || !kycPending || kycApproved) return;
5035
+ if (workflow || step !== "status" || !kycPending || kycApproved || provider.toLowerCase() !== "abroad") return;
4621
5036
  if (onboardingStatus === "awaiting_provider") return;
4622
5037
  let active = true;
4623
5038
  const check = async () => {
@@ -4633,14 +5048,15 @@ function RampWidget({ onClose }) {
4633
5048
  active = false;
4634
5049
  clearInterval(id);
4635
5050
  };
4636
- }, [step, kycPending, kycApproved, onboardingStatus, client]);
5051
+ }, [step, kycPending, kycApproved, onboardingStatus, workflow, client, provider]);
4637
5052
  async function loadCountries(resetSelection) {
4638
5053
  setCountriesLoading(true);
4639
5054
  try {
4640
5055
  const { countries: list } = await client.getRampCountries();
4641
5056
  setCountries(list);
4642
- const first = list[0];
4643
- const stillValid = list.some((c) => c.code === country);
5057
+ const available = mergeRampCountries(list, routes);
5058
+ const first = available[0];
5059
+ const stillValid = available.some((c) => c.code === country);
4644
5060
  if (first && (resetSelection || !stillValid)) {
4645
5061
  setCountry(first.code);
4646
5062
  if (first.currency) setCurrency(first.currency);
@@ -4665,11 +5081,22 @@ function RampWidget({ onClose }) {
4665
5081
  try {
4666
5082
  await loadCountries(false);
4667
5083
  if (step === "select_route") {
4668
- const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
5084
+ const result = await quoteRampOptions(
5085
+ client,
5086
+ {
5087
+ country,
5088
+ amount: Number(amount),
5089
+ amountExact: amount,
5090
+ currency,
5091
+ direction
5092
+ },
5093
+ routes
5094
+ );
4669
5095
  setQuotes(result.quotes ?? []);
4670
5096
  setRequirementsRequired(result.requirementsRequired ?? []);
4671
5097
  } else if (step === "status" && txId) {
4672
5098
  const tx = await client.getRampTransaction(txId);
5099
+ if (tx.transactionVersion !== void 0 && !acceptWorkflow(tx)) return;
4673
5100
  setTxStatus(tx.status);
4674
5101
  applySettlement(tx);
4675
5102
  if (tx.kycUrl) setKycUrl(tx.kycUrl);
@@ -4681,11 +5108,16 @@ function RampWidget({ onClose }) {
4681
5108
  }
4682
5109
  }
4683
5110
  function resetToInput({ keepMessage = false } = {}) {
5111
+ writeRampStorage(storageKey, null);
5112
+ writeRampStorage(`${storageKey}:selection`, "1");
4684
5113
  setStep("input");
4685
5114
  setQuotes([]);
4686
5115
  setRequirementsRequired([]);
4687
5116
  setSelectedQuote(null);
4688
5117
  setFieldValues({});
5118
+ setWorkflow(null);
5119
+ workflowRef.current = null;
5120
+ setLegacySignature(void 0);
4689
5121
  setTxId(null);
4690
5122
  setProvider("");
4691
5123
  setKycUrl(null);
@@ -4701,8 +5133,20 @@ function RampWidget({ onClose }) {
4701
5133
  setNoticeMsg(null);
4702
5134
  if (!keepMessage) setErrorMsg(null);
4703
5135
  }
5136
+ function dismissSavedRamp(id) {
5137
+ if (completionLocked.current) return;
5138
+ const pending = savedRampsOf(storageKey).filter((ramp) => ramp.txId !== id);
5139
+ writeRampStorage(`${storageKey}:saved`, JSON.stringify(pending));
5140
+ writeRampStorage(`${storageKey}:hidden`, JSON.stringify([.../* @__PURE__ */ new Set([...hiddenRampsOf(storageKey), id])]));
5141
+ if (readRampStorage(storageKey) === id) writeRampStorage(storageKey, null);
5142
+ setSavedRamps(pending);
5143
+ if (txId === id) resetToInput();
5144
+ setNoticeMsg(
5145
+ "Removed from this tab\u2019s pending list. The operation may still be active at the provider; no payment was cancelled or refunded."
5146
+ );
5147
+ }
4704
5148
  async function resumeWithSignature(id, ps) {
4705
- const outcome = await signTx(ps.unsignedXdr);
5149
+ const outcome = await client.signTx(ps.unsignedXdr, { skipSponsorship: ps.action === "sep10" });
4706
5150
  if (outcome.status === "cosign_pending") {
4707
5151
  setErrorMsg("Approve the signature on your device, then resume.");
4708
5152
  setStep("error");
@@ -4715,20 +5159,15 @@ function RampWidget({ onClose }) {
4715
5159
  }
4716
5160
  const result = await client.submitRampSignature(id, {
4717
5161
  signedXdr: outcome.signedXdr,
4718
- // The CCTP actions are not in the generated schema until it is regenerated.
4719
5162
  action: ps.action
4720
5163
  });
4721
5164
  await applyResult(result);
4722
5165
  }
4723
- const resumeRef = useRef(resumeWithSignature);
4724
- resumeRef.current = resumeWithSignature;
4725
5166
  async function applyResult(result) {
5167
+ if ("transactionVersion" in result && result.transactionVersion !== void 0 && !acceptWorkflow(result)) return;
4726
5168
  setTxId(result.txId);
4727
5169
  setProvider(result.provider);
4728
- if (result.pendingSignature) {
4729
- await resumeWithSignature(result.txId, result.pendingSignature);
4730
- return;
4731
- }
5170
+ setLegacySignature(result.pendingSignature);
4732
5171
  setKycUrl(result.kycUrl ?? null);
4733
5172
  setTosUrl(result.tosUrl ?? null);
4734
5173
  setOnboardingStatus(result.onboardingStatus);
@@ -4740,14 +5179,21 @@ function RampWidget({ onClose }) {
4740
5179
  setStep("status");
4741
5180
  }
4742
5181
  async function loadQuotes() {
4743
- const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
5182
+ const result = await quoteRampOptions(
5183
+ client,
5184
+ {
5185
+ country,
5186
+ amount: Number(amount),
5187
+ amountExact: amount,
5188
+ currency,
5189
+ direction
5190
+ },
5191
+ routes
5192
+ );
4744
5193
  const list = result.quotes ?? [];
4745
5194
  const locked = result.requirementsRequired ?? [];
4746
5195
  if (list.length === 0 && locked.length === 0) {
4747
- const down = (result.unavailable ?? []).map((u) => u.provider);
4748
- setErrorMsg(
4749
- down.length > 0 ? `${down.join(", ")} ${down.length === 1 ? "is" : "are"} temporarily unavailable. Please try again later.` : `No ramp providers available for ${country} yet.`
4750
- );
5196
+ setErrorMsg(unavailableRampMessage(country, result.unavailable ?? []));
4751
5197
  setStep("error");
4752
5198
  return false;
4753
5199
  }
@@ -4793,19 +5239,40 @@ function RampWidget({ onClose }) {
4793
5239
  setIsLoading(true);
4794
5240
  setErrorMsg(null);
4795
5241
  try {
4796
- const base = { quoteId: quote.quoteId, amount: Number(amount), currency, country };
4797
- if (walletAddress) base.walletAddress = walletAddress;
4798
- const STANDARD_BODY_KEYS = /* @__PURE__ */ new Set(["email", "fullName", "taxId", "qrCode"]);
5242
+ const body = {
5243
+ quoteId: quote.quoteId,
5244
+ amount: quote.fiatAmount ?? Number(amount),
5245
+ ...quote.terms ? { amountExact: quote.terms.fiatAmount } : {},
5246
+ currency,
5247
+ country,
5248
+ ...walletAddress ? { walletAddress } : {}
5249
+ };
4799
5250
  const extraFields = {};
4800
5251
  for (const f of requiredFieldsOf(quote)) {
4801
5252
  const val = (fieldValues[f.key] ?? "").trim();
4802
5253
  if (!val) continue;
4803
- if (f.bankType) base.bankDetails = { type: f.bankType, value: val };
4804
- else if (STANDARD_BODY_KEYS.has(f.key)) base[f.key] = val;
5254
+ if (f.bankType) {
5255
+ if (isBankType(f.bankType)) body.bankDetails = { type: f.bankType, value: val };
5256
+ } else if (f.key === "email" || f.key === "fullName" || f.key === "taxId" || f.key === "qrCode") body[f.key] = val;
4805
5257
  else extraFields[f.key] = val;
4806
5258
  }
4807
- if (Object.keys(extraFields).length > 0) base.fields = extraFields;
4808
- const result = direction === "onramp" ? await client.createOnRamp(base) : await client.createOffRamp(base);
5259
+ if (quote.route)
5260
+ body.fields = Object.fromEntries(
5261
+ requiredFieldsOf(quote).map((field) => [field.key, (fieldValues[field.key] ?? "").trim()]).filter(([, value]) => value)
5262
+ );
5263
+ else if (Object.keys(extraFields).length > 0) body.fields = extraFields;
5264
+ const onrampBody = {
5265
+ quoteId: body.quoteId,
5266
+ amount: body.amount,
5267
+ currency: body.currency,
5268
+ country: body.country,
5269
+ ...body.amountExact !== void 0 ? { amountExact: body.amountExact } : {},
5270
+ ...body.walletAddress !== void 0 ? { walletAddress: body.walletAddress } : {},
5271
+ ...body.email !== void 0 ? { email: body.email } : {},
5272
+ ...body.fullName !== void 0 ? { fullName: body.fullName } : {},
5273
+ ...body.fields !== void 0 ? { fields: body.fields } : {}
5274
+ };
5275
+ const result = direction === "onramp" ? await client.createOnRamp(onrampBody) : await client.createOffRamp(body);
4809
5276
  await applyResult(result);
4810
5277
  } catch (e) {
4811
5278
  const requirement = requiredRampKyc(e);
@@ -4855,8 +5322,24 @@ function RampWidget({ onClose }) {
4855
5322
  function handleOpenTos() {
4856
5323
  if (tosUrl) window.open(tosUrl, "_blank", "noopener,noreferrer");
4857
5324
  }
5325
+ async function handleLegacySignature() {
5326
+ if (!txId || !legacySignature || completionLocked.current) return;
5327
+ completionLocked.current = true;
5328
+ setCompleting(true);
5329
+ setErrorMsg(null);
5330
+ try {
5331
+ await resumeWithSignature(txId, legacySignature);
5332
+ } catch (e) {
5333
+ setErrorMsg(rampErrorMessage(e, "Failed to submit the signature."));
5334
+ setStep("error");
5335
+ } finally {
5336
+ completionLocked.current = false;
5337
+ setCompleting(false);
5338
+ }
5339
+ }
4858
5340
  async function handleCompleteWithdraw() {
4859
- if (!txId) return;
5341
+ if (!txId || completionLocked.current) return;
5342
+ completionLocked.current = true;
4860
5343
  setCompleting(true);
4861
5344
  setErrorMsg(null);
4862
5345
  try {
@@ -4873,11 +5356,12 @@ function RampWidget({ onClose }) {
4873
5356
  msg.includes("KYC") ? "Finish KYC at the provider first, then try again." : msg || "Failed to complete the withdrawal."
4874
5357
  );
4875
5358
  } finally {
5359
+ completionLocked.current = false;
4876
5360
  setCompleting(false);
4877
5361
  }
4878
5362
  }
4879
5363
  const kycBlocking = kycPending && !kycApproved;
4880
- const canComplete = direction === "offramp" && step === "status" && txStatus !== "completed" && !stellarTxHash && !txHash && !kycPending;
5364
+ const canComplete = !workflow && !legacySignature && direction === "offramp" && step === "status" && txStatus !== "completed" && txStatus !== "failed" && !stellarTxHash && !txHash && !kycPending;
4881
5365
  const flowSteps = flowStepsOf(quotes, selectedQuote);
4882
5366
  const flowStepIndex = flowSteps.indexOf(STEP_LABEL[step] ?? "");
4883
5367
  if (pendingKyc?.type === "FORM") {
@@ -4935,6 +5419,20 @@ function RampWidget({ onClose }) {
4935
5419
  return /* @__PURE__ */ jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsx(
4936
5420
  RampWidgetTemplate,
4937
5421
  {
5422
+ workflowContent: workflow ? /* @__PURE__ */ jsx(
5423
+ RampWorkflow,
5424
+ {
5425
+ client,
5426
+ snapshot: workflow,
5427
+ onBusyChange: (busy) => {
5428
+ completionLocked.current = busy;
5429
+ setCompleting(busy);
5430
+ },
5431
+ onChange: (next) => {
5432
+ if (acceptWorkflow(next)) setTxStatus(next.status);
5433
+ }
5434
+ }
5435
+ ) : legacySignature && step === "status" && txStatus !== "completed" && txStatus !== "failed" ? /* @__PURE__ */ jsx("button", { type: "button", disabled: completing, onClick: () => void handleLegacySignature(), children: "Authorize wallet request" }) : void 0,
4938
5436
  theme,
4939
5437
  accentColor,
4940
5438
  styleOverrides,
@@ -4971,9 +5469,18 @@ function RampWidget({ onClose }) {
4971
5469
  depositInstructions,
4972
5470
  canComplete,
4973
5471
  completing,
5472
+ savedRamps,
5473
+ onResumeRamp: resumeSavedRamp,
5474
+ onDismissRamp: dismissSavedRamp,
5475
+ onDismissCurrentRamp: workflow ? () => dismissSavedRamp(workflow.txId) : void 0,
5476
+ onExploreRoutes: workflow ? () => {
5477
+ if (!completionLocked.current) resetToInput();
5478
+ } : void 0,
4974
5479
  errorMsg,
4975
5480
  noticeMsg,
4976
- onDirectionChange: setDirection,
5481
+ onDirectionChange: (next) => {
5482
+ setDirection(next);
5483
+ },
4977
5484
  onAmountChange: (next) => {
4978
5485
  setAmount(next);
4979
5486
  setErrorMsg(null);
@@ -5006,49 +5513,6 @@ function walletNotReadyReason(wallet, chain) {
5006
5513
  }
5007
5514
  return null;
5008
5515
  }
5009
-
5010
- // src/lib/qr-code/index.tsx
5011
- var import_ErrorCorrectLevel = __toESM(require_ErrorCorrectLevel());
5012
- var import_QRCode = __toESM(require_QRCode());
5013
- var QRCodeSvg = forwardRef(function QRCodeSvg2({ bgColor, bgD, fgD, fgColor, size, title, viewBoxSize, xmlns = "http://www.w3.org/2000/svg", ...props }, ref) {
5014
- return /* @__PURE__ */ jsxs("svg", { ...props, height: size, ref, viewBox: `0 0 ${viewBoxSize} ${viewBoxSize}`, width: size, xmlns, children: [
5015
- title ? /* @__PURE__ */ jsx("title", { children: title }) : null,
5016
- /* @__PURE__ */ jsx("path", { d: bgD, fill: bgColor }),
5017
- /* @__PURE__ */ jsx("path", { d: fgD, fill: fgColor })
5018
- ] });
5019
- });
5020
- QRCodeSvg.displayName = "QRCodeSvg";
5021
- function bytesToBinaryString(bytes) {
5022
- return bytes.map((b) => String.fromCharCode(b & 255)).join("");
5023
- }
5024
- function encodeStringToUtf8Bytes(input) {
5025
- return Array.from(new TextEncoder().encode(input));
5026
- }
5027
- var QRCode = forwardRef(function QRCode2({ bgColor = "#FFFFFF", fgColor = "#000000", level = "L", size = 256, value, ...props }, ref) {
5028
- const qrcode = new import_QRCode.default(-1, import_ErrorCorrectLevel.default[level]);
5029
- const utf8Bytes = encodeStringToUtf8Bytes(value);
5030
- const binaryString = bytesToBinaryString(utf8Bytes);
5031
- qrcode.addData(binaryString, "Byte");
5032
- qrcode.make();
5033
- const cells = qrcode.modules;
5034
- return /* @__PURE__ */ jsx(
5035
- QRCodeSvg,
5036
- {
5037
- ...props,
5038
- bgColor,
5039
- bgD: cells.map(
5040
- (row, rowIndex) => row.map((cell, cellIndex) => !cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
5041
- ).join(" "),
5042
- fgColor,
5043
- fgD: cells.map(
5044
- (row, rowIndex) => row.map((cell, cellIndex) => cell ? `M ${cellIndex} ${rowIndex} l 1 0 0 1 -1 0 Z` : "").join(" ")
5045
- ).join(" "),
5046
- ref,
5047
- size,
5048
- viewBoxSize: cells.length
5049
- }
5050
- );
5051
- });
5052
5516
  function PollarLogo(props) {
5053
5517
  return /* @__PURE__ */ jsxs("svg", { width: "45", height: "48", viewBox: "0 0 45 48", fill: "none", xmlns: "http://www.w3.org/2000/svg", ...props, children: [
5054
5518
  /* @__PURE__ */ jsx(
@@ -5089,6 +5553,8 @@ function ReceiveModalTemplate({
5089
5553
  onSelectChain,
5090
5554
  copied,
5091
5555
  notReadyReason,
5556
+ recoveryLocked,
5557
+ onSetupRecovery,
5092
5558
  onCopy,
5093
5559
  onClose
5094
5560
  }) {
@@ -5107,7 +5573,10 @@ function ReceiveModalTemplate({
5107
5573
  /* @__PURE__ */ jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", children: /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 16 16", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "M2 2l12 12M14 2L2 14", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }) }) }) })
5108
5574
  ] }),
5109
5575
  chains && onSelectChain && /* @__PURE__ */ jsx(ChainSelect, { value: selectedChain, options: chains, onChange: onSelectChain }),
5110
- walletAddress ? /* @__PURE__ */ jsxs(Fragment, { children: [
5576
+ recoveryLocked ? /* @__PURE__ */ jsxs("div", { className: "pollar-receive-locked", role: "alert", children: [
5577
+ /* @__PURE__ */ jsx("p", { children: "Set up a way to recover this wallet before receiving funds. Without one, losing this device means losing whatever you receive, and nobody can get it back." }),
5578
+ onSetupRecovery && /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-receive-copy-btn", onClick: onSetupRecovery, children: "Set up recovery" })
5579
+ ] }) : walletAddress ? /* @__PURE__ */ jsxs(Fragment, { children: [
5111
5580
  /* @__PURE__ */ jsxs("div", { className: "pollar-receive-qr", children: [
5112
5581
  /* @__PURE__ */ jsxs("div", { className: "pollar-receive-network-badge", children: [
5113
5582
  /* @__PURE__ */ jsx("span", { className: "pollar-receive-network-dot", "aria-hidden": true }),
@@ -5181,7 +5650,7 @@ function ReceiveModalTemplate({
5181
5650
  );
5182
5651
  }
5183
5652
  function ReceiveModal({ onClose }) {
5184
- const { wallet, wallets, styles } = usePollar();
5653
+ const { wallet, wallets, styles, recoveryLocked, openRecoveryModal } = usePollar();
5185
5654
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
5186
5655
  const [copied, setCopied] = useState(false);
5187
5656
  const copyTimerRef = useRef(null);
@@ -5223,6 +5692,11 @@ function ReceiveModal({ onClose }) {
5223
5692
  setSelectedChain(chain);
5224
5693
  },
5225
5694
  notReadyReason,
5695
+ recoveryLocked,
5696
+ onSetupRecovery: () => {
5697
+ onClose();
5698
+ openRecoveryModal();
5699
+ },
5226
5700
  copied,
5227
5701
  onCopy: handleCopy,
5228
5702
  onClose
@@ -5746,6 +6220,11 @@ function SendModalTemplate({
5746
6220
  );
5747
6221
  }
5748
6222
  var MAX_MEMO_ID = 18446744073709551615n;
6223
+ var SOLANA_FEE_MARGIN_LAMPORTS = 10000n;
6224
+ function solanaMaxAfterFee(available) {
6225
+ const lamports = BigInt(toBaseUnits(available, 9)) - SOLANA_FEE_MARGIN_LAMPORTS;
6226
+ return fromBaseUnits((lamports > 0n ? lamports : 0n).toString(), 9);
6227
+ }
5749
6228
  function parseMemo(type, raw) {
5750
6229
  const value = type === "id" ? raw.trim() : raw;
5751
6230
  if (value.trim() === "") return void 0;
@@ -5905,7 +6384,15 @@ function SendModal({ onClose }) {
5905
6384
  }
5906
6385
  function handleMax() {
5907
6386
  if (selectedAsset?.available !== null && selectedAsset?.available !== void 0) {
5908
- setAmount(selectedAsset.available);
6387
+ const paysOwnSolFee = selectedChain === "SOLANA" && selectedAsset.type === "native" && selectedAsset.sponsored !== true;
6388
+ let max = selectedAsset.available;
6389
+ if (paysOwnSolFee) {
6390
+ try {
6391
+ max = solanaMaxAfterFee(selectedAsset.available);
6392
+ } catch {
6393
+ }
6394
+ }
6395
+ setAmount(max);
5909
6396
  setFormError("");
5910
6397
  }
5911
6398
  }
@@ -7104,9 +7591,12 @@ function EarnModal({ onClose }) {
7104
7591
  var nonEmpty2 = (value) => typeof value === "string" && !!value.trim();
7105
7592
  function requiredCardStep(error) {
7106
7593
  if (!error || typeof error !== "object") return null;
7107
- const { code, body } = error;
7108
- if (code !== "SDK_CARDS_REQUIREMENT_REQUIRED" || !body || typeof body !== "object") return null;
7109
- const { cardProviderId, requirementType, optionId } = body;
7594
+ if (!("code" in error) || error.code !== "SDK_CARDS_REQUIREMENT_REQUIRED") return null;
7595
+ const body = "body" in error ? error.body : void 0;
7596
+ if (typeof body !== "object" || body === null) return null;
7597
+ const cardProviderId = "cardProviderId" in body ? body.cardProviderId : void 0;
7598
+ const requirementType = "requirementType" in body ? body.requirementType : void 0;
7599
+ const optionId = "optionId" in body ? body.optionId : void 0;
7110
7600
  if (!nonEmpty2(cardProviderId) || !nonEmpty2(optionId)) return null;
7111
7601
  if (requirementType === "KYC" || requirementType === "FORM" || requirementType === "REGISTRY_CHECK" || requirementType === "PROVIDER_REGISTRATION") {
7112
7602
  return { cardProviderId, type: requirementType, optionId };
@@ -7760,15 +8250,17 @@ function CardModal({ onClose }) {
7760
8250
  transactions === null && /* @__PURE__ */ jsx("p", { className: "pollar-card-note", children: "Loading..." }),
7761
8251
  transactions?.length === 0 && /* @__PURE__ */ jsx("p", { className: "pollar-card-note", children: "No movements yet." }),
7762
8252
  transactions?.map((tx) => {
7763
- const purchase = tx.purchase;
7764
- const local = purchase?.localAmount && purchase.localCurrency && purchase.localCurrency !== "USD" ? money(String(purchase.localAmount), purchase.localCurrency) : null;
8253
+ const localAmount = typeof tx.purchase?.localAmount === "number" ? tx.purchase.localAmount : null;
8254
+ const localCurrency = typeof tx.purchase?.localCurrency === "string" ? tx.purchase.localCurrency : null;
8255
+ const purchaseStatus = typeof tx.purchase?.status === "string" ? tx.purchase.status : null;
8256
+ const local = localAmount && localCurrency && localCurrency !== "USD" ? money(String(localAmount), localCurrency) : null;
7765
8257
  return /* @__PURE__ */ jsxs("div", { className: "pollar-card-tx", children: [
7766
8258
  /* @__PURE__ */ jsxs("div", { children: [
7767
8259
  tx.merchantName ?? TX_LABELS[tx.type] ?? tx.type,
7768
8260
  /* @__PURE__ */ jsxs("span", { className: "pollar-card-tx-meta", children: [
7769
8261
  new Date(tx.occurredAt).toLocaleDateString(),
7770
8262
  tx.merchantName ? ` \xB7 ${TX_LABELS[tx.type] ?? tx.type}` : "",
7771
- purchase?.status && purchase.status !== "completed" ? ` \xB7 ${purchase.status}` : ""
8263
+ purchaseStatus && purchaseStatus !== "completed" ? ` \xB7 ${purchaseStatus}` : ""
7772
8264
  ] })
7773
8265
  ] }),
7774
8266
  /* @__PURE__ */ jsxs("div", { className: "pollar-card-tx-amount", "data-direction": tx.direction ?? void 0, children: [
@@ -8167,7 +8659,8 @@ function useCosign() {
8167
8659
  revokeDevice: useCallback(
8168
8660
  (deviceId, options) => getClient().cosign.revokeDevice(deviceId, options),
8169
8661
  [getClient]
8170
- )
8662
+ ),
8663
+ switchCustody: useCallback((to) => getClient().cosign.switchCustody(to), [getClient])
8171
8664
  };
8172
8665
  }
8173
8666
  var OPERATION_LABELS = {
@@ -8189,6 +8682,15 @@ function expiresIn(request) {
8189
8682
  const m = Math.floor(ms % 36e5 / 6e4);
8190
8683
  return h > 0 ? `${h}h ${m}m` : `${m}m`;
8191
8684
  }
8685
+ function readableAmount(details) {
8686
+ const { amount, decimals, symbol } = details;
8687
+ if (typeof amount !== "string" || typeof decimals !== "number" || typeof symbol !== "string") return null;
8688
+ try {
8689
+ return { amount: fromBaseUnits(amount, decimals), symbol };
8690
+ } catch {
8691
+ return null;
8692
+ }
8693
+ }
8192
8694
  function detailRows(request) {
8193
8695
  const details = request.details ?? {};
8194
8696
  const rows = [];
@@ -8200,8 +8702,9 @@ function detailRows(request) {
8200
8702
  ]);
8201
8703
  };
8202
8704
  push("Destination", details.destination);
8203
- push("Amount", details.amount);
8204
- push("Asset", details.asset ?? details.assetCode);
8705
+ const readable = readableAmount(details);
8706
+ push("Amount", readable ? `${readable.amount} ${readable.symbol}` : details.amount);
8707
+ push("Asset", details.asset ?? details.assetCode ?? readable?.symbol);
8205
8708
  push("Network", request.network);
8206
8709
  return rows;
8207
8710
  }
@@ -8276,7 +8779,6 @@ function CosignModalTemplate({
8276
8779
  current && /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-detail", children: [
8277
8780
  /* @__PURE__ */ jsx("p", { className: "pollar-cosign-detail-title", children: labelOf(current) }),
8278
8781
  current.summary && /* @__PURE__ */ jsx("p", { className: "pollar-cosign-muted", children: current.summary }),
8279
- /* @__PURE__ */ jsx(GroupSteps, { request: current }),
8280
8782
  /* @__PURE__ */ jsxs("dl", { className: "pollar-cosign-rows", children: [
8281
8783
  detailRows(current).map(([k, v]) => /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-row", children: [
8282
8784
  /* @__PURE__ */ jsx("dt", { children: k }),
@@ -8327,22 +8829,6 @@ function CosignModalTemplate({
8327
8829
  }
8328
8830
  );
8329
8831
  }
8330
- function GroupSteps({ request }) {
8331
- const group = cosignGroupOf(request);
8332
- if (!group || group.steps.length < 2) return null;
8333
- const stepKey = cosignStepKeyOf(request);
8334
- return /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-group", children: [
8335
- /* @__PURE__ */ jsx("p", { className: "pollar-cosign-group-title", children: group.title }),
8336
- /* @__PURE__ */ jsx("p", { className: "pollar-cosign-muted", children: "Approving covers these steps; each one is checked before it is signed." }),
8337
- /* @__PURE__ */ jsx("ol", { className: "pollar-cosign-group-steps", children: group.steps.map((step) => {
8338
- const isThis = stepKey !== null && stepInGroup({ steps: [step] }, stepKey);
8339
- return /* @__PURE__ */ jsxs("li", { className: isThis ? "pollar-cosign-group-step-current" : void 0, children: [
8340
- step.label,
8341
- isThis && /* @__PURE__ */ jsx("span", { className: "pollar-cosign-muted", children: " (this one)" })
8342
- ] }, step.key);
8343
- }) })
8344
- ] });
8345
- }
8346
8832
  function CosignModal({ onClose }) {
8347
8833
  const { getClient, styles, network } = usePollar();
8348
8834
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
@@ -8425,6 +8911,493 @@ function CosignModal({ onClose }) {
8425
8911
  }
8426
8912
  ) });
8427
8913
  }
8914
+ var CHAIN_NAME3 = { STELLAR: "Stellar", SOLANA: "Solana", POLYGON: "Polygon" };
8915
+ var short = (address) => `${address.slice(0, 6)}...${address.slice(-4)}`;
8916
+ var SELF_CUSTODY_RISKS = "In a self-custody wallet only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign.";
8917
+ function CustodyMigrationModalTemplate(p) {
8918
+ const cssVars = buildModalCssVars(p.theme, p.accentColor, p.styleOverrides);
8919
+ const [pick, setPick] = useState(p.state.choice ? null : p.state.toCustody);
8920
+ const [risksAccepted, setRisksAccepted] = useState(false);
8921
+ const custody = p.state.choice ? pick : p.state.toCustody;
8922
+ const canMove = !!custody && (custody !== "SELF_CUSTODY" || risksAccepted) && !p.busy;
8923
+ const moving = p.state.wallets.filter((w) => w.required);
8924
+ const changing = moving.filter((w) => w.addressChanges);
8925
+ const failed = moving.some((w) => w.status === "FAILED");
8926
+ return /* @__PURE__ */ jsxs("div", { className: "pollar-modal-card pollar-recovery-modal", "data-theme": p.theme, style: cssVars, children: [
8927
+ /* @__PURE__ */ jsx("div", { className: "pollar-modal-header", children: /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: "Move your wallets to this device" }) }),
8928
+ /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-body", children: [
8929
+ /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Until now Pollar held the keys of your wallets. This app now keeps wallets on its users' devices, so they move here, with everything they hold." }),
8930
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
8931
+ /* @__PURE__ */ jsx("h3", { children: "What moves" }),
8932
+ moving.map((w) => /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item", children: [
8933
+ /* @__PURE__ */ jsxs("span", { children: [
8934
+ CHAIN_NAME3[w.chain] ?? w.chain,
8935
+ " \xB7 ",
8936
+ short(w.address)
8937
+ ] }),
8938
+ /* @__PURE__ */ jsx("span", { className: "pollar-recovery-tag", children: w.addressChanges ? "New address" : "Same address" })
8939
+ ] }, w.chain))
8940
+ ] }),
8941
+ changing.length > 0 && /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: [
8942
+ "Your ",
8943
+ changing.map((w) => CHAIN_NAME3[w.chain] ?? w.chain).join(" and "),
8944
+ " address changes: every token and the balance go to a new address on this device. Stop sharing the old one; anything sent there later needs help from support to recover."
8945
+ ] }),
8946
+ p.state.choice && /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
8947
+ /* @__PURE__ */ jsx("h3", { children: "Choose how your wallet signs" }),
8948
+ /* @__PURE__ */ jsxs("label", { className: "pollar-recovery-check", children: [
8949
+ /* @__PURE__ */ jsx("input", { type: "radio", name: "custody", checked: pick === "COSIGNED", onChange: () => setPick("COSIGNED") }),
8950
+ " ",
8951
+ "Co-signed: you and the app approve every signature together, and the wallet can be recovered with help."
8952
+ ] }),
8953
+ /* @__PURE__ */ jsxs("label", { className: "pollar-recovery-check", children: [
8954
+ /* @__PURE__ */ jsx("input", { type: "radio", name: "custody", checked: pick === "SELF_CUSTODY", onChange: () => setPick("SELF_CUSTODY") }),
8955
+ " ",
8956
+ "Self-custody: only your device signs. Nobody else can move or recover your funds."
8957
+ ] })
8958
+ ] }),
8959
+ custody === "SELF_CUSTODY" && /* @__PURE__ */ jsxs(Fragment, { children: [
8960
+ /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: SELF_CUSTODY_RISKS }),
8961
+ /* @__PURE__ */ jsxs("label", { className: "pollar-recovery-check", children: [
8962
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: risksAccepted, onChange: (e) => setRisksAccepted(e.target.checked) }),
8963
+ " I understand and accept these risks"
8964
+ ] })
8965
+ ] }),
8966
+ failed && !p.error && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-error", role: "alert", children: "The last attempt did not finish. What did not move is still where it was; try again." }),
8967
+ p.blockedReasons && /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: [
8968
+ "A wallet cannot move while something still uses it: ",
8969
+ p.blockedReasons.join(", "),
8970
+ ". Let it finish and try again."
8971
+ ] }),
8972
+ /* @__PURE__ */ jsx(
8973
+ "button",
8974
+ {
8975
+ type: "button",
8976
+ className: "pollar-recovery-btn pollar-recovery-btn-primary",
8977
+ onClick: () => custody && p.onMove(p.state.choice ? custody : void 0),
8978
+ disabled: !canMove,
8979
+ children: p.busy ? "Moving your wallets..." : "Move my wallets"
8980
+ }
8981
+ ),
8982
+ p.blockedReasons && /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-recovery-btn pollar-recovery-btn-quiet", onClick: p.onLater, disabled: p.busy, children: "Continue for now" }),
8983
+ /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-recovery-btn pollar-recovery-btn-quiet", onClick: p.onLogout, disabled: p.busy, children: "Log out" }),
8984
+ p.error && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-error", role: "alert", children: p.error })
8985
+ ] }),
8986
+ /* @__PURE__ */ jsx(PollarModalFooter, {})
8987
+ ] });
8988
+ }
8989
+ function CustodyMigrationModal({ state, onMoved, onLater }) {
8990
+ const { getClient, styles } = usePollar();
8991
+ const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
8992
+ const [busy, setBusy] = useState(false);
8993
+ const [error, setError] = useState(null);
8994
+ const [blockedReasons, setBlockedReasons] = useState(null);
8995
+ async function move(custody) {
8996
+ setBusy(true);
8997
+ setError(null);
8998
+ setBlockedReasons(null);
8999
+ try {
9000
+ await getClient().cosign.migrateWallets(custody);
9001
+ onMoved();
9002
+ } catch (err) {
9003
+ if (isPollarApiError(err) && err.code === "SDK_CUSTODY_MIGRATION_BLOCKED") {
9004
+ const reasons = err.body.reasons;
9005
+ setBlockedReasons(Array.isArray(reasons) ? reasons.map(String) : []);
9006
+ return;
9007
+ }
9008
+ setError(transactionErrorMessage(isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null));
9009
+ } finally {
9010
+ setBusy(false);
9011
+ }
9012
+ }
9013
+ return /* @__PURE__ */ jsx("div", { className: "pollar-overlay", style: overlayStyle, children: /* @__PURE__ */ jsx(
9014
+ CustodyMigrationModalTemplate,
9015
+ {
9016
+ theme,
9017
+ accentColor,
9018
+ styleOverrides,
9019
+ state,
9020
+ busy,
9021
+ error,
9022
+ blockedReasons,
9023
+ onMove: (custody) => void move(custody),
9024
+ onLater,
9025
+ onLogout: () => void getClient().logout()
9026
+ }
9027
+ ) });
9028
+ }
9029
+ function CloseIcon3() {
9030
+ return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "m3 3 8 8M11 3l-8 8", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
9031
+ }
9032
+ var shortKey = (key) => `${key.slice(0, 6)}...${key.slice(-4)}`;
9033
+ function Button(props) {
9034
+ const running = props.busy === props.id;
9035
+ return /* @__PURE__ */ jsx(
9036
+ "button",
9037
+ {
9038
+ type: "button",
9039
+ className: `pollar-recovery-btn pollar-recovery-btn-${props.kind ?? "quiet"}`,
9040
+ onClick: props.onClick,
9041
+ disabled: props.disabled || props.busy !== null,
9042
+ children: running ? props.busyLabel ?? "Working..." : props.label
9043
+ }
9044
+ );
9045
+ }
9046
+ function CodeForm(props) {
9047
+ const [code, setCode] = useState("");
9048
+ return /* @__PURE__ */ jsxs(
9049
+ "form",
9050
+ {
9051
+ className: "pollar-recovery-row",
9052
+ onSubmit: (e) => {
9053
+ e.preventDefault();
9054
+ if (code.trim()) props.onSubmit(code);
9055
+ },
9056
+ children: [
9057
+ /* @__PURE__ */ jsx(
9058
+ "input",
9059
+ {
9060
+ className: "pollar-recovery-input",
9061
+ value: code,
9062
+ onChange: (e) => setCode(e.target.value),
9063
+ placeholder: "Code from the email",
9064
+ autoComplete: "one-time-code",
9065
+ "aria-label": "Code from the email"
9066
+ }
9067
+ ),
9068
+ /* @__PURE__ */ jsx(
9069
+ Button,
9070
+ {
9071
+ busy: props.busy,
9072
+ id: props.id,
9073
+ label: props.label,
9074
+ kind: "primary",
9075
+ onClick: () => code.trim() && props.onSubmit(code)
9076
+ }
9077
+ )
9078
+ ]
9079
+ }
9080
+ );
9081
+ }
9082
+ function RecoveryModalTemplate(p) {
9083
+ const cssVars = buildModalCssVars(p.theme, p.accentColor, p.styleOverrides);
9084
+ const [exportAccepted, setExportAccepted] = useState(false);
9085
+ const [risksAccepted, setRisksAccepted] = useState(false);
9086
+ const state = p.state;
9087
+ const locked = !!state && state.required && !state.configured;
9088
+ const risksPending = locked && p.selfCustodyRisks && !risksAccepted;
9089
+ const lastMethod = !!state && state.passkeys.length + (state.email.verified ? 1 : 0) + (state.devices.length >= 2 ? 1 : 0) <= 1;
9090
+ let body;
9091
+ if (!state) {
9092
+ body = /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Loading your wallet's recovery methods..." });
9093
+ } else if (!state.required) {
9094
+ body = /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "This wallet doesn't need recovery methods: it isn't secured by your device." });
9095
+ } else if (!p.thisDeviceEnrolled && p.view !== "recover-email") {
9096
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9097
+ /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "status", children: "This device isn't part of your wallet yet. Get back in with one of your recovery methods, or ask a device that already has the wallet to add this one." }),
9098
+ state.passkeys.length > 0 && p.passkeyAvailable && /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "recover-passkey", label: "Use a passkey", kind: "primary", onClick: p.onRecoverWithPasskey }),
9099
+ state.email.verified && /* @__PURE__ */ jsx(
9100
+ Button,
9101
+ {
9102
+ busy: p.busy,
9103
+ id: "recover-send",
9104
+ label: `Email a code to ${state.email.address ?? "your email"}`,
9105
+ onClick: () => p.onView("recover-email")
9106
+ }
9107
+ ),
9108
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "enroll", label: "Ask another device to add this one", onClick: p.onRequestEnrollment })
9109
+ ] });
9110
+ } else if (risksPending && p.view === "overview") {
9111
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9112
+ /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "This is a self-custody wallet: only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign." }),
9113
+ /* @__PURE__ */ jsx(
9114
+ Button,
9115
+ {
9116
+ busy: p.busy,
9117
+ id: "accept-risks",
9118
+ label: "I understand and accept these risks",
9119
+ kind: "primary",
9120
+ onClick: () => setRisksAccepted(true)
9121
+ }
9122
+ )
9123
+ ] });
9124
+ } else if (p.view === "recover-email") {
9125
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9126
+ /* @__PURE__ */ jsxs("p", { className: "pollar-recovery-muted", children: [
9127
+ "We'll email a code to ",
9128
+ state.email.address,
9129
+ ". The code goes straight to the wallet provider, encrypted on this device."
9130
+ ] }),
9131
+ !p.codeSent ? /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "recover-send", label: "Send code", kind: "primary", onClick: p.onSendRecoveryCode }) : /* @__PURE__ */ jsx(CodeForm, { busy: p.busy, id: "recover-code", label: "Recover", onSubmit: p.onRecoverWithCode }),
9132
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9133
+ ] });
9134
+ } else if (p.view === "email") {
9135
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9136
+ /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Confirm an email you control. If you lose this device, a code sent there lets you add a new one. Anyone who controls this inbox could do the same, so use one that is well protected." }),
9137
+ !p.codeSent ? /* @__PURE__ */ jsxs(
9138
+ "form",
9139
+ {
9140
+ className: "pollar-recovery-row",
9141
+ onSubmit: (e) => {
9142
+ e.preventDefault();
9143
+ p.onSendEmailCode();
9144
+ },
9145
+ children: [
9146
+ /* @__PURE__ */ jsx(
9147
+ "input",
9148
+ {
9149
+ className: "pollar-recovery-input",
9150
+ type: "email",
9151
+ value: p.emailDraft,
9152
+ onChange: (e) => p.onEmailDraft(e.target.value),
9153
+ placeholder: "you@example.com",
9154
+ "aria-label": "Email"
9155
+ }
9156
+ ),
9157
+ /* @__PURE__ */ jsx(
9158
+ Button,
9159
+ {
9160
+ busy: p.busy,
9161
+ id: "email-send",
9162
+ label: "Send code",
9163
+ kind: "primary",
9164
+ onClick: p.onSendEmailCode,
9165
+ disabled: !p.emailDraft.trim()
9166
+ }
9167
+ )
9168
+ ]
9169
+ }
9170
+ ) : /* @__PURE__ */ jsx(CodeForm, { busy: p.busy, id: "email-confirm", label: "Confirm", onSubmit: p.onConfirmEmail }),
9171
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9172
+ ] });
9173
+ } else if (p.view === "export") {
9174
+ body = p.phrase ? /* @__PURE__ */ jsxs(Fragment, { children: [
9175
+ /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "Anyone with these words controls every account in this wallet. Write them down offline and never share them." }),
9176
+ /* @__PURE__ */ jsx("pre", { className: "pollar-recovery-phrase", children: p.phrase }),
9177
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "hide", label: "Hide", kind: "primary", onClick: () => p.onView("overview") })
9178
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
9179
+ /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Your recovery phrase lets you take this wallet to another app. It is decrypted only on this device; the app and Pollar never see it." }),
9180
+ /* @__PURE__ */ jsxs("label", { className: "pollar-recovery-check", children: [
9181
+ /* @__PURE__ */ jsx("input", { type: "checkbox", checked: exportAccepted, onChange: (e) => setExportAccepted(e.target.checked) }),
9182
+ " I understand that whoever sees the phrase controls the wallet"
9183
+ ] }),
9184
+ /* @__PURE__ */ jsx(
9185
+ Button,
9186
+ {
9187
+ busy: p.busy,
9188
+ id: "export",
9189
+ label: "Show recovery phrase",
9190
+ kind: "danger",
9191
+ onClick: p.onExport,
9192
+ disabled: !exportAccepted
9193
+ }
9194
+ ),
9195
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9196
+ ] });
9197
+ } else {
9198
+ const enrollments = p.devices?.enrollments ?? [];
9199
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
9200
+ locked ? /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: "Your wallet is locked until you add a way to recover it. Without one, losing this device means losing the funds, and nobody can get them back, not the app and not Pollar. Add a passkey, a second device or your email." }) : /* @__PURE__ */ jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-ok", role: "status", children: "Your wallet can be recovered if you lose this device." }),
9201
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
9202
+ /* @__PURE__ */ jsx("h3", { children: "Passkeys" }),
9203
+ state.passkeys.length === 0 && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "No passkey yet." }),
9204
+ state.passkeys.map((pk) => /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item", children: [
9205
+ /* @__PURE__ */ jsx("span", { children: pk.name }),
9206
+ /* @__PURE__ */ jsx(
9207
+ Button,
9208
+ {
9209
+ busy: p.busy,
9210
+ id: `passkey:${pk.authenticatorId}`,
9211
+ label: "Remove",
9212
+ kind: "danger",
9213
+ onClick: () => p.onRemovePasskey(pk.authenticatorId),
9214
+ disabled: lastMethod
9215
+ }
9216
+ )
9217
+ ] }, pk.authenticatorId)),
9218
+ p.passkeyAvailable ? /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "passkey", label: "Add a passkey", onClick: p.onAddPasskey }) : /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: "Passkeys aren't available on this device." })
9219
+ ] }),
9220
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
9221
+ /* @__PURE__ */ jsx("h3", { children: "Email" }),
9222
+ state.email.verified ? /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item", children: [
9223
+ /* @__PURE__ */ jsx("span", { children: state.email.address }),
9224
+ /* @__PURE__ */ jsx("span", { className: "pollar-recovery-tag", children: "Confirmed" })
9225
+ ] }) : /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "email", label: "Confirm an email", onClick: () => p.onView("email") })
9226
+ ] }),
9227
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
9228
+ /* @__PURE__ */ jsx("h3", { children: "Devices" }),
9229
+ (p.devices?.devices ?? []).map((d) => /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item", children: [
9230
+ /* @__PURE__ */ jsx("span", { children: d.label ?? shortKey(d.publicKey) }),
9231
+ d.publicKey.toLowerCase() === p.thisDeviceKey && /* @__PURE__ */ jsx("span", { className: "pollar-recovery-tag", children: "This device" })
9232
+ ] }, d.id)),
9233
+ enrollments.map((e) => /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-item pollar-recovery-item-pending", children: [
9234
+ /* @__PURE__ */ jsxs("span", { children: [
9235
+ e.label ?? "A device",
9236
+ " wants to join (",
9237
+ shortKey(e.publicKey),
9238
+ ")"
9239
+ ] }),
9240
+ /* @__PURE__ */ jsxs("span", { className: "pollar-recovery-actions", children: [
9241
+ /* @__PURE__ */ jsx(
9242
+ Button,
9243
+ {
9244
+ busy: p.busy,
9245
+ id: `approve:${e.id}`,
9246
+ label: "Approve",
9247
+ kind: "primary",
9248
+ onClick: () => p.onApproveDevice(e.id)
9249
+ }
9250
+ ),
9251
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: `reject:${e.id}`, label: "Reject", onClick: () => p.onRejectDevice(e.id) })
9252
+ ] })
9253
+ ] }, e.id)),
9254
+ /* @__PURE__ */ jsx("p", { className: "pollar-recovery-muted", children: 'To add another device, sign in there and choose "Ask another device to add this one"; it shows up here to approve. Only approve a device you are holding.' })
9255
+ ] }),
9256
+ /* @__PURE__ */ jsxs("section", { className: "pollar-recovery-section", children: [
9257
+ /* @__PURE__ */ jsx("h3", { children: "Recovery phrase" }),
9258
+ /* @__PURE__ */ jsx(Button, { busy: p.busy, id: "export", label: "Export recovery phrase", onClick: () => p.onView("export") })
9259
+ ] })
9260
+ ] });
9261
+ }
9262
+ return /* @__PURE__ */ jsxs(
9263
+ "div",
9264
+ {
9265
+ className: "pollar-modal-card pollar-recovery-modal",
9266
+ "data-theme": p.theme,
9267
+ style: cssVars,
9268
+ onClick: (e) => e.stopPropagation(),
9269
+ children: [
9270
+ /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header", children: [
9271
+ /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: "Wallet recovery" }),
9272
+ /* @__PURE__ */ jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: p.onClose, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx(CloseIcon3, {}) }) })
9273
+ ] }),
9274
+ /* @__PURE__ */ jsxs("div", { className: "pollar-recovery-body", children: [
9275
+ body,
9276
+ p.notice && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-notice", role: "status", children: p.notice }),
9277
+ p.error && /* @__PURE__ */ jsx("p", { className: "pollar-recovery-error", role: "alert", children: p.error })
9278
+ ] }),
9279
+ /* @__PURE__ */ jsx(PollarModalFooter, {})
9280
+ ]
9281
+ }
9282
+ );
9283
+ }
9284
+ function errorText2(err) {
9285
+ return transactionErrorMessage(isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null);
9286
+ }
9287
+ function RecoveryModal({ onClose }) {
9288
+ const { getClient, styles, refreshRecovery, appConfig } = usePollar();
9289
+ const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
9290
+ const recovery = getClient().cosign.recovery;
9291
+ const [state, setState] = useState(null);
9292
+ const [devices, setDevices] = useState(null);
9293
+ const [thisDeviceKey, setThisDeviceKey] = useState(null);
9294
+ const [view, setView] = useState("overview");
9295
+ const [busy, setBusy] = useState(null);
9296
+ const [error, setError] = useState(null);
9297
+ const [notice, setNotice] = useState(null);
9298
+ const [emailDraft, setEmailDraft] = useState("");
9299
+ const [codeSent, setCodeSent] = useState(false);
9300
+ const [phrase, setPhrase] = useState(null);
9301
+ const load = useCallback(async () => {
9302
+ const client = getClient();
9303
+ const [nextState, nextDevices, key] = await Promise.all([
9304
+ client.cosign.recovery.status(),
9305
+ client.cosign.devices(),
9306
+ client.cosign.publicKeyHex()
9307
+ ]);
9308
+ setState(nextState);
9309
+ setDevices(nextDevices);
9310
+ setThisDeviceKey(key.toLowerCase());
9311
+ if (nextState.email.address) setEmailDraft((d) => d || nextState.email.address);
9312
+ refreshRecovery();
9313
+ }, [getClient, refreshRecovery]);
9314
+ useEffect(() => {
9315
+ load().catch((err) => setError(errorText2(err)));
9316
+ }, [load]);
9317
+ const onCloseRef = useRef(onClose);
9318
+ onCloseRef.current = onClose;
9319
+ useEffect(
9320
+ () => getClient().onAuthStateChange((authState) => {
9321
+ if (authState.step === "idle") onCloseRef.current();
9322
+ }),
9323
+ [getClient]
9324
+ );
9325
+ const run = useCallback(
9326
+ async (key, action, after) => {
9327
+ setBusy(key);
9328
+ setError(null);
9329
+ setNotice(null);
9330
+ try {
9331
+ await action();
9332
+ await load();
9333
+ if (after?.notice) setNotice(after.notice);
9334
+ if (after?.view) setView(after.view);
9335
+ } catch (err) {
9336
+ setError(errorText2(err));
9337
+ } finally {
9338
+ setBusy(null);
9339
+ }
9340
+ },
9341
+ [load]
9342
+ );
9343
+ const enrolled = !!thisDeviceKey && !!devices?.devices.some((d) => d.publicKey.toLowerCase() === thisDeviceKey);
9344
+ return /* @__PURE__ */ jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsx(
9345
+ RecoveryModalTemplate,
9346
+ {
9347
+ theme,
9348
+ accentColor,
9349
+ styleOverrides,
9350
+ state,
9351
+ devices,
9352
+ thisDeviceKey,
9353
+ thisDeviceEnrolled: enrolled,
9354
+ passkeyAvailable: recovery.passkeyAvailable,
9355
+ selfCustodyRisks: appConfig.application?.walletCustodyMode === "SELF_CUSTODY" && devices?.custody === "SELF_CUSTODY",
9356
+ view,
9357
+ busy,
9358
+ error,
9359
+ notice,
9360
+ emailDraft,
9361
+ codeSent,
9362
+ phrase,
9363
+ onView: (next) => {
9364
+ setError(null);
9365
+ setNotice(null);
9366
+ setCodeSent(false);
9367
+ if (next !== "export") setPhrase(null);
9368
+ setView(next);
9369
+ },
9370
+ onEmailDraft: setEmailDraft,
9371
+ onAddPasskey: () => void run("passkey", () => recovery.addPasskey(), { notice: "Passkey added." }),
9372
+ onRemovePasskey: (id) => void run(`passkey:${id}`, () => recovery.removePasskey(id)),
9373
+ onSendEmailCode: () => void run("email-send", async () => {
9374
+ await recovery.startEmail(emailDraft.trim());
9375
+ setCodeSent(true);
9376
+ }),
9377
+ onConfirmEmail: (code) => void run("email-confirm", () => recovery.confirmEmail(code.trim()), { notice: "Email confirmed.", view: "overview" }),
9378
+ onApproveDevice: (id) => void run(`approve:${id}`, () => recovery.approveDevice(id), { notice: "Device added." }),
9379
+ onRejectDevice: (id) => void run(`reject:${id}`, () => recovery.rejectDevice(id)),
9380
+ onRequestEnrollment: () => void run("enroll", () => recovery.requestDeviceEnrollment(), {
9381
+ notice: "Request sent. Approve it from a device that already has this wallet within 15 minutes."
9382
+ }),
9383
+ onRecoverWithPasskey: () => void run("recover-passkey", () => recovery.recoverWithPasskey(), {
9384
+ notice: "This device is part of your wallet again."
9385
+ }),
9386
+ onSendRecoveryCode: () => void run("recover-send", async () => {
9387
+ await recovery.startEmailRecovery();
9388
+ setCodeSent(true);
9389
+ }),
9390
+ onRecoverWithCode: (code) => void run("recover-code", () => recovery.recoverWithEmailCode(code.trim()), {
9391
+ notice: "This device is part of your wallet again.",
9392
+ view: "overview"
9393
+ }),
9394
+ onExport: () => void run("export", async () => {
9395
+ setPhrase(await recovery.exportWallet());
9396
+ }),
9397
+ onClose
9398
+ }
9399
+ ) });
9400
+ }
8428
9401
  var OPERATION_LABELS2 = {
8429
9402
  payment: "Send",
8430
9403
  path_payment_strict_send: "Swap",
@@ -8451,7 +9424,7 @@ function detailRows2(confirmation) {
8451
9424
  push("Network", confirmation.network);
8452
9425
  return rows;
8453
9426
  }
8454
- function CloseIcon3() {
9427
+ function CloseIcon4() {
8455
9428
  return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: /* @__PURE__ */ jsx("path", { d: "m3 3 8 8M11 3l-8 8", stroke: "currentColor", strokeWidth: "1.7", strokeLinecap: "round" }) });
8456
9429
  }
8457
9430
  function ConfirmModal({ confirmation }) {
@@ -8475,7 +9448,7 @@ function ConfirmModal({ confirmation }) {
8475
9448
  children: [
8476
9449
  /* @__PURE__ */ jsxs("div", { className: "pollar-modal-header", children: [
8477
9450
  /* @__PURE__ */ jsx("h2", { className: "pollar-modal-title", children: "Confirm transaction" }),
8478
- /* @__PURE__ */ jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: confirmation.reject, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx(CloseIcon3, {}) }) })
9451
+ /* @__PURE__ */ jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsx("button", { type: "button", className: "pollar-modal-close", onClick: confirmation.reject, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsx(CloseIcon4, {}) }) })
8479
9452
  ] }),
8480
9453
  /* @__PURE__ */ jsxs("div", { className: "pollar-cosign-detail", children: [
8481
9454
  /* @__PURE__ */ jsx("p", { className: "pollar-cosign-detail-title", children: labelOf2(confirmation) }),
@@ -8612,6 +9585,25 @@ function formatDate(iso) {
8612
9585
  function truncateMiddle(value, head = 4, tail = 4) {
8613
9586
  return value.length <= head + tail + 1 ? value : `${value.slice(0, head)}\u2026${value.slice(-tail)}`;
8614
9587
  }
9588
+ function formatFee(fee) {
9589
+ if (fee.unit === "lamports") {
9590
+ try {
9591
+ return `${fromBaseUnits(fee.amount, 9)} SOL`;
9592
+ } catch {
9593
+ }
9594
+ }
9595
+ return `${fee.amount} ${fee.unit}`;
9596
+ }
9597
+ function formatAmount4(details) {
9598
+ const { amount, decimals, symbol } = details ?? {};
9599
+ if (typeof amount !== "string" || typeof decimals !== "number") return null;
9600
+ try {
9601
+ const value = fromBaseUnits(amount, decimals);
9602
+ return typeof symbol === "string" ? `${value} ${symbol}` : value;
9603
+ } catch {
9604
+ return null;
9605
+ }
9606
+ }
8615
9607
  var STELLAR_ADDRESS = /\b[GC][A-Z2-7]{55}\b/g;
8616
9608
  function AddressChip({ value, label }) {
8617
9609
  return /* @__PURE__ */ jsxs("span", { className: "pollar-hist-item-issuer", children: [
@@ -8749,15 +9741,17 @@ function TxHistoryModalTemplate({
8749
9741
  ] }),
8750
9742
  /* @__PURE__ */ jsxs("span", { className: "pollar-hist-item-meta", children: [
8751
9743
  /* @__PURE__ */ jsx("span", { children: record.operation }),
9744
+ record.chain !== "STELLAR" && formatAmount4(record.details) && /* @__PURE__ */ jsxs("span", { children: [
9745
+ "\xB7 ",
9746
+ formatAmount4(record.details)
9747
+ ] }),
8752
9748
  typeof record.details?.sponsored === "boolean" && /* @__PURE__ */ jsxs("span", { children: [
8753
9749
  "\xB7 ",
8754
9750
  record.details.sponsored ? "Sponsored" : "Self-paid"
8755
9751
  ] }),
8756
9752
  record.fee && /* @__PURE__ */ jsxs("span", { children: [
8757
9753
  "\xB7 ",
8758
- record.fee.amount,
8759
- " ",
8760
- record.fee.unit
9754
+ formatFee(record.fee)
8761
9755
  ] })
8762
9756
  ] }),
8763
9757
  /* @__PURE__ */ jsxs("div", { className: "pollar-hist-item-footer", children: [
@@ -9068,6 +10062,48 @@ var browserPasskeySigner = async ({ credentialId, challenge }) => {
9068
10062
  signature: response.signature
9069
10063
  };
9070
10064
  };
10065
+ var browserRecoveryPasskey = {
10066
+ async create({ challenge, userName }) {
10067
+ const rpId = window.location.hostname;
10068
+ const response = await startRegistration({
10069
+ optionsJSON: {
10070
+ challenge,
10071
+ rp: { id: rpId, name: rpId },
10072
+ user: { id: randomUserId(), name: userName, displayName: userName },
10073
+ pubKeyCredParams: [
10074
+ { type: "public-key", alg: -7 },
10075
+ { type: "public-key", alg: -257 }
10076
+ ],
10077
+ authenticatorSelection: { residentKey: "preferred", userVerification: "preferred" },
10078
+ attestation: "none",
10079
+ timeout: 6e4
10080
+ }
10081
+ });
10082
+ return {
10083
+ credentialId: response.id,
10084
+ clientDataJson: response.response.clientDataJSON,
10085
+ attestationObject: response.response.attestationObject,
10086
+ transports: response.response.transports ?? []
10087
+ };
10088
+ },
10089
+ async get({ challenge, allowCredentials }) {
10090
+ const rpId = window.location.hostname;
10091
+ const response = await startAuthentication({
10092
+ optionsJSON: {
10093
+ challenge,
10094
+ rpId,
10095
+ allowCredentials: allowCredentials.map((id) => ({ id, type: "public-key" })),
10096
+ userVerification: "preferred"
10097
+ }
10098
+ });
10099
+ return {
10100
+ credentialId: response.id,
10101
+ authenticatorData: response.response.authenticatorData,
10102
+ clientDataJson: response.response.clientDataJSON,
10103
+ signature: response.response.signature
10104
+ };
10105
+ }
10106
+ };
9071
10107
  var DEFAULT_APP_CONFIG = {
9072
10108
  application: { name: "", network: "testnet", chains: [] },
9073
10109
  styles: {}
@@ -9092,7 +10128,11 @@ function PollarProvider({
9092
10128
  }) {
9093
10129
  const [pollarClient] = useState(() => {
9094
10130
  if (isPollarClient(client)) {
9095
- client.setPasskeyDefaults({ passkey: browserPasskeyCeremony, passkeySign: browserPasskeySigner });
10131
+ client.setPasskeyDefaults({
10132
+ passkey: browserPasskeyCeremony,
10133
+ passkeySign: browserPasskeySigner,
10134
+ recoveryPasskey: browserRecoveryPasskey
10135
+ });
9096
10136
  return client;
9097
10137
  }
9098
10138
  const alreadyBuilt = clientByConfig.get(client);
@@ -9100,7 +10140,8 @@ function PollarProvider({
9100
10140
  const built = new PollarClient({
9101
10141
  ...client,
9102
10142
  passkey: client.passkey ?? browserPasskeyCeremony,
9103
- passkeySign: client.passkeySign ?? browserPasskeySigner
10143
+ passkeySign: client.passkeySign ?? browserPasskeySigner,
10144
+ recoveryPasskey: client.recoveryPasskey ?? browserRecoveryPasskey
9104
10145
  });
9105
10146
  clientByConfig.set(client, built);
9106
10147
  return built;
@@ -9255,6 +10296,7 @@ function PollarProvider({
9255
10296
  const [receiveModalOpen, setReceiveModalOpen] = useState(false);
9256
10297
  const [sessionsModalOpen, setSessionsModalOpen] = useState(false);
9257
10298
  const [cosignModalOpen, setCosignModalOpen] = useState(false);
10299
+ const [recoveryModalOpen, setRecoveryModalOpen] = useState(false);
9258
10300
  const cosignPendingCount = useRef(0);
9259
10301
  useEffect(() => {
9260
10302
  if (cosign.pending.length > cosignPendingCount.current) setCosignModalOpen(true);
@@ -9277,6 +10319,7 @@ function PollarProvider({
9277
10319
  setReceiveModalOpen(false);
9278
10320
  setSessionsModalOpen(false);
9279
10321
  setCosignModalOpen(false);
10322
+ setRecoveryModalOpen(false);
9280
10323
  setDistributionRulesModalOpen(false);
9281
10324
  }),
9282
10325
  [pollarClient]
@@ -9285,6 +10328,45 @@ function PollarProvider({
9285
10328
  const walletProvisioning = sessionState?.wallet?.provisioning;
9286
10329
  const hasSession = sessionState !== null;
9287
10330
  const getClient = useCallback(() => pollarClient, [pollarClient]);
10331
+ const [recovery, setRecovery] = useState(null);
10332
+ const [recoveryTick, setRecoveryTick] = useState(0);
10333
+ const refreshRecovery = useCallback(() => setRecoveryTick((t) => t + 1), []);
10334
+ useEffect(() => {
10335
+ if (!hasSession) {
10336
+ setRecovery(null);
10337
+ return;
10338
+ }
10339
+ let cancelled = false;
10340
+ pollarClient.cosign.recovery.status().then((next) => {
10341
+ if (!cancelled) setRecovery(next);
10342
+ }).catch(() => {
10343
+ if (!cancelled) setRecovery(null);
10344
+ });
10345
+ return () => {
10346
+ cancelled = true;
10347
+ };
10348
+ }, [pollarClient, hasSession, walletAddress, recoveryTick]);
10349
+ const recoveryLocked = !!recovery && recovery.required && !recovery.configured;
10350
+ const [custodyMigration, setCustodyMigration] = useState(null);
10351
+ const [custodyMigrationTick, setCustodyMigrationTick] = useState(0);
10352
+ const refreshCustodyMigration = useCallback(() => setCustodyMigrationTick((t) => t + 1), []);
10353
+ const [custodyMigrationLater, setCustodyMigrationLater] = useState(false);
10354
+ useEffect(() => {
10355
+ if (!hasSession) {
10356
+ setCustodyMigration(null);
10357
+ setCustodyMigrationLater(false);
10358
+ return;
10359
+ }
10360
+ let cancelled = false;
10361
+ pollarClient.cosign.custodyMigration().then((next) => {
10362
+ if (!cancelled) setCustodyMigration(next);
10363
+ }).catch(() => {
10364
+ if (!cancelled) setCustodyMigration(null);
10365
+ });
10366
+ return () => {
10367
+ cancelled = true;
10368
+ };
10369
+ }, [pollarClient, hasSession, walletAddress, custodyMigrationTick]);
9288
10370
  const refreshWalletBalance = useCallback(() => pollarClient.refreshBalance(), [pollarClient, walletAddress]);
9289
10371
  const refreshAssets = useCallback(() => pollarClient.refreshAssets(), [pollarClient, walletAddress]);
9290
10372
  const contextValue = useMemo(() => {
@@ -9295,7 +10377,7 @@ function PollarProvider({
9295
10377
  wallets: pollarClient.getWallets(),
9296
10378
  isAuthenticated: hasSession,
9297
10379
  hasWallet: pollarClient.hasWallet(),
9298
- createWallet: () => pollarClient.createWallet(),
10380
+ createWallet: (options) => pollarClient.createWallet(options),
9299
10381
  verified,
9300
10382
  // client
9301
10383
  getClient,
@@ -9364,6 +10446,12 @@ function PollarProvider({
9364
10446
  // cosign
9365
10447
  cosign,
9366
10448
  openCosignModal: () => setCosignModalOpen(true),
10449
+ recovery,
10450
+ recoveryLocked,
10451
+ refreshRecovery,
10452
+ openRecoveryModal: () => setRecoveryModalOpen(true),
10453
+ custodyMigration,
10454
+ refreshCustodyMigration,
9367
10455
  confirmations,
9368
10456
  // distribution
9369
10457
  openDistributionRulesModal: () => setDistributionRulesModalOpen(true),
@@ -9387,6 +10475,11 @@ function PollarProvider({
9387
10475
  }, [
9388
10476
  walletAddress,
9389
10477
  walletProvisioning,
10478
+ recovery,
10479
+ recoveryLocked,
10480
+ refreshRecovery,
10481
+ custodyMigration,
10482
+ refreshCustodyMigration,
9390
10483
  hasSession,
9391
10484
  verified,
9392
10485
  pollarClient,
@@ -9444,6 +10537,23 @@ function PollarProvider({
9444
10537
  receiveModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setReceiveModalOpen(false), children: /* @__PURE__ */ jsx(ReceiveModal, { onClose: () => setReceiveModalOpen(false) }) }),
9445
10538
  sessionsModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setSessionsModalOpen(false), children: /* @__PURE__ */ jsx(SessionsModal, { onClose: () => setSessionsModalOpen(false) }) }),
9446
10539
  cosignModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setCosignModalOpen(false), children: /* @__PURE__ */ jsx(CosignModal, { onClose: () => setCosignModalOpen(false) }) }),
10540
+ custodyMigration?.required && !custodyMigrationLater && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: refreshCustodyMigration, children: /* @__PURE__ */ jsx(
10541
+ CustodyMigrationModal,
10542
+ {
10543
+ state: custodyMigration,
10544
+ onMoved: () => {
10545
+ refreshCustodyMigration();
10546
+ refreshRecovery();
10547
+ setRecoveryModalOpen(true);
10548
+ },
10549
+ onLater: () => {
10550
+ setCustodyMigrationLater(true);
10551
+ refreshCustodyMigration();
10552
+ refreshRecovery();
10553
+ }
10554
+ }
10555
+ ) }),
10556
+ recoveryModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setRecoveryModalOpen(false), children: /* @__PURE__ */ jsx(RecoveryModal, { onClose: () => setRecoveryModalOpen(false) }) }),
9447
10557
  firstConfirmation && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: firstConfirmation.reject, children: /* @__PURE__ */ jsx(ConfirmModal, { confirmation: firstConfirmation }, firstConfirmation.id) }),
9448
10558
  distributionRulesModalOpen && /* @__PURE__ */ jsx(ModalErrorBoundary, { onClose: () => setDistributionRulesModalOpen(false), children: /* @__PURE__ */ jsx(DistributionRulesModal, { onClose: () => setDistributionRulesModalOpen(false) }) })
9449
10559
  ] });
@@ -9494,6 +10604,8 @@ function WalletButtonTemplate({
9494
10604
  isInProgress,
9495
10605
  showCreateAccount,
9496
10606
  notReadyReason,
10607
+ recoveryLocked,
10608
+ onRecovery,
9497
10609
  creatingAccount,
9498
10610
  onToggleOpen,
9499
10611
  onCreateAccount,
@@ -9511,8 +10623,11 @@ function WalletButtonTemplate({
9511
10623
  signedIn,
9512
10624
  creatingWallet,
9513
10625
  createWalletError,
10626
+ custodyChoice,
9514
10627
  onCreateWallet
9515
10628
  }) {
10629
+ const [selfCustodyNotice, setSelfCustodyNotice] = useState(false);
10630
+ const [risksAccepted, setRisksAccepted] = useState(false);
9516
10631
  if (!walletAddress && signedIn) {
9517
10632
  return /* @__PURE__ */ jsxs("div", { className: "pollar-wallet-wrapper", ref: wrapperRef, children: [
9518
10633
  /* @__PURE__ */ jsxs("button", { className: "pollar-wallet-btn", style: { backgroundColor: accentColor }, onClick: onToggleOpen, children: [
@@ -9536,19 +10651,84 @@ function WalletButtonTemplate({
9536
10651
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "status", children: createWalletError }),
9537
10652
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
9538
10653
  ] }),
9539
- onCreateWallet && /* @__PURE__ */ jsxs(Fragment, { children: [
10654
+ onCreateWallet && !custodyChoice && /* @__PURE__ */ jsxs(Fragment, { children: [
9540
10655
  /* @__PURE__ */ jsx(
9541
10656
  "button",
9542
10657
  {
9543
10658
  className: "pollar-wallet-dropdown-item",
9544
10659
  style: { color: itemColor },
9545
- onClick: onCreateWallet,
10660
+ onClick: () => onCreateWallet(),
9546
10661
  disabled: creatingWallet,
9547
10662
  children: creatingWallet ? "Creating wallet\u2026" : "Create wallet"
9548
10663
  }
9549
10664
  ),
9550
10665
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
9551
10666
  ] }),
10667
+ onCreateWallet && custodyChoice && !selfCustodyNotice && /* @__PURE__ */ jsxs(Fragment, { children: [
10668
+ /* @__PURE__ */ jsx(
10669
+ "button",
10670
+ {
10671
+ className: "pollar-wallet-dropdown-item",
10672
+ style: { color: itemColor },
10673
+ onClick: () => onCreateWallet("COSIGNED"),
10674
+ disabled: creatingWallet,
10675
+ children: creatingWallet ? "Creating wallet\u2026" : "Create co-signed wallet"
10676
+ }
10677
+ ),
10678
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: "You and the app approve every signature together. Your wallet can be recovered with help." }),
10679
+ /* @__PURE__ */ jsx(
10680
+ "button",
10681
+ {
10682
+ className: "pollar-wallet-dropdown-item",
10683
+ style: { color: itemColor },
10684
+ onClick: () => setSelfCustodyNotice(true),
10685
+ disabled: creatingWallet,
10686
+ children: "Create self-custody wallet"
10687
+ }
10688
+ ),
10689
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: "Only your device signs. Nobody else can move or recover your funds." }),
10690
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
10691
+ ] }),
10692
+ onCreateWallet && custodyChoice && selfCustodyNotice && /* @__PURE__ */ jsxs(Fragment, { children: [
10693
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "alert", children: "In a self-custody wallet only your device can sign. If you lose this device and every recovery method, nobody can recover the wallet or its funds, not the app and not Pollar. Nobody can undo or freeze a transaction you sign." }),
10694
+ /* @__PURE__ */ jsxs("label", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, children: [
10695
+ /* @__PURE__ */ jsx(
10696
+ "input",
10697
+ {
10698
+ type: "checkbox",
10699
+ checked: risksAccepted,
10700
+ onChange: (e) => setRisksAccepted(e.target.checked),
10701
+ disabled: creatingWallet
10702
+ }
10703
+ ),
10704
+ " ",
10705
+ "I understand and accept these risks"
10706
+ ] }),
10707
+ /* @__PURE__ */ jsx(
10708
+ "button",
10709
+ {
10710
+ className: "pollar-wallet-dropdown-item",
10711
+ style: { color: itemColor },
10712
+ onClick: () => onCreateWallet("SELF_CUSTODY"),
10713
+ disabled: creatingWallet || !risksAccepted,
10714
+ children: creatingWallet ? "Creating wallet\u2026" : "Create self-custody wallet"
10715
+ }
10716
+ ),
10717
+ /* @__PURE__ */ jsx(
10718
+ "button",
10719
+ {
10720
+ className: "pollar-wallet-dropdown-item",
10721
+ style: { color: itemColor },
10722
+ onClick: () => {
10723
+ setSelfCustodyNotice(false);
10724
+ setRisksAccepted(false);
10725
+ },
10726
+ disabled: creatingWallet,
10727
+ children: "Back"
10728
+ }
10729
+ ),
10730
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
10731
+ ] }),
9552
10732
  /* @__PURE__ */ jsx("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onSessions, children: "Active sessions" }),
9553
10733
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" }),
9554
10734
  /* @__PURE__ */ jsx("button", { className: "pollar-wallet-dropdown-item pollar-danger", onClick: onLogout, children: "Log out" })
@@ -9604,6 +10784,10 @@ function WalletButtonTemplate({
9604
10784
  }
9605
10785
  ),
9606
10786
  open && /* @__PURE__ */ jsxs("div", { className: "pollar-wallet-dropdown", style: { backgroundColor: dropdownBg, borderColor: dropdownBorder }, children: [
10787
+ recoveryLocked && /* @__PURE__ */ jsxs(Fragment, { children: [
10788
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice pollar-danger", role: "alert", children: "Locked: add a way to recover this wallet before sending or receiving. If you lose this device without one, the funds are lost." }),
10789
+ /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
10790
+ ] }),
9607
10791
  notReadyReason && /* @__PURE__ */ jsxs(Fragment, { children: [
9608
10792
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "status", children: notReadyReason }),
9609
10793
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" })
@@ -9836,6 +11020,31 @@ function WalletButtonTemplate({
9836
11020
  ),
9837
11021
  "Active sessions"
9838
11022
  ] }),
11023
+ onRecovery && /* @__PURE__ */ jsxs(
11024
+ "button",
11025
+ {
11026
+ className: `pollar-wallet-dropdown-item${recoveryLocked ? " pollar-danger" : ""}`,
11027
+ style: recoveryLocked ? void 0 : { color: itemColor },
11028
+ onClick: onRecovery,
11029
+ children: [
11030
+ /* @__PURE__ */ jsx(
11031
+ "svg",
11032
+ {
11033
+ width: "14",
11034
+ height: "14",
11035
+ viewBox: "0 0 24 24",
11036
+ fill: "none",
11037
+ stroke: "currentColor",
11038
+ strokeWidth: "2",
11039
+ strokeLinecap: "round",
11040
+ strokeLinejoin: "round",
11041
+ children: /* @__PURE__ */ jsx("path", { d: "M12 2 4 5v6c0 5 3.4 9.3 8 11 4.6-1.7 8-6 8-11V5l-8-3z" })
11042
+ }
11043
+ ),
11044
+ recoveryLocked ? "Set up wallet recovery" : "Wallet recovery"
11045
+ ]
11046
+ }
11047
+ ),
9839
11048
  /* @__PURE__ */ jsx("div", { className: "pollar-wallet-dropdown-divider" }),
9840
11049
  /* @__PURE__ */ jsxs("button", { className: "pollar-wallet-dropdown-item pollar-danger", onClick: onLogout, children: [
9841
11050
  /* @__PURE__ */ jsxs(
@@ -9878,7 +11087,10 @@ function WalletButton() {
9878
11087
  tx: transaction,
9879
11088
  isAuthenticated,
9880
11089
  createWallet,
9881
- appConfig
11090
+ appConfig,
11091
+ recovery,
11092
+ recoveryLocked,
11093
+ openRecoveryModal
9882
11094
  } = usePollar();
9883
11095
  const { primaryChain, primaryAddress } = useChains();
9884
11096
  const walletAddress = primaryAddress || (wallet?.address ?? "");
@@ -9941,12 +11153,12 @@ function WalletButton() {
9941
11153
  setCreating(false);
9942
11154
  }
9943
11155
  }
9944
- async function handleCreateWallet() {
11156
+ async function handleCreateWallet(custody) {
9945
11157
  if (creatingWallet) return;
9946
11158
  setCreatingWallet(true);
9947
11159
  setCreateWalletError(null);
9948
11160
  try {
9949
- await createWallet();
11161
+ await createWallet(custody ? { custody } : void 0);
9950
11162
  setOpen(false);
9951
11163
  } catch (err) {
9952
11164
  setCreateWalletError(
@@ -9976,6 +11188,10 @@ function WalletButton() {
9976
11188
  setOpen(false);
9977
11189
  openSessionsModal();
9978
11190
  }
11191
+ function handleRecovery() {
11192
+ setOpen(false);
11193
+ openRecoveryModal();
11194
+ }
9979
11195
  function handleKyc() {
9980
11196
  setOpen(false);
9981
11197
  openKycModal();
@@ -10004,6 +11220,7 @@ function WalletButton() {
10004
11220
  signedIn: isAuthenticated,
10005
11221
  creatingWallet,
10006
11222
  createWalletError,
11223
+ custodyChoice: appConfig.application?.walletCustodyMode === "USER_CHOICE",
10007
11224
  ...appConfig.application?.walletProvisioning === "NONE" ? {} : { onCreateWallet: handleCreateWallet },
10008
11225
  notReadyReason,
10009
11226
  creatingAccount: creating,
@@ -10015,6 +11232,8 @@ function WalletButton() {
10015
11232
  onSend: handleSend,
10016
11233
  onReceive: handleReceive,
10017
11234
  onSessions: handleSessions,
11235
+ recoveryLocked,
11236
+ ...recovery?.required ? { onRecovery: handleRecovery } : {},
10018
11237
  onKyc: handleKyc,
10019
11238
  onRamp: handleRamp,
10020
11239
  onDistributionRules: handleDistributionRules,
@@ -10024,6 +11243,6 @@ function WalletButton() {
10024
11243
  );
10025
11244
  }
10026
11245
 
10027
- export { CardModal, ChainSelect, CosignModal, CosignModalTemplate, DistributionRulesModal, DistributionRulesModalTemplate, EarnModal, EnabledAssetsModal, EnabledAssetsModalTemplate, KycModal, KycModalTemplate, KycStatus, LoginModalTemplate, PollarProvider, ProviderRegistrationModal, RampWidget, RampWidgetTemplate, ReceiveModal, ReceiveModalTemplate, RegistryCheckModal, RequirementFormModal, RouteDisplay, SendModal, SendModalTemplate, SessionsModal, SessionsModalTemplate, SwapModal, SwapModalTemplate, TransactionModalTemplate, TxHistoryModalTemplate, TxStatusView, WalletBalanceModal, WalletBalanceModalTemplate, WalletButton, WalletButtonTemplate, addressForChain, browserPasskeyCeremony, browserPasskeySigner, buildModalCssVars, chainsOf, createPollarAdapterHook, modalChrome, readableTextOn, resolveChain, useChains, useCosign, usePollar, walletNotReadyReason };
11246
+ export { CardModal, ChainSelect, CosignModal, CosignModalTemplate, CustodyMigrationModal, CustodyMigrationModalTemplate, DistributionRulesModal, DistributionRulesModalTemplate, EarnModal, EnabledAssetsModal, EnabledAssetsModalTemplate, KycModal, KycModalTemplate, KycStatus, LoginModalTemplate, PollarProvider, ProviderRegistrationModal, RampWidget, RampWidgetTemplate, RampWorkflow, ReceiveModal, ReceiveModalTemplate, RecoveryModal, RecoveryModalTemplate, RegistryCheckModal, RequirementFormModal, RouteDisplay, SendModal, SendModalTemplate, SessionsModal, SessionsModalTemplate, SwapModal, SwapModalTemplate, TransactionModalTemplate, TxHistoryModalTemplate, TxStatusView, WalletBalanceModal, WalletBalanceModalTemplate, WalletButton, WalletButtonTemplate, addressForChain, browserPasskeyCeremony, browserPasskeySigner, browserRecoveryPasskey, buildModalCssVars, chainsOf, createPollarAdapterHook, modalChrome, readableTextOn, resolveChain, useChains, useCosign, usePollar, walletNotReadyReason };
10028
11247
  //# sourceMappingURL=index.mjs.map
10029
11248
  //# sourceMappingURL=index.mjs.map