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