@pollar/react 0.11.5 → 0.12.0-rc.5

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
@@ -1001,7 +1001,6 @@ var require_QRCode = __commonJS({
1001
1001
  });
1002
1002
 
1003
1003
  // src/constants.ts
1004
- var LOGO_POLLAR = "https://pollar.xyz/assets/logo_pollar.png";
1005
1004
  var LOGO_POLLAR_WORDMARK_ON_LIGHT = "https://www.pollar.xyz/brandkit/pollar-logo-horizontal-on-light.png";
1006
1005
  var LOGO_POLLAR_WORDMARK_ON_DARK = "https://www.pollar.xyz/brandkit/pollar-logo-horizontal-on-dark.png";
1007
1006
  var LOGO_POLLAR_MARK_ON_LIGHT = "https://www.pollar.xyz/brandkit/pollar-mark-on-light.png";
@@ -1031,6 +1030,13 @@ var ModalErrorBoundary = class extends react.Component {
1031
1030
  return this.props.children;
1032
1031
  }
1033
1032
  };
1033
+ function ModalLogo({ logoUrl }) {
1034
+ if (logoUrl) return /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl, alt: "Logo", className: "pollar-logo" });
1035
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1036
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: LOGO_POLLAR_MARK_ON_LIGHT, alt: "Pollar", className: "pollar-logo pollar-logo-on-light" }),
1037
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: LOGO_POLLAR_MARK_ON_DARK, alt: "Pollar", className: "pollar-logo pollar-logo-on-dark" })
1038
+ ] });
1039
+ }
1034
1040
  var PollarModalFooter = () => {
1035
1041
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-footer", children: [
1036
1042
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-footer-protected", children: "Protected by" }),
@@ -1038,7 +1044,7 @@ var PollarModalFooter = () => {
1038
1044
  /* @__PURE__ */ jsxRuntime.jsx("img", { src: LOGO_POLLAR_WORDMARK_ON_DARK, alt: "Pollar", className: "pollar-footer-logo pollar-logo-on-dark" }),
1039
1045
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-footer-version", children: [
1040
1046
  "v",
1041
- "0.11.5"
1047
+ "0.12.0-rc.5"
1042
1048
  ] })
1043
1049
  ] });
1044
1050
  };
@@ -1220,42 +1226,75 @@ function readableTextOn(background) {
1220
1226
  const [r, g, b] = rgb;
1221
1227
  return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255 > 0.6 ? "#111827" : "#ffffff";
1222
1228
  }
1229
+ var PALETTES = {
1230
+ light: {
1231
+ bg: "#ffffff",
1232
+ border: "#e5e7eb",
1233
+ text: "#111827",
1234
+ muted: "#6b7280",
1235
+ "input-bg": "#f9fafb",
1236
+ "error-bg": "#fef2f2",
1237
+ "error-border": "#fecaca",
1238
+ "error-text": "#dc2626",
1239
+ "success-text": "#16a34a"
1240
+ },
1241
+ dark: {
1242
+ bg: "#1a1a1a",
1243
+ border: "#374151",
1244
+ text: "#ffffff",
1245
+ muted: "#9ca3af",
1246
+ "input-bg": "#374151",
1247
+ "error-bg": "#2a1515",
1248
+ "error-border": "#7f1d1d",
1249
+ "error-text": "#f87171",
1250
+ "success-text": "#4ade80"
1251
+ }
1252
+ };
1253
+ function mix(fg, percent, bg) {
1254
+ return `color-mix(in srgb, ${fg} ${percent}%, ${bg})`;
1255
+ }
1223
1256
  function buildModalCssVars(theme, accentColor, overrides = {}, variant = "compact") {
1224
1257
  const isDark = theme === "dark";
1225
- const textColor = color(overrides.textColor, isDark ? "#ffffff" : "#111827");
1258
+ const palette = isDark ? PALETTES.dark : PALETTES.light;
1259
+ const bg = color(overrides.backgroundColor, "");
1260
+ const text = color(overrides.textColor, "");
1261
+ const pinned = isDark || bg !== "" || text !== "";
1226
1262
  const secondaryBg = color(overrides.buttonColor, "");
1227
- return {
1228
- "--pollar-accent": accentColor,
1229
- "--pollar-bg": color(overrides.backgroundColor, isDark ? "#1a1a1a" : "#ffffff"),
1230
- "--pollar-border": isDark ? "#374151" : "#e5e7eb",
1231
- "--pollar-text": textColor,
1232
- "--pollar-muted": isDark ? "#9ca3af" : "#6b7280",
1263
+ const vars = {
1264
+ "--pollar-brand-accent": accentColor,
1233
1265
  // The primary button is the accent's, straight up; its label follows the
1234
1266
  // accent's luminance so a pale accent doesn't render white-on-white.
1235
- "--pollar-btn-primary-fg": readableTextOn(accentColor),
1236
- ...secondaryBg ? {
1237
- "--pollar-btn-secondary-bg": secondaryBg,
1238
- "--pollar-btn-secondary-fg": readableTextOn(secondaryBg)
1239
- } : {},
1240
- "--pollar-input-bg": isDark ? "#374151" : "#f9fafb",
1241
- "--pollar-error-bg": isDark ? "#2a1515" : "#fef2f2",
1242
- "--pollar-error-border": isDark ? "#7f1d1d" : "#fecaca",
1243
- "--pollar-error-text": isDark ? "#f87171" : "#dc2626",
1244
- "--pollar-success-text": isDark ? "#4ade80" : "#16a34a",
1245
- "--pollar-logo-on-light-display": isDark ? "none" : "block",
1246
- "--pollar-logo-on-dark-display": isDark ? "block" : "none",
1247
- "--pollar-modal-border-radius": px(overrides.modalBorderRadius, DEFAULT_MODAL_BORDER_RADIUS),
1248
- "--pollar-buttons-border-radius": px(overrides.buttonBorderRadius, DEFAULT_BUTTON_BORDER_RADIUS),
1249
- "--pollar-buttons-height": "44px",
1250
- "--pollar-input-height": "44px",
1251
- "--pollar-input-border-radius": "0.5rem",
1252
- "--pollar-card-border-radius": "10px",
1253
- ...variant === "hero" ? {
1254
- "--pollar-modal-padding": "2rem",
1255
- "--pollar-modal-heading-size": "1.375rem",
1256
- "--pollar-modal-subtitle-size": "0.9rem"
1257
- } : {}
1267
+ "--pollar-brand-btn-primary-fg": readableTextOn(accentColor),
1268
+ "--pollar-brand-modal-border-radius": px(overrides.modalBorderRadius, DEFAULT_MODAL_BORDER_RADIUS),
1269
+ "--pollar-brand-buttons-border-radius": px(overrides.buttonBorderRadius, DEFAULT_BUTTON_BORDER_RADIUS)
1258
1270
  };
1271
+ if (secondaryBg) {
1272
+ vars["--pollar-brand-btn-secondary-bg"] = secondaryBg;
1273
+ vars["--pollar-brand-btn-secondary-fg"] = readableTextOn(secondaryBg);
1274
+ }
1275
+ if (pinned) {
1276
+ for (const [name, value] of Object.entries(palette)) vars[`--pollar-brand-${name}`] = value;
1277
+ vars["--pollar-brand-logo-on-light-display"] = isDark ? "none" : "block";
1278
+ vars["--pollar-brand-logo-on-dark-display"] = isDark ? "block" : "none";
1279
+ }
1280
+ if (bg || text) {
1281
+ const surface = bg || palette.bg;
1282
+ const ink = text || palette.text;
1283
+ vars["--pollar-brand-bg"] = surface;
1284
+ vars["--pollar-brand-text"] = ink;
1285
+ vars["--pollar-brand-border"] = mix(ink, 14, surface);
1286
+ vars["--pollar-brand-muted"] = mix(ink, 62, surface);
1287
+ vars["--pollar-brand-input-bg"] = mix(ink, 4, surface);
1288
+ const darkSurface = readableTextOn(surface) === "#ffffff";
1289
+ vars["--pollar-brand-logo-on-light-display"] = darkSurface ? "none" : "block";
1290
+ vars["--pollar-brand-logo-on-dark-display"] = darkSurface ? "block" : "none";
1291
+ }
1292
+ if (variant === "hero") {
1293
+ vars["--pollar-brand-modal-padding"] = "2rem";
1294
+ vars["--pollar-brand-modal-heading-size"] = "1.375rem";
1295
+ vars["--pollar-brand-modal-subtitle-size"] = "0.9rem";
1296
+ }
1297
+ return vars;
1259
1298
  }
1260
1299
  function modalChrome(styles) {
1261
1300
  const zIndex = styles.modalZIndex;
@@ -1270,7 +1309,7 @@ function modalChrome(styles) {
1270
1309
  buttonBorderRadius: styles.buttonBorderRadius
1271
1310
  },
1272
1311
  overlayStyle: {
1273
- "--pollar-overlay-z-index": typeof zIndex === "number" && Number.isFinite(zIndex) ? zIndex : DEFAULT_OVERLAY_Z_INDEX
1312
+ "--pollar-brand-overlay-z-index": typeof zIndex === "number" && Number.isFinite(zIndex) ? zIndex : DEFAULT_OVERLAY_Z_INDEX
1274
1313
  }
1275
1314
  };
1276
1315
  }
@@ -1915,7 +1954,7 @@ function KycStatus({ status, label, className }) {
1915
1954
 
1916
1955
  // src/components/kyc-modal/kyc-messages.ts
1917
1956
  function kycErrorMessage(error, phase) {
1918
- const code = error?.code;
1957
+ const code = typeof error === "object" && error !== null && "code" in error ? error.code : void 0;
1919
1958
  switch (code) {
1920
1959
  case "SDK_KYC_NOT_CONFIGURED":
1921
1960
  return "Identity verification is not available for this app yet.";
@@ -1955,6 +1994,8 @@ function KycModalTemplate({
1955
1994
  reviewReason,
1956
1995
  processing = false,
1957
1996
  isLoading,
1997
+ startingProviderId = null,
1998
+ directOpen = false,
1958
1999
  error,
1959
2000
  onSelectProvider,
1960
2001
  onDoneVerifying,
@@ -1963,6 +2004,7 @@ function KycModalTemplate({
1963
2004
  onClose
1964
2005
  }) {
1965
2006
  const cssVars = buildModalCssVars(theme, accentColor, styleOverrides, "hero");
2007
+ const busy = isLoading || startingProviderId !== null;
1966
2008
  return /* @__PURE__ */ jsxRuntime.jsxs(
1967
2009
  "div",
1968
2010
  {
@@ -1990,7 +2032,7 @@ function KycModalTemplate({
1990
2032
  type: "button",
1991
2033
  className: "pollar-modal-close",
1992
2034
  onClick: onRefresh,
1993
- disabled: isLoading,
2035
+ disabled: busy,
1994
2036
  "aria-label": "Refresh",
1995
2037
  title: "Refresh providers",
1996
2038
  children: /* @__PURE__ */ jsxRuntime.jsx(
@@ -2020,7 +2062,14 @@ function KycModalTemplate({
2020
2062
  ] })
2021
2063
  ] }),
2022
2064
  error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-kyc-error", role: "alert", children: error }),
2023
- step === "select_provider" && (isLoading && providers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-loading-block", children: [
2065
+ step === "select_provider" && (startingProviderId !== null && directOpen ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-loading-block", "aria-live": "polite", children: [
2066
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-spinner" }),
2067
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
2068
+ "Opening ",
2069
+ selectedProvider?.name ?? "verification",
2070
+ "\u2026"
2071
+ ] })
2072
+ ] }) : isLoading && providers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-loading-block", children: [
2024
2073
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-spinner" }),
2025
2074
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Loading providers\u2026" })
2026
2075
  ] }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-kyc-providers", children: [
@@ -2030,11 +2079,15 @@ function KycModalTemplate({
2030
2079
  {
2031
2080
  type: "button",
2032
2081
  className: "pollar-kyc-provider-btn",
2033
- disabled: isLoading,
2082
+ disabled: busy,
2083
+ "aria-busy": startingProviderId === p.id,
2034
2084
  onClick: () => onSelectProvider(p),
2035
2085
  children: [
2036
2086
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-kyc-provider-name", children: p.name }),
2037
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-kyc-provider-flow", children: isLoading && selectedProvider?.id === p.id ? "Opening\u2026" : p.flow === "redirect" ? "Continue in a new tab" : "Continue here" })
2087
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-kyc-provider-flow", children: startingProviderId === p.id ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2088
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
2089
+ "Opening\u2026"
2090
+ ] }) : p.flow === "redirect" ? "Continue in a new tab" : "Continue here" })
2038
2091
  ]
2039
2092
  },
2040
2093
  p.id
@@ -2085,7 +2138,7 @@ function KycModalTemplate({
2085
2138
  /* @__PURE__ */ jsxRuntime.jsx(KycStatus, { status: kycStatus, ...processing ? { label: "Approved" } : {} }),
2086
2139
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-kyc-result-text", children: kycStatus === "approved" ? "Your identity has been verified successfully." : kycStatus === "rejected" ? "Your verification was not approved. Contact support for the next steps." : kycStatus === "expired" ? "Your verification expired. Start again to verify your identity." : processing ? kycProcessingMessage() : kycReviewMessage(reviewReason) }),
2087
2140
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-actions", children: [
2088
- kycStatus === "expired" && onStartAgain && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onStartAgain, disabled: isLoading, children: "Start again" }),
2141
+ kycStatus === "expired" && onStartAgain && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onStartAgain, disabled: busy, children: "Start again" }),
2089
2142
  kycStatus !== "approved" && kycStatus !== "rejected" && kycStatus !== "expired" && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onDoneVerifying, children: "Check again" }),
2090
2143
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", onClick: onClose, children: "Close" })
2091
2144
  ] })
@@ -2118,6 +2171,8 @@ function KycModal({
2118
2171
  const [reviewReason, setReviewReason] = react.useState(null);
2119
2172
  const [processing, setProcessing] = react.useState(false);
2120
2173
  const [isLoading, setIsLoading] = react.useState(false);
2174
+ const [startingProviderId, setStartingProviderId] = react.useState(null);
2175
+ const [directOpen, setDirectOpen] = react.useState(false);
2121
2176
  const [error, setError] = react.useState(null);
2122
2177
  const idempotencyKeys = react.useRef({});
2123
2178
  const autoOpened = react.useRef(false);
@@ -2145,7 +2200,7 @@ function KycModal({
2145
2200
  starting.current = true;
2146
2201
  setSelectedProvider(provider);
2147
2202
  setError(null);
2148
- setIsLoading(true);
2203
+ setStartingProviderId(provider.id);
2149
2204
  const key = (_a = idempotencyKeys.current)[_b = provider.id] ?? (_a[_b] = newIdempotencyKey());
2150
2205
  try {
2151
2206
  const result = await client.resolveKyc(
@@ -2160,7 +2215,13 @@ function KycModal({
2160
2215
  finish("approved");
2161
2216
  return;
2162
2217
  }
2163
- setSession(result);
2218
+ const { sessionId } = result;
2219
+ if (sessionId === void 0) throw new Error("The KYC provider answered without a session.");
2220
+ setSession({
2221
+ sessionId,
2222
+ ...result.kycUrl !== void 0 ? { kycUrl: result.kycUrl } : {},
2223
+ ...result.fields !== void 0 ? { fields: result.fields } : {}
2224
+ });
2164
2225
  setStep("verifying");
2165
2226
  } catch (e) {
2166
2227
  const code = core.isPollarApiError(e) ? e.code : void 0;
@@ -2177,7 +2238,7 @@ function KycModal({
2177
2238
  setStep("select_provider");
2178
2239
  } finally {
2179
2240
  starting.current = false;
2180
- setIsLoading(false);
2241
+ setStartingProviderId(null);
2181
2242
  }
2182
2243
  }
2183
2244
  const loadProviders = react.useCallback(() => {
@@ -2196,6 +2257,7 @@ function KycModal({
2196
2257
  void loadProviders().then((list) => {
2197
2258
  if (!providerId || autoOpened.current) return;
2198
2259
  autoOpened.current = true;
2260
+ setDirectOpen(true);
2199
2261
  const target = list.find((p) => p.id === providerId);
2200
2262
  if (target) void handleSelectProvider(target);
2201
2263
  else
@@ -2245,6 +2307,8 @@ function KycModal({
2245
2307
  reviewReason,
2246
2308
  processing,
2247
2309
  isLoading,
2310
+ startingProviderId,
2311
+ directOpen,
2248
2312
  error,
2249
2313
  onSelectProvider: handleSelectProvider,
2250
2314
  onDoneVerifying: handleDoneVerifying,
@@ -2347,10 +2411,14 @@ function fieldErrorMessage(code, language) {
2347
2411
  }
2348
2412
  function fieldErrorsOf(error) {
2349
2413
  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;
2414
+ if (!("code" in error) || error.code !== "KYC_FORM_INVALID_ANSWERS") return null;
2415
+ const body = "body" in error ? error.body : void 0;
2416
+ const list = typeof body === "object" && body !== null && "errors" in body ? body.errors : void 0;
2417
+ if (!Array.isArray(list)) return null;
2418
+ const items = list;
2352
2419
  const errors = {};
2353
- for (const item of body.errors) {
2420
+ for (const item of items) {
2421
+ if (typeof item !== "object" || item === null || !("key" in item) || !("code" in item)) continue;
2354
2422
  if (typeof item.key === "string" && typeof item.code === "string") errors[item.key] = item.code;
2355
2423
  }
2356
2424
  return Object.keys(errors).length ? errors : null;
@@ -2930,7 +2998,7 @@ function LoginModalTemplate({
2930
2998
  }
2931
2999
  ) }),
2932
3000
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-header", children: [
2933
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-logo-wrap", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl ?? LOGO_POLLAR, alt: "Logo", className: "pollar-logo" }) }),
3001
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-logo-wrap", children: /* @__PURE__ */ jsxRuntime.jsx(ModalLogo, { logoUrl }) }),
2934
3002
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-title", children: appName }),
2935
3003
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-subtitle", children: "Log in or sign up" })
2936
3004
  ] }),
@@ -3096,7 +3164,7 @@ function LoginModalStatus({
3096
3164
  }
3097
3165
  ) }),
3098
3166
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-header", children: [
3099
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-logo-wrap", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl ?? LOGO_POLLAR, alt: "Logo", className: "pollar-logo" }) }),
3167
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-logo-wrap", children: /* @__PURE__ */ jsxRuntime.jsx(ModalLogo, { logoUrl }) }),
3100
3168
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-title", children: appName }),
3101
3169
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-subtitle", children: "Log in or sign up" })
3102
3170
  ] }),
@@ -3198,7 +3266,7 @@ function PrivyLoginSubmodal({
3198
3266
  }
3199
3267
  ) }),
3200
3268
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-header", children: [
3201
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-logo-wrap", children: /* @__PURE__ */ jsxRuntime.jsx("img", { src: logoUrl ?? LOGO_POLLAR, alt: "Logo", className: "pollar-logo" }) }),
3269
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-logo-wrap", children: /* @__PURE__ */ jsxRuntime.jsx(ModalLogo, { logoUrl }) }),
3202
3270
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-title", children: appName }),
3203
3271
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-subtitle", children: adapter.meta.label })
3204
3272
  ] }),
@@ -3399,69 +3467,45 @@ function LoginModal({ onClose }) {
3399
3467
  }
3400
3468
  ) });
3401
3469
  }
3402
-
3403
- // src/components/ramp-widget/ramp-kyc.ts
3404
- var nonEmpty = (value) => typeof value === "string" && !!value.trim();
3405
- function requiredRampKyc(error) {
3406
- 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;
3410
- if (!nonEmpty(rampProviderId) || !nonEmpty(corridorId)) return null;
3411
- if ((requirementType === "FORM" || requirementType === "REGISTRY_CHECK" || requirementType === "PROVIDER_REGISTRATION") && nonEmpty(optionId)) {
3412
- return { rampProviderId, corridorId, type: requirementType, optionId };
3413
- }
3414
- const kyc = nonEmpty(optionId) && requirementType === "KYC" ? optionId : kycProviderId;
3415
- return nonEmpty(kyc) ? { rampProviderId, corridorId, type: "KYC", optionId: kyc } : null;
3416
- }
3417
- function pendingFromQuote(requirement) {
3418
- return {
3419
- rampProviderId: requirement.rampProviderId,
3420
- corridorId: requirement.corridorId,
3421
- type: requirement.type,
3422
- optionId: requirement.optionId,
3423
- progress: { position: requirement.completed + 1, total: requirement.total }
3424
- };
3425
- }
3426
- function lockedRouteCopy(requirement) {
3427
- if (requirement.type === "FORM") return { message: "Answer a few questions to see this route", action: "Continue" };
3428
- if (requirement.type === "PROVIDER_REGISTRATION") {
3429
- return { message: "Register with the provider to see this route", action: "Continue" };
3430
- }
3431
- if (requirement.type === "REGISTRY_CHECK") {
3432
- return requirement.status === "pending" ? { message: "Your details are under review", action: null } : { message: "Confirm your details to see this route", action: "Confirm" };
3433
- }
3434
- switch (requirement.status) {
3435
- case "expired":
3436
- return { message: "Your verification expired", action: "Verify again" };
3437
- case "pending":
3438
- if (!requirement.reviewReason) return { message: "Verification in progress", action: "Continue" };
3439
- return {
3440
- message: requirement.reviewReason === "DUPLICATE_DOCUMENT" ? kycReviewMessage(requirement.reviewReason) : "Your verification is under review",
3441
- action: null
3442
- };
3443
- case "rejected":
3444
- return { message: "Verification was not approved", action: null };
3445
- default:
3446
- return { message: "Verify your identity to see this route", action: "Verify" };
3447
- }
3470
+ function findWalletBalance(balances, asset) {
3471
+ const { issuer } = asset;
3472
+ return balances.find((b) => {
3473
+ if (resolveChain(b.chain) !== asset.chain) return false;
3474
+ if (issuer === null) return b.type === "native" || !b.issuer && b.code === asset.code;
3475
+ if (b.code !== asset.code) return false;
3476
+ return issuer === void 0 || !b.issuer || b.issuer.toLowerCase() === issuer.toLowerCase();
3477
+ });
3448
3478
  }
3449
- var RAIL_LABELS = {
3450
- SPEI: "SPEI (Mexico)",
3451
- PIX: "PIX (Brazil)",
3452
- PSE: "PSE (Colombia)",
3453
- ACH: "ACH (US)"
3454
- };
3455
- function RouteDisplay({ quote, busy = false, disabled = false, onSelect }) {
3479
+ function useWalletAvailable(asset) {
3480
+ const { walletBalance, refreshWalletBalance } = usePollar();
3481
+ const wanted = !!asset;
3482
+ react.useEffect(() => {
3483
+ if (wanted && walletBalance.step === "idle") void refreshWalletBalance();
3484
+ }, [wanted, walletBalance.step, refreshWalletBalance]);
3485
+ if (!asset || walletBalance.step !== "loaded") return void 0;
3486
+ const record = findWalletBalance(walletBalance.data.balances, asset);
3487
+ return record ? record.available : "0";
3488
+ }
3489
+ var CUSTODY_LABEL = { COSIGNED: "co-signed", SELF_CUSTODY: "self-custody" };
3490
+ var assetOf = (node) => node.kind === "fiat" ? node.currency : node.code;
3491
+ function etaText(seconds) {
3492
+ if (seconds === null) return null;
3493
+ if (seconds < 90) return `~${Math.max(1, Math.round(seconds))} s`;
3494
+ if (seconds < 5400) return `~${Math.round(seconds / 60)} min`;
3495
+ return `~${Math.round(seconds / 3600)} h`;
3496
+ }
3497
+ function RouteDisplay({ quote, best = false, busy = false, disabled = false, onSelect }) {
3456
3498
  const inert = busy || disabled;
3457
3499
  const select = () => {
3458
3500
  if (!inert) onSelect(quote);
3459
3501
  };
3502
+ const first = quote.legs[0];
3503
+ const last = quote.legs[quote.legs.length - 1];
3460
3504
  return /* @__PURE__ */ jsxRuntime.jsxs(
3461
3505
  "div",
3462
3506
  {
3463
3507
  className: "pollar-ramp-route-card",
3464
- "data-recommended": quote.recommended,
3508
+ "data-recommended": best,
3465
3509
  "data-busy": busy || void 0,
3466
3510
  "data-disabled": disabled || void 0,
3467
3511
  role: "button",
@@ -3472,54 +3516,152 @@ function RouteDisplay({ quote, busy = false, disabled = false, onSelect }) {
3472
3516
  onKeyDown: (e) => e.key === "Enter" && select(),
3473
3517
  children: [
3474
3518
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-route-left", children: [
3475
- /* @__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}` })
3519
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-provider", children: first && last ? `${quote.amountIn} ${assetOf(first.from)} \u2192 ~${quote.amountOut} ${assetOf(last.to)}` : `~${quote.amountOut}` }),
3520
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-meta", children: busy ? "Starting\u2026" : [quote.legs.map((leg) => core.rampLegTitle(leg)).join(" \xB7 "), etaText(quote.etaSeconds)].filter(Boolean).join(" \xB7 ") })
3477
3521
  ] }),
3478
3522
  /* @__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
3523
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-ramp-route-fee", children: [
3480
- quote.fee,
3481
- "% fee"
3524
+ quote.legs.length === 1 ? "1 step" : `${quote.legs.length} steps`,
3525
+ " \xB7 fees included"
3482
3526
  ] }),
3483
- quote.recommended && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-badge", children: "Best rate" })
3527
+ best && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-badge", children: "Best rate" })
3484
3528
  ] }) })
3485
3529
  ]
3486
3530
  }
3487
3531
  );
3488
3532
  }
3489
- function LockedRouteDisplay({ requirement, disabled = false, onVerify }) {
3490
- const { message, action } = lockedRouteCopy(requirement);
3533
+ function RejectedRouteDisplay({
3534
+ rejection,
3535
+ disabled = false,
3536
+ trusting = false,
3537
+ walletBusy = false,
3538
+ custodyChoice = false,
3539
+ onVerify,
3540
+ onTrust,
3541
+ onWalletStep
3542
+ }) {
3543
+ const requirement = rejection.requirement;
3544
+ const copy = requirement ? core.rampRequirementCopy(requirement) : null;
3545
+ const trustAsset = !requirement && onTrust ? core.rampTrustlineAsset(rejection) : null;
3546
+ const step = !requirement ? core.rampWalletStep(rejection) : null;
3547
+ const fromMenu = step?.action === "create_wallet" && custodyChoice;
3548
+ const walletStep = onWalletStep && !fromMenu ? step : null;
3549
+ const picks = custodyChoice && step?.action !== "set_up_recovery" ? ["COSIGNED", "SELF_CUSTODY"] : [void 0];
3550
+ const meta = copy ? copy.message : fromMenu ? "You need a wallet to receive this. Create it from the wallet menu, then find the route again." : core.rampResolutionText(rejection.failure);
3551
+ const busy = trusting || !!walletStep && walletBusy;
3491
3552
  return /* @__PURE__ */ jsxRuntime.jsxs(
3492
3553
  "div",
3493
3554
  {
3494
3555
  className: "pollar-ramp-route-card",
3495
3556
  "data-locked": true,
3496
- "data-kyc-status": requirement.status,
3497
- "data-disabled": disabled || void 0,
3557
+ "data-kyc-status": requirement?.status,
3558
+ "data-disabled": !(copy?.action && onVerify) && !trustAsset && !walletStep || disabled && !busy || void 0,
3498
3559
  children: [
3499
3560
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-route-left", children: [
3500
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-provider", children: requirement.provider }),
3501
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-meta", children: message })
3561
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-provider", children: rejection.legs.map((leg) => core.rampLegTitle(leg)).join(" \xB7 ") }),
3562
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-route-meta", children: meta })
3502
3563
  ] }),
3503
- action && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-route-right", children: /* @__PURE__ */ jsxRuntime.jsx(
3564
+ trustAsset && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-route-right", children: /* @__PURE__ */ jsxRuntime.jsx(
3565
+ "button",
3566
+ {
3567
+ type: "button",
3568
+ className: "pollar-btn-secondary pollar-ramp-route-verify",
3569
+ disabled,
3570
+ "aria-busy": trusting,
3571
+ onClick: () => onTrust(trustAsset),
3572
+ children: trusting ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3573
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3574
+ " Adding\u2026"
3575
+ ] }) : `Add ${trustAsset.code} trustline`
3576
+ }
3577
+ ) }),
3578
+ walletStep && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-route-right", children: walletBusy ? /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "pollar-btn-secondary pollar-ramp-route-verify", disabled: true, "aria-busy": true, children: [
3579
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3580
+ " ",
3581
+ walletStep.busyLabel
3582
+ ] }) : picks.map((custody) => /* @__PURE__ */ jsxRuntime.jsx(
3583
+ "button",
3584
+ {
3585
+ type: "button",
3586
+ className: "pollar-btn-secondary pollar-ramp-route-verify",
3587
+ disabled,
3588
+ onClick: () => onWalletStep(walletStep, custody),
3589
+ children: custody ? `${walletStep.label} (${CUSTODY_LABEL[custody]})` : walletStep.label
3590
+ },
3591
+ custody ?? "app"
3592
+ )) }),
3593
+ requirement && copy?.action && onVerify && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-route-right", children: /* @__PURE__ */ jsxRuntime.jsx(
3504
3594
  "button",
3505
3595
  {
3506
3596
  type: "button",
3507
3597
  className: "pollar-btn-secondary pollar-ramp-route-verify",
3508
3598
  disabled,
3509
3599
  onClick: () => onVerify(requirement),
3510
- children: action
3600
+ children: copy.action
3511
3601
  }
3512
3602
  ) })
3513
3603
  ]
3514
3604
  }
3515
3605
  );
3516
3606
  }
3517
- var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
3518
- function isFieldValid(spec, raw) {
3519
- const value = (raw ?? "").trim();
3520
- if (!value) return spec.optional === true;
3521
- if (spec.type === "email") return EMAIL_RE.test(value);
3522
- return true;
3607
+ function formatAvailable(available) {
3608
+ if (available === null) return "\u2014";
3609
+ const n = parseFloat(available);
3610
+ return isNaN(n) ? available : n.toLocaleString(void 0, { maximumFractionDigits: 7 });
3611
+ }
3612
+ function AmountField({
3613
+ id,
3614
+ label,
3615
+ value,
3616
+ onChange,
3617
+ unit,
3618
+ available,
3619
+ onMax,
3620
+ placeholder = "0.00",
3621
+ disabled = false
3622
+ }) {
3623
+ const showBalance = available !== void 0;
3624
+ const canMax = showBalance && available !== null && parseFloat(available) > 0 && !disabled;
3625
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-field", children: [
3626
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-label-row", children: [
3627
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-send-label", htmlFor: id, children: label }),
3628
+ showBalance && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-send-hint", children: [
3629
+ "Available: ",
3630
+ formatAvailable(available),
3631
+ " ",
3632
+ unit ?? ""
3633
+ ] })
3634
+ ] }),
3635
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-input-shell", children: [
3636
+ /* @__PURE__ */ jsxRuntime.jsx(
3637
+ "input",
3638
+ {
3639
+ id,
3640
+ className: "pollar-input pollar-send-amount-input",
3641
+ type: "text",
3642
+ inputMode: "decimal",
3643
+ placeholder,
3644
+ value,
3645
+ disabled,
3646
+ onChange: (e) => onChange(e.target.value),
3647
+ "aria-label": id ? void 0 : label
3648
+ }
3649
+ ),
3650
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-input-suffix", children: [
3651
+ showBalance && /* @__PURE__ */ jsxRuntime.jsx(
3652
+ "button",
3653
+ {
3654
+ type: "button",
3655
+ className: "pollar-send-inline-action",
3656
+ disabled: !canMax,
3657
+ onClick: () => onMax ? onMax() : available && onChange(available),
3658
+ children: "Max"
3659
+ }
3660
+ ),
3661
+ unit && /* @__PURE__ */ jsxRuntime.jsx("span", { children: unit })
3662
+ ] })
3663
+ ] })
3664
+ ] });
3523
3665
  }
3524
3666
  function scannableImageSrc(image) {
3525
3667
  return image.encoding === "base64" ? `data:${image.mediaType};base64,${image.data}` : `data:${image.mediaType};charset=utf-8,${encodeURIComponent(image.data)}`;
@@ -3538,102 +3680,143 @@ function placeholderFor(field, fields, values) {
3538
3680
  }
3539
3681
  return field.placeholder;
3540
3682
  }
3541
- var CHAIN_NAME = { STELLAR: "Stellar", POLYGON: "Polygon", SOLANA: "Solana" };
3542
- var EXPLORER_NAME = { STELLAR: "Stellar Expert", POLYGON: "Polygonscan", SOLANA: "Solana Explorer" };
3543
- var LOADING_STEPS = ["Detecting your country\u2026", "Consulting providers\u2026", "Route found!"];
3544
3683
  function flagEmoji(code) {
3545
3684
  const cc = code.trim().toUpperCase();
3546
3685
  if (!/^[A-Z]{2}$/.test(cc)) return "";
3547
3686
  const base = 127462;
3548
3687
  return String.fromCodePoint(base + (cc.charCodeAt(0) - 65), base + (cc.charCodeAt(1) - 65));
3549
3688
  }
3550
- var STATUS_LABEL = {
3551
- pending: "Pending",
3552
- processing: "Processing",
3553
- completed: "Completed",
3554
- failed: "Failed"
3555
- };
3556
- function displayValue(field) {
3557
- if (field.type !== "datetime") return field.value;
3558
- const at = new Date(field.value);
3559
- return Number.isNaN(at.getTime()) ? field.value : at.toLocaleString();
3689
+ var CHAIN_NAME = { STELLAR: "Stellar", POLYGON: "Polygon", SOLANA: "Solana" };
3690
+ function explorerTxUrl2(chain, hash, network) {
3691
+ const testnet = network === "testnet";
3692
+ if (chain === "SOLANA") return `https://explorer.solana.com/tx/${hash}${testnet ? "?cluster=devnet" : ""}`;
3693
+ if (chain === "POLYGON") return `https://${testnet ? "amoy." : ""}polygonscan.com/tx/${hash}`;
3694
+ return `https://stellar.expert/explorer/${testnet ? "testnet" : "public"}/tx/${hash}`;
3695
+ }
3696
+ function legChain(leg) {
3697
+ if (leg.from.kind === "asset") return leg.from.chain;
3698
+ return leg.to.kind === "asset" ? leg.to.chain : "STELLAR";
3699
+ }
3700
+ function stepChain(kind, leg) {
3701
+ if (kind === "mint" && leg.to.kind === "asset") return leg.to.chain;
3702
+ return legChain(leg);
3703
+ }
3704
+ var timeLeftText = (seconds) => seconds <= 0 ? "Taking a little longer than usual" : seconds < 60 ? `About ${seconds}s left` : `About ${Math.ceil(seconds / 60)} min left`;
3705
+ function useNowEverySecond(running) {
3706
+ const [now, setNow] = react.useState(() => Date.now());
3707
+ react.useEffect(() => {
3708
+ if (!running) return;
3709
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
3710
+ return () => clearInterval(timer);
3711
+ }, [running]);
3712
+ return now;
3713
+ }
3714
+ var fiatKey = (fiat) => `${fiat.currency}:${fiat.rail}`;
3715
+ var assetKey3 = (asset) => `${asset.code}:${asset.chain}:${asset.identifier ?? ""}`;
3716
+ function trustKey(rejection) {
3717
+ const asset = core.rampTrustlineAsset(rejection);
3718
+ return asset ? `${asset.code}:${asset.issuer}` : null;
3719
+ }
3720
+ var LOADING_STEPS = ["Checking routes\u2026", "Quoting each step\u2026", "Route found!"];
3721
+ function useRenderAt(at) {
3722
+ const [, setTick] = react.useState(0);
3723
+ react.useEffect(() => {
3724
+ if (at === null) return;
3725
+ const timer = setTimeout(() => setTick((n) => n + 1), Math.max(0, at - Date.now()) + 50);
3726
+ return () => clearTimeout(timer);
3727
+ }, [at]);
3560
3728
  }
3561
3729
  function RampWidgetTemplate({
3562
3730
  theme,
3563
3731
  accentColor,
3564
3732
  styleOverrides,
3733
+ network,
3565
3734
  step,
3566
3735
  flowSteps,
3567
3736
  flowStepIndex,
3568
- startingQuoteId,
3569
3737
  direction,
3570
- amount,
3571
- currency,
3572
- country,
3573
- requiredFields,
3574
- fieldValues,
3738
+ routesLoading,
3575
3739
  countries,
3576
- countriesLoading,
3577
- refreshing,
3740
+ country,
3741
+ fiats,
3742
+ fiat,
3743
+ assets,
3744
+ asset,
3745
+ amount,
3746
+ sellAvailable,
3578
3747
  quotes,
3579
- kycRequired,
3748
+ rejected,
3749
+ startingQuoteId,
3750
+ fields,
3751
+ fieldValues,
3752
+ order,
3753
+ pendingOrders,
3580
3754
  isLoading,
3581
- provider,
3582
- txStatus,
3583
- kycUrl,
3584
- tosUrl,
3585
- kycBlocking,
3586
- onboardingStatus,
3587
- kycJustApproved,
3588
- stellarTxHash,
3589
- txHash,
3590
- txChain,
3591
- explorerUrl,
3592
- fundsInTransit = false,
3593
- retrying = false,
3594
- onRetryTransit,
3595
- depositInstructions,
3596
- canComplete,
3597
- completing,
3755
+ refreshing,
3756
+ signing,
3598
3757
  errorMsg,
3599
- noticeMsg,
3758
+ notice = null,
3600
3759
  onDirectionChange,
3601
- onAmountChange,
3602
- onFieldChange,
3603
3760
  onCountryChange,
3761
+ onFiatChange,
3762
+ onAssetChange,
3763
+ onAmountChange,
3604
3764
  onFindRoute,
3605
3765
  onSelectQuote,
3606
3766
  onVerifyRoute,
3767
+ trustingAsset = null,
3768
+ onTrustAsset,
3769
+ walletBusy = null,
3770
+ custodyChoice = false,
3771
+ onWalletStep,
3772
+ noChains = false,
3773
+ retrying = false,
3774
+ onRetryOrder,
3775
+ onSetUpRecovery,
3776
+ recoveryMissing = null,
3777
+ checkingPayment = false,
3778
+ onCheckPayment,
3779
+ pollTrouble = false,
3780
+ onFieldChange,
3607
3781
  onContactContinue,
3608
- onOpenKyc,
3609
- onOpenTos,
3610
- onCompleteWithdraw,
3782
+ onSign,
3783
+ onApprove,
3784
+ onResumeOrder,
3785
+ onNewOrder,
3611
3786
  onBack,
3787
+ onStepSelect,
3612
3788
  onRetry,
3613
3789
  onRefresh,
3614
3790
  onClose
3615
3791
  }) {
3616
- const settledHash = txHash ?? stellarTxHash;
3617
- const chainName = CHAIN_NAME[txChain ?? "STELLAR"];
3618
3792
  const cssVars = buildModalCssVars(theme, accentColor, styleOverrides, "hero");
3793
+ const onramp = direction === "ONRAMP";
3794
+ const sendUnit = onramp ? fiat?.currency : asset?.code;
3795
+ const view = order ? core.describeRampAction(order) : null;
3796
+ const settled = order ? ["completed", "failed", "refunded"].includes(order.state) : false;
3797
+ const running = order?.legs.find((leg) => core.rampLegStatus(leg) === "active" && order.pendingAction?.legIndex !== leg.index);
3798
+ const now = useNowEverySecond(step === "status" && !settled && !!running && running.etaSeconds !== null);
3799
+ const nextApproval = order && !settled ? core.rampNextApproval(order) : null;
3800
+ const stopped = order ? core.rampOrderStopped(order) : false;
3801
+ const receive = order ? core.rampOrderReceive(order) : null;
3802
+ const waitsOnRecovery = order?.failure?.resolutionAction === "set_up_recovery";
3803
+ const payBy = view?.action?.kind === "FIAT_PAYMENT" && view.expiresAt ? Date.parse(view.expiresAt) : null;
3804
+ useRenderAt(view?.canSign && view.expiresAt ? Date.parse(view.expiresAt) : null);
3805
+ const stepsOpen = !!onStepSelect && step !== "status" && !isLoading && startingQuoteId === null && trustingAsset === null && walletBusy === null;
3619
3806
  const stepTitle = {
3620
- input: direction === "onramp" ? "Buy crypto" : "Sell crypto",
3621
- loading_quote: "Finding best route",
3622
- select_route: "Select provider",
3807
+ input: onramp ? "Buy crypto" : "Sell crypto",
3808
+ loading_quote: "Finding the best route",
3809
+ select_route: "Choose a route",
3623
3810
  contact: "Your details",
3624
- status: direction === "onramp" ? "Complete your deposit" : "Complete your withdrawal",
3811
+ status: order?.state === "completed" ? "Transfer completed" : onramp ? "Your purchase" : "Your sale",
3625
3812
  error: "Something went wrong"
3626
3813
  };
3627
3814
  const stepSubtitle = {
3628
- input: direction === "onramp" ? "Enter the amount you want to deposit" : "Enter the amount you want to withdraw",
3629
- loading_quote: "Comparing providers in real time\u2026",
3630
- select_route: "All prices include fees",
3631
- // What this step actually asks for is whatever the route declared, and that
3632
- // is rarely a name and an email: an Abroad off-ramp asks where to send the
3633
- // money, not who you are. Say which, rather than asserting the Bridge case
3634
- // over every provider.
3635
- 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`,
3815
+ input: onramp ? "Pick what you receive and how much you pay" : "Pick what you sell and how much",
3816
+ loading_quote: "Comparing routes in real time\u2026",
3817
+ select_route: "All amounts include fees",
3818
+ contact: `This route needs ${listOf(fields.filter((f) => !f.optional).map((f) => f.label.toLowerCase()))} to continue`,
3819
+ status: view?.title ?? "Processing",
3637
3820
  error: "Please try again"
3638
3821
  };
3639
3822
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-card pollar-ramp-modal", style: cssVars, onClick: (e) => e.stopPropagation(), children: [
@@ -3674,85 +3857,124 @@ function RampWidgetTemplate({
3674
3857
  " of ",
3675
3858
  flowSteps.length
3676
3859
  ] }),
3677
- /* @__PURE__ */ jsxRuntime.jsx(
3678
- "div",
3860
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "pollar-ramp-steps-track", children: flowSteps.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx("li", { className: "pollar-ramp-steps-item", "aria-current": i === flowStepIndex ? "step" : void 0, children: /* @__PURE__ */ jsxRuntime.jsxs(
3861
+ "button",
3679
3862
  {
3680
- className: "pollar-ramp-steps-track",
3681
- role: "progressbar",
3682
- "aria-valuenow": flowStepIndex + 1,
3683
- "aria-valuemin": 1,
3684
- "aria-valuemax": flowSteps.length,
3685
- "aria-label": flowSteps[flowStepIndex],
3686
- children: flowSteps.map((label, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-steps-segment", "data-done": i <= flowStepIndex || void 0 }, label))
3863
+ type: "button",
3864
+ className: "pollar-ramp-steps-step",
3865
+ "data-done": i <= flowStepIndex || void 0,
3866
+ disabled: !stepsOpen || i >= flowStepIndex,
3867
+ onClick: () => onStepSelect?.(label),
3868
+ children: [
3869
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-steps-segment" }),
3870
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-steps-name", children: label })
3871
+ ]
3687
3872
  }
3688
- )
3873
+ ) }, label)) })
3874
+ ] }),
3875
+ step === "input" && pendingOrders.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-ramp-payment pollar-ramp-saved", "aria-label": "Pending operations", children: [
3876
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "These operations are waiting for you. Starting another does not cancel them." }),
3877
+ pendingOrders.map((pending) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-saved-row", children: /* @__PURE__ */ jsxRuntime.jsxs(
3878
+ "button",
3879
+ {
3880
+ type: "button",
3881
+ className: "pollar-btn-secondary",
3882
+ disabled: isLoading,
3883
+ onClick: () => onResumeOrder(pending.id),
3884
+ children: [
3885
+ "Resume ",
3886
+ pending.direction === "ONRAMP" ? "purchase" : "sale",
3887
+ " \xB7 ",
3888
+ pending.id.slice(-8)
3889
+ ]
3890
+ }
3891
+ ) }, pending.id))
3689
3892
  ] }),
3690
3893
  step === "input" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3691
3894
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-tabs", children: [
3692
- /* @__PURE__ */ jsxRuntime.jsx(
3693
- "button",
3694
- {
3695
- type: "button",
3696
- className: "pollar-tab",
3697
- "data-active": direction === "onramp",
3698
- onClick: () => onDirectionChange("onramp"),
3699
- children: "Buy"
3700
- }
3701
- ),
3702
- /* @__PURE__ */ jsxRuntime.jsx(
3703
- "button",
3704
- {
3705
- type: "button",
3706
- className: "pollar-tab",
3707
- "data-active": direction === "offramp",
3708
- onClick: () => onDirectionChange("offramp"),
3709
- children: "Sell"
3710
- }
3711
- )
3895
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-tab", "data-active": onramp, onClick: () => onDirectionChange("ONRAMP"), children: "Buy" }),
3896
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-tab", "data-active": !onramp, onClick: () => onDirectionChange("OFFRAMP"), children: "Sell" })
3712
3897
  ] }),
3713
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
3714
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-ramp-label", children: "Country" }),
3715
- countriesLoading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-input pollar-select-loading", children: [
3716
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3717
- /* @__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)) })
3724
- ] }),
3725
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
3726
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-ramp-label", children: [
3727
- "Amount",
3728
- currency ? ` (${currency})` : ""
3898
+ routesLoading ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-input pollar-select-loading", children: [
3899
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3900
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Loading routes\u2026" })
3901
+ ] }) : countries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-error", children: noChains ? "This app has no chains enabled, so there is nothing to buy or sell yet." : "No ramp routes are available for this app yet." }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3902
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
3903
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-country", children: "Country" }),
3904
+ /* @__PURE__ */ jsxRuntime.jsx(
3905
+ "select",
3906
+ {
3907
+ id: "pollar-ramp-country",
3908
+ className: "pollar-ramp-input",
3909
+ value: country,
3910
+ onChange: (e) => onCountryChange(e.target.value),
3911
+ children: countries.map((c) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: c.country, children: [
3912
+ flagEmoji(c.country),
3913
+ " ",
3914
+ c.country
3915
+ ] }, c.country))
3916
+ }
3917
+ )
3729
3918
  ] }),
3730
- /* @__PURE__ */ jsxRuntime.jsx(
3731
- "input",
3732
- {
3733
- type: "number",
3734
- className: "pollar-ramp-input",
3735
- placeholder: "0.00",
3736
- value: amount,
3737
- min: "0",
3738
- onChange: (e) => onAmountChange(e.target.value)
3739
- }
3740
- ),
3741
- errorMsg && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-field-error", children: errorMsg })
3919
+ fiats.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
3920
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-fiat", children: onramp ? "Pay with" : "Receive in" }),
3921
+ /* @__PURE__ */ jsxRuntime.jsx(
3922
+ "select",
3923
+ {
3924
+ id: "pollar-ramp-fiat",
3925
+ className: "pollar-ramp-input",
3926
+ value: fiat ? fiatKey(fiat) : "",
3927
+ onChange: (e) => onFiatChange(e.target.value),
3928
+ children: fiats.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: fiatKey(f), children: [
3929
+ f.currency,
3930
+ " \xB7 ",
3931
+ core.rampRailLabel(f.rail, { language: formLanguage() })
3932
+ ] }, fiatKey(f)))
3933
+ }
3934
+ )
3935
+ ] }),
3936
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
3937
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-ramp-label", htmlFor: "pollar-ramp-asset", children: onramp ? "Receive" : "Sell" }),
3938
+ /* @__PURE__ */ jsxRuntime.jsx(
3939
+ "select",
3940
+ {
3941
+ id: "pollar-ramp-asset",
3942
+ className: "pollar-ramp-input",
3943
+ value: asset ? assetKey3(asset) : "",
3944
+ onChange: (e) => onAssetChange(e.target.value),
3945
+ children: assets.map((a) => /* @__PURE__ */ jsxRuntime.jsxs("option", { value: assetKey3(a), children: [
3946
+ a.code,
3947
+ " on ",
3948
+ CHAIN_NAME[a.chain] ?? a.chain
3949
+ ] }, assetKey3(a)))
3950
+ }
3951
+ )
3952
+ ] }),
3953
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
3954
+ /* @__PURE__ */ jsxRuntime.jsx(
3955
+ AmountField,
3956
+ {
3957
+ id: "pollar-ramp-amount",
3958
+ label: onramp ? "You pay" : "You sell",
3959
+ value: amount,
3960
+ onChange: onAmountChange,
3961
+ unit: sendUnit,
3962
+ available: onramp ? void 0 : sellAvailable
3963
+ }
3964
+ ),
3965
+ errorMsg && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-field-error", children: errorMsg })
3966
+ ] })
3742
3967
  ] }),
3743
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-actions", children: [
3744
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: "Cancel" }),
3745
- /* @__PURE__ */ jsxRuntime.jsx(
3746
- "button",
3747
- {
3748
- type: "button",
3749
- className: "pollar-btn-primary",
3750
- disabled: !amount || isLoading || countriesLoading || countries.length === 0,
3751
- onClick: onFindRoute,
3752
- children: "Find best route"
3753
- }
3754
- )
3755
- ] })
3968
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
3969
+ "button",
3970
+ {
3971
+ type: "button",
3972
+ className: "pollar-btn-primary",
3973
+ disabled: !amount || !fiat || !asset || isLoading || routesLoading,
3974
+ onClick: onFindRoute,
3975
+ children: "Find best route"
3976
+ }
3977
+ ) })
3756
3978
  ] }),
3757
3979
  step === "loading_quote" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-loading", children: [
3758
3980
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-spinner pollar-ramp-loading-spinner" }),
@@ -3762,33 +3984,44 @@ function RampWidgetTemplate({
3762
3984
  ] }, i))
3763
3985
  ] }),
3764
3986
  step === "select_route" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3765
- noticeMsg && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: noticeMsg }),
3766
3987
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-route-list", children: [
3767
3988
  quotes.map((q, i) => /* @__PURE__ */ jsxRuntime.jsx(
3768
3989
  RouteDisplay,
3769
3990
  {
3770
3991
  quote: q,
3771
- busy: startingQuoteId != null && q.quoteId === startingQuoteId,
3992
+ best: i === 0,
3993
+ busy: startingQuoteId === q.quoteId,
3772
3994
  disabled: startingQuoteId != null && q.quoteId !== startingQuoteId,
3773
3995
  onSelect: onSelectQuote
3774
3996
  },
3775
- i
3997
+ q.quoteId
3776
3998
  )),
3777
- kycRequired.map((r) => /* @__PURE__ */ jsxRuntime.jsx(
3778
- LockedRouteDisplay,
3999
+ rejected.map((r, i) => /* @__PURE__ */ jsxRuntime.jsx(
4000
+ RejectedRouteDisplay,
3779
4001
  {
3780
- requirement: r,
3781
- disabled: startingQuoteId != null,
3782
- onVerify: onVerifyRoute
4002
+ rejection: r,
4003
+ disabled: startingQuoteId != null || trustingAsset !== null || walletBusy !== null,
4004
+ trusting: trustKey(r) === trustingAsset,
4005
+ walletBusy: walletBusy === i,
4006
+ custodyChoice,
4007
+ onVerify: onVerifyRoute,
4008
+ onTrust: onTrustAsset,
4009
+ onWalletStep: onWalletStep && ((step2, custody) => onWalletStep(step2, i, custody))
3783
4010
  },
3784
- `${r.rampProviderId}:${r.corridorId}`
4011
+ i
3785
4012
  ))
3786
4013
  ] }),
4014
+ quotes.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: core.rampNoRouteText(rejected) }),
4015
+ notice && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-ramp-payment-note", children: [
4016
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm", "aria-hidden": true }),
4017
+ " ",
4018
+ notice
4019
+ ] }),
3787
4020
  errorMsg && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", style: { color: "var(--pollar-error-text)" }, children: errorMsg }),
3788
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: errorMsg ? "pollar-btn-primary" : "pollar-btn-secondary", onClick: onBack, children: "Back" })
4021
+ (errorMsg || !quotes.length) && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", onClick: onBack, children: "Back" })
3789
4022
  ] }),
3790
4023
  step === "contact" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3791
- requiredFields.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
4024
+ fields.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-field", children: [
3792
4025
  /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-ramp-label", children: [
3793
4026
  f.label,
3794
4027
  f.optional && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-field-optional", children: " (optional)" })
@@ -3810,175 +4043,178 @@ function RampWidgetTemplate({
3810
4043
  type: f.type,
3811
4044
  className: "pollar-ramp-input",
3812
4045
  value: fieldValues[f.key] ?? "",
3813
- placeholder: placeholderFor(f, requiredFields, fieldValues),
4046
+ placeholder: placeholderFor(f, fields, fieldValues),
3814
4047
  autoComplete: f.type === "email" ? "email" : "off",
3815
4048
  onChange: (e) => onFieldChange(f.key, e.target.value)
3816
4049
  }
3817
4050
  ),
3818
4051
  f.hint && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-field-hint", children: f.hint }),
3819
- f.type === "email" && (fieldValues[f.key] ?? "").trim() !== "" && !isFieldValid(f, fieldValues[f.key]) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-field-error", children: "Enter a valid email address." })
4052
+ core.rampFieldError(f, fieldValues[f.key]) && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-field-error", children: core.rampFieldError(f, fieldValues[f.key]) })
3820
4053
  ] }, f.key)),
3821
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-actions", children: [
3822
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onBack, children: "Back" }),
3823
- /* @__PURE__ */ jsxRuntime.jsx(
4054
+ errorMsg && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-field-error", children: errorMsg }),
4055
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
4056
+ "button",
4057
+ {
4058
+ type: "button",
4059
+ className: "pollar-btn-primary",
4060
+ disabled: fields.some((f) => !core.rampFieldValid(f, fieldValues[f.key])) || isLoading,
4061
+ onClick: onContactContinue,
4062
+ children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4063
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
4064
+ "Starting\u2026"
4065
+ ] }) : "Continue"
4066
+ }
4067
+ ) })
4068
+ ] }),
4069
+ step === "status" && order && view && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment", children: [
4070
+ /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "pollar-ramp-legs", "aria-label": "Progress", children: order.legs.map((leg) => {
4071
+ const status = core.rampLegStatus(leg);
4072
+ const hash = leg.onchainTxHash;
4073
+ const note = core.rampLegNote(leg);
4074
+ return /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "pollar-ramp-leg", "data-status": status, children: [
4075
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-leg-dot", "aria-hidden": true }),
4076
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-leg-title", children: core.rampLegTitle(leg) }),
4077
+ status === "active" && !settled && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm", "aria-label": "In progress" }),
4078
+ hash && /* @__PURE__ */ jsxRuntime.jsxs(
4079
+ "a",
4080
+ {
4081
+ className: "pollar-ramp-leg-link",
4082
+ href: explorerTxUrl2(legChain(leg), hash, network),
4083
+ target: "_blank",
4084
+ rel: "noopener noreferrer",
4085
+ children: [
4086
+ hash.slice(0, 6),
4087
+ "\u2026",
4088
+ hash.slice(-4)
4089
+ ]
4090
+ }
4091
+ ),
4092
+ note && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-leg-note", children: note }),
4093
+ status === "active" && !settled && !!leg.steps?.length && /* @__PURE__ */ jsxRuntime.jsx("ol", { className: "pollar-ramp-steps", children: leg.steps.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("li", { className: "pollar-ramp-step", "data-status": s.status, children: [
4094
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-step-title", children: core.rampStepTitle(s, leg) }),
4095
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-ramp-step-state", children: [
4096
+ s.status === "confirming" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm", "aria-hidden": true }),
4097
+ s.hash ? /* @__PURE__ */ jsxRuntime.jsx(
4098
+ "a",
4099
+ {
4100
+ className: "pollar-ramp-leg-link",
4101
+ href: explorerTxUrl2(stepChain(s.kind, leg), s.hash, network),
4102
+ target: "_blank",
4103
+ rel: "noopener noreferrer",
4104
+ children: core.rampStepStatusText(s)
4105
+ }
4106
+ ) : core.rampStepStatusText(s)
4107
+ ] })
4108
+ ] }, s.kind)) }),
4109
+ running === leg && !settled && core.rampLegTimeLeft(leg, now) !== null && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-leg-eta", children: timeLeftText(core.rampLegTimeLeft(leg, now)) })
4110
+ ] }, leg.index);
4111
+ }) }),
4112
+ nextApproval && !order.pendingAction && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-ramp-next", children: [
4113
+ "Next: ",
4114
+ nextApproval,
4115
+ ". You approve it on this device when it comes up."
4116
+ ] }),
4117
+ receive && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
4118
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: order.state === "completed" ? "You received" : "You receive" }),
4119
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-value", children: [
4120
+ receive.estimated && "~",
4121
+ receive.amount,
4122
+ " ",
4123
+ receive.unit
4124
+ ] })
4125
+ ] }),
4126
+ order.failure && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
4127
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: "What to do" }),
4128
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: core.rampOrderResolutionText(order) }),
4129
+ stopped && !settled && waitsOnRecovery && recoveryMissing !== false && onSetUpRecovery && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onSetUpRecovery, children: "Set up recovery" }),
4130
+ stopped && !settled && waitsOnRecovery && recoveryMissing === true && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "Once it is set up, the order goes on by itself." }),
4131
+ stopped && !settled && order.failure.resolutionOwner === "END_USER" && onRetryOrder && /* @__PURE__ */ jsxRuntime.jsx(
3824
4132
  "button",
3825
4133
  {
3826
4134
  type: "button",
3827
4135
  className: "pollar-btn-primary",
3828
- disabled: requiredFields.some((f) => !isFieldValid(f, fieldValues[f.key])) || isLoading,
3829
- onClick: onContactContinue,
3830
- children: isLoading ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4136
+ disabled: retrying || waitsOnRecovery && recoveryMissing === true,
4137
+ onClick: onRetryOrder,
4138
+ children: retrying ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3831
4139
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3832
- "Starting\u2026"
3833
- ] }) : "Continue"
4140
+ " Trying again\u2026"
4141
+ ] }) : "Try again"
3834
4142
  }
3835
- )
3836
- ] })
3837
- ] }),
3838
- step === "status" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment", children: [
3839
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
3840
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: "Provider" }),
3841
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsxRuntime.jsx("code", { children: provider }) })
3842
- ] }),
3843
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
3844
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: "Status" }),
3845
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-value", children: [
3846
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: { color: txStatus === "completed" ? "var(--pollar-success-text)" : void 0 }, children: txStatus ? STATUS_LABEL[txStatus] : "Processing" }),
3847
- txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm", "aria-label": "Checking status" })
3848
- ] })
3849
- ] }),
3850
- fundsInTransit && txStatus !== "completed" && txStatus !== "failed" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
3851
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: "Funds in transit" }),
3852
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "Your money is safe in your own wallet, waiting for the next step of the transfer." }),
3853
- onRetryTransit && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onRetryTransit, disabled: retrying, children: retrying ? "Retrying..." : "Retry now" })
3854
- ] }),
3855
- settledHash && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
3856
- /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-ramp-payment-label", children: [
3857
- chainName,
3858
- " tx"
3859
- ] }),
4143
+ ),
3860
4144
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-value", style: { display: "flex", alignItems: "center", gap: 6 }, children: [
3861
4145
  /* @__PURE__ */ jsxRuntime.jsxs("code", { style: { flex: 1, wordBreak: "break-all" }, children: [
3862
- settledHash.slice(0, 8),
3863
- "\u2026",
3864
- settledHash.slice(-8)
4146
+ "Order ",
4147
+ order.id
3865
4148
  ] }),
3866
- /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { value: settledHash, label: "Copy transaction hash" }),
3867
- explorerUrl && /* @__PURE__ */ jsxRuntime.jsx(
3868
- "a",
3869
- {
3870
- href: explorerUrl,
3871
- target: "_blank",
3872
- rel: "noopener noreferrer",
3873
- className: "pollar-copy-btn",
3874
- "aria-label": `View on ${EXPLORER_NAME[txChain ?? "STELLAR"]}`,
3875
- title: `View on ${EXPLORER_NAME[txChain ?? "STELLAR"]}`,
3876
- children: /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "13", height: "13", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: [
3877
- /* @__PURE__ */ jsxRuntime.jsx(
3878
- "path",
3879
- {
3880
- d: "M6 3H3v8h8V8",
3881
- stroke: "currentColor",
3882
- strokeWidth: "1.5",
3883
- strokeLinecap: "round",
3884
- strokeLinejoin: "round"
3885
- }
3886
- ),
3887
- /* @__PURE__ */ jsxRuntime.jsx(
3888
- "path",
3889
- {
3890
- d: "M8.5 2.5h3v3M11 3L6.5 7.5",
3891
- stroke: "currentColor",
3892
- strokeWidth: "1.5",
3893
- strokeLinecap: "round",
3894
- strokeLinejoin: "round"
3895
- }
3896
- )
3897
- ] })
3898
- }
3899
- )
4149
+ /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { value: order.id, label: "Copy order id" })
3900
4150
  ] })
3901
4151
  ] }),
3902
- depositInstructions?.scannable && txStatus !== "completed" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
4152
+ !settled && payBy !== null && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: payBy > Date.now() ? `Pay before ${new Date(payBy).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" })}.` : "This payment window has closed. If you have not paid, start a new operation." }),
4153
+ !settled && view.scannable && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
3903
4154
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: "Payment QR" }),
3904
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-payment-value", children: depositInstructions.scannable.image.inlineSafe ? /* @__PURE__ */ jsxRuntime.jsx(
4155
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-payment-value", children: view.scannable.image.inlineSafe ? /* @__PURE__ */ jsxRuntime.jsx(
3905
4156
  "div",
3906
4157
  {
3907
4158
  className: "pollar-ramp-qr",
3908
4159
  "aria-label": "Payment QR",
3909
- dangerouslySetInnerHTML: { __html: depositInstructions.scannable.image.data }
4160
+ dangerouslySetInnerHTML: { __html: view.scannable.image.data }
3910
4161
  }
3911
4162
  ) : /* @__PURE__ */ jsxRuntime.jsx(
3912
4163
  "img",
3913
4164
  {
3914
- src: scannableImageSrc(depositInstructions.scannable.image),
4165
+ src: scannableImageSrc(view.scannable.image),
3915
4166
  alt: "Payment QR",
3916
4167
  style: { width: "100%", maxWidth: 220, height: "auto", display: "block", margin: "0 auto" }
3917
4168
  }
3918
4169
  ) })
3919
4170
  ] }),
3920
- depositInstructions?.scannable?.payload && depositInstructions.scannable.payloadLabel && txStatus !== "completed" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
3921
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: depositInstructions.scannable.payloadLabel }),
4171
+ !settled && view.codeMissing && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", style: { color: "var(--pollar-error-text)" }, children: "The payment code could not be shown. Refresh; if it still does not appear, contact this app's support." }),
4172
+ !settled && view.scannable?.payload && view.scannable.payloadLabel && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
4173
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: view.scannable.payloadLabel }),
3922
4174
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
3923
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: { flex: 1, wordBreak: "break-all" }, children: depositInstructions.scannable.payload }),
3924
- /* @__PURE__ */ jsxRuntime.jsx(
3925
- CopyButton,
3926
- {
3927
- value: depositInstructions.scannable.payload,
3928
- label: `Copy ${depositInstructions.scannable.payloadLabel}`
3929
- }
3930
- )
4175
+ /* @__PURE__ */ jsxRuntime.jsx("code", { style: { flex: 1, wordBreak: "break-all" }, children: view.scannable.payload }),
4176
+ /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { value: view.scannable.payload, label: `Copy ${view.scannable.payloadLabel}` })
3931
4177
  ] }) })
3932
4178
  ] }),
3933
- depositInstructions && txStatus !== "completed" && depositInstructions.fields.map((f) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
3934
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: f.label }),
3935
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-payment-value", children: f.copyable ? /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
3936
- /* @__PURE__ */ jsxRuntime.jsx("code", { style: { flex: 1, wordBreak: "break-all" }, children: f.value }),
3937
- /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { value: f.value, label: `Copy ${f.label}` })
3938
- ] }) : f.type === "url" ? /* @__PURE__ */ jsxRuntime.jsx("a", { href: f.value, target: "_blank", rel: "noopener noreferrer", children: f.value }) : /* @__PURE__ */ jsxRuntime.jsx("span", { children: displayValue(f) }) })
3939
- ] }, f.key)),
3940
- tosUrl && !depositInstructions && txStatus !== "completed" && /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "pollar-btn-primary", onClick: onOpenTos, children: [
3941
- "Accept terms at ",
3942
- provider
3943
- ] }),
3944
- kycUrl && !depositInstructions && txStatus !== "completed" && /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "pollar-btn-primary", onClick: onOpenKyc, children: [
3945
- "Continue at ",
3946
- provider
3947
- ] }),
3948
- kycBlocking && onboardingStatus === "awaiting_provider" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-ramp-payment-note", children: [
3949
- provider,
3950
- " is still setting up your account for this payment method. Your verification is complete and there is nothing left for you to do \u2014 nothing has been sent, and this will update on its own once ",
3951
- provider,
3952
- " is ready."
3953
- ] }),
3954
- kycBlocking && onboardingStatus !== "awaiting_provider" && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-ramp-payment-note", children: [
3955
- provider,
3956
- " needs to verify your identity before this payout. Nothing has been sent yet \u2014 complete verification with ",
3957
- provider,
3958
- ", and this will update on its own."
3959
- ] }),
3960
- kycJustApproved && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3961
- /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-ramp-payment-note", children: [
3962
- provider,
3963
- " approved your verification. Request a new quote to continue \u2014 the previous one was consumed."
3964
- ] }),
3965
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", onClick: onRetry, children: "Request a new quote" })
3966
- ] }),
3967
- canComplete && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", disabled: completing, onClick: onCompleteWithdraw, children: completing ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4179
+ !settled && view.details.map((d) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment-field", children: [
4180
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-ramp-payment-label", children: d.label }),
4181
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-ramp-payment-value", children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { display: "flex", alignItems: "center", gap: 6 }, children: [
4182
+ /* @__PURE__ */ jsxRuntime.jsx("code", { style: { flex: 1, wordBreak: "break-all" }, children: d.value }),
4183
+ /* @__PURE__ */ jsxRuntime.jsx(CopyButton, { value: d.value, label: `Copy ${d.label}` })
4184
+ ] }) })
4185
+ ] }, d.label)),
4186
+ !settled && view.action?.kind === "FIAT_PAYMENT" && onCheckPayment && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", disabled: checkingPayment, onClick: onCheckPayment, children: checkingPayment ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3968
4187
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }),
3969
- "Submitting\u2026"
3970
- ] }) : "I've completed KYC \u2014 withdraw" }),
3971
- errorMsg && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", style: { color: "var(--pollar-error-text)" }, children: errorMsg }),
3972
- /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: txStatus === "completed" ? "Done" : "Close" })
4188
+ " Checking your payment..."
4189
+ ] }) : "I've paid" }),
4190
+ !settled && view.canSign && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", disabled: signing, onClick: onSign, children: signing ? "Waiting for your wallet\u2026" : "Authorize" }),
4191
+ !settled && view.action?.kind === "SIGNATURE" && !view.canSign && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "Preparing a new signature request\u2026" }),
4192
+ !settled && view.canApprove && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", onClick: onApprove, children: "Approve on this device" }),
4193
+ !settled && view.link && /* @__PURE__ */ jsxRuntime.jsx(
4194
+ "a",
4195
+ {
4196
+ className: "pollar-btn-primary pollar-ramp-hosted-link",
4197
+ href: view.link,
4198
+ target: "_blank",
4199
+ rel: "noopener noreferrer",
4200
+ children: view.linkLabel
4201
+ }
4202
+ ),
4203
+ !settled && order.pendingAction?.kind === "KYC" && !view.link && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: view.note ?? "The provider is reviewing your details. This updates on its own." }),
4204
+ pollTrouble && !settled && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "Having trouble reaching the server. Retrying\u2026" }),
4205
+ errorMsg && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-field-error", children: errorMsg }),
4206
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-explore", children: [
4207
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onNewOrder, children: "Start another operation" }),
4208
+ !stopped && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", children: "This one keeps going; you can resume it later." })
4209
+ ] })
3973
4210
  ] }),
3974
- step === "error" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-ramp-payment", children: [
3975
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-ramp-payment-note", style: { color: "var(--pollar-error-text)" }, children: errorMsg ?? "Unexpected error." }),
4211
+ step === "error" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4212
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-error", children: errorMsg ?? "Something went wrong." }),
3976
4213
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-actions", children: [
3977
4214
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-secondary", onClick: onClose, children: "Close" }),
3978
4215
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-btn-primary", onClick: onRetry, children: "Try again" })
3979
4216
  ] })
3980
- ] }),
3981
- /* @__PURE__ */ jsxRuntime.jsx(PollarModalFooter, {})
4217
+ ] })
3982
4218
  ] });
3983
4219
  }
3984
4220
 
@@ -4133,19 +4369,26 @@ var SHARED_FIELD_LABELS = {
4133
4369
  country_code: "Pa\xEDs de residencia"
4134
4370
  }
4135
4371
  };
4136
- var errorCode = (error) => error && typeof error === "object" && typeof error.code === "string" ? error.code : null;
4372
+ var errorCode = (error) => typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" ? error.code : null;
4373
+ var errorBody = (error) => {
4374
+ const body = typeof error === "object" && error !== null && "body" in error ? error.body : void 0;
4375
+ return typeof body === "object" && body !== null ? body : void 0;
4376
+ };
4137
4377
  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") : [];
4378
+ const body = errorBody(error);
4379
+ const missing = body && "missing" in body ? body.missing : void 0;
4380
+ return Array.isArray(missing) ? missing.filter((item) => typeof item === "string") : [];
4140
4381
  }
4141
4382
  function registrationFixOf(error) {
4142
- const body = error && typeof error === "object" ? error.body : void 0;
4383
+ const body = errorBody(error);
4384
+ const invalid = body && "invalid" in body ? body.invalid : void 0;
4385
+ const formList = body && "forms" in body ? body.forms : void 0;
4143
4386
  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) }] : [];
4387
+ const forms = Array.isArray(formList) ? formList.flatMap((item) => {
4388
+ if (typeof item !== "object" || item === null || !("formId" in item) || typeof item.formId !== "string") return [];
4389
+ return [{ formId: item.formId, keys: strings("keys" in item ? item.keys : void 0) }];
4147
4390
  }) : [];
4148
- return { invalid: strings(body?.invalid), forms };
4391
+ return { invalid: strings(invalid), forms };
4149
4392
  }
4150
4393
  function RegistryCheckModal({ optionId, progress, onClose, onApproved }) {
4151
4394
  const { getClient, styles } = usePollar();
@@ -4465,472 +4708,499 @@ function ProviderRegistrationModal({
4465
4708
  }
4466
4709
  ) });
4467
4710
  }
4468
- var TERMINAL = ["completed", "failed"];
4469
- function requiredFieldsOf(quote) {
4470
- return quote.requiredFields ?? [];
4471
- }
4472
- function flowStepsOf(quotes, selected) {
4473
- const needsDetails = selected != null ? requiredFieldsOf(selected).length > 0 : quotes.some((q) => requiredFieldsOf(q).length > 0);
4474
- return needsDetails ? ["Amount", "Provider", "Details", "Complete"] : ["Amount", "Provider", "Complete"];
4475
- }
4476
- var STEP_LABEL = {
4477
- input: "Amount",
4478
- loading_quote: "Amount",
4479
- select_route: "Provider",
4480
- contact: "Details",
4481
- status: "Complete"
4482
- };
4483
- function brokenLimitOf(amount, quote) {
4484
- const { minAmount, maxAmount } = quote;
4485
- if (!Number.isFinite(amount)) return null;
4486
- if (minAmount != null && amount < minAmount) return { limit: "min", value: minAmount };
4487
- if (maxAmount != null && amount > maxAmount) return { limit: "max", value: maxAmount };
4488
- return null;
4489
- }
4490
- function limitMessage(limit, value, currency) {
4491
- return `The ${limit === "min" ? "minimum" : "maximum"} amount for this route is ${value} ${currency}.`;
4492
- }
4711
+ var PAYMENT_CHECK_EVERY_MS = 1e4;
4712
+ var PAYMENT_CHECK_WINDOW_MS = 2 * 6e4;
4493
4713
  var RAMP_ERROR_MESSAGES = {
4494
- SDK_RAMPS_QUOTE_EXPIRED: "This quote expired. Request a new one and try again.",
4495
- SDK_RAMPS_ASSET_NOT_ENABLED: "This currency is not available for that route right now.",
4496
- SDK_RAMPS_KYC_REQUIRED: "The provider needs to verify your identity before continuing.",
4497
- SDK_RAMPS_WALLET_UNSUPPORTED: "This wallet type cannot be used for this ramp.",
4498
- SDK_RAMPS_PROVIDER_NOT_CONFIGURED: "This ramp provider is not configured for this app yet.",
4499
- SDK_RAMPS_ANCHOR_ERROR: "The provider rejected the request. Please try again in a moment.",
4500
- SDK_RAMPS_BRIDGE_ERROR: "The provider rejected the request. Please try again in a moment.",
4501
- SDK_RAMPS_ETHERFUSE_ERROR: "The provider rejected the request. Please try again in a moment.",
4502
- SDK_RAMPS_INSUFFICIENT_BALANCE: "This wallet does not hold enough to cover that amount. Try a smaller one.",
4503
- // Nothing moved on-chain, so the balance is untouched. Almost always no XLM
4504
- // for the network fee on a wallet the app does not sponsor.
4505
- SDK_RAMPS_ONCHAIN_SUBMIT_FAILED: "The network rejected the transaction, so nothing was sent. The wallet may need XLM for the fee."
4714
+ SDK_RAMPS_QUOTE_EXPIRED: "This quote expired. Find a route again.",
4715
+ SDK_RAMPS_QUOTE_NOT_FOUND: "This quote is no longer available. Find a route again.",
4716
+ SDK_RAMPS_ACTION_STALE: "This step changed. It was refreshed; try again.",
4717
+ SDK_RAMPS_TX_NOT_FOUND: "This operation was not found.",
4718
+ SDK_RAMPS_WALLET_NOT_READY: "Approve the new wallet account on your device, then try again."
4506
4719
  };
4507
- function rampErrorMessage(e, fallback) {
4508
- const body = e?.body;
4509
- const code = e?.code;
4510
- 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;
4514
- if ((limit === "min" || limit === "max") && typeof value === "number" && typeof currency === "string") {
4515
- return limitMessage(limit, value, currency);
4516
- }
4517
- return RAMP_ERROR_MESSAGES[code] ?? (e instanceof Error ? e.message : fallback);
4720
+ function rampErrorMessage(error, fallback) {
4721
+ const unavailable = core.appUnavailableMessage(error);
4722
+ if (unavailable) return unavailable;
4723
+ if (core.isPollarApiError(error)) {
4724
+ const known = RAMP_ERROR_MESSAGES[error.code];
4725
+ if (known) return known;
4726
+ const message = error.body.message;
4727
+ if (typeof message === "string" && message && !/^[A-Z0-9_]+$/.test(message)) return message;
4728
+ }
4729
+ return fallback;
4518
4730
  }
4519
- function explorerTxUrl2(chain, hash, network) {
4520
- if (!hash) return null;
4521
- const testnet = network === "testnet";
4522
- if (chain === "SOLANA") return `https://explorer.solana.com/tx/${hash}${testnet ? "?cluster=devnet" : ""}`;
4523
- if (chain === "POLYGON") return `https://${testnet ? "amoy." : ""}polygonscan.com/tx/${hash}`;
4524
- return `https://stellar.expert/explorer/${testnet ? "testnet" : "public"}/tx/${hash}`;
4731
+ function routeKey(quote) {
4732
+ const node = (n) => n.kind === "asset" ? `${n.code}:${n.chain}` : `${n.currency}:${n.rail}`;
4733
+ return quote.legs.map((leg) => `${leg.kind}:${node(leg.from)}>${node(leg.to)}`).join("|");
4734
+ }
4735
+ var DECIMAL = /^\d+(\.\d+)?$/;
4736
+ async function signLegWithWallet(client, orderId, actionId) {
4737
+ const lent = [];
4738
+ const lend = (chain, encoding, handler) => {
4739
+ try {
4740
+ lent.push(client.registerRampSigningHandler(chain, encoding, handler));
4741
+ } catch {
4742
+ }
4743
+ };
4744
+ lend("STELLAR", "xdr", async (action) => {
4745
+ const wallet = client.getWallet();
4746
+ if (!wallet) throw new Error("Connect the Stellar wallet this step was built for.");
4747
+ core.assertRampStellarSigner(action, wallet.address);
4748
+ const outcome = await client.signTx(action.payload.value);
4749
+ if (client.getWallet()?.address !== wallet.address)
4750
+ throw new Error("The connected wallet changed. Refresh before signing.");
4751
+ if (outcome.status === "signed") return outcome.signedXdr;
4752
+ throw new Error(
4753
+ outcome.status === "error" ? outcome.message ?? outcome.details ?? "Signing was cancelled." : "Signing was cancelled."
4754
+ );
4755
+ });
4756
+ lend("SOLANA", "solana-v0-json", async (action) => {
4757
+ const payload = JSON.parse(action.payload.value);
4758
+ const solanaWallet = () => client.getWallets().find((w) => w.chain === "SOLANA" && w.custody === "external");
4759
+ const wallet = solanaWallet();
4760
+ if (!wallet || payload.from !== wallet.address || !payload.unsignedTransaction)
4761
+ throw new Error("This step must be signed by the Solana wallet it was built for. Reconnect that wallet.");
4762
+ const signed = await client.signSolanaTransaction(payload.unsignedTransaction);
4763
+ if (solanaWallet()?.address !== wallet.address) throw new Error("The connected wallet changed. Refresh before signing.");
4764
+ return signed;
4765
+ });
4766
+ try {
4767
+ return await client.signRampLeg(orderId, actionId);
4768
+ } finally {
4769
+ for (const release of lent) release();
4770
+ }
4525
4771
  }
4526
4772
  function RampWidget({ onClose }) {
4527
- const { getClient, signTx, wallet, styles, network } = usePollar();
4528
- const walletAddress = wallet?.address ?? "";
4773
+ const {
4774
+ getClient,
4775
+ styles,
4776
+ network,
4777
+ openCosignModal,
4778
+ setCosignNextStep,
4779
+ openRecoveryModal,
4780
+ recovery,
4781
+ recoveryLocked,
4782
+ appConfig,
4783
+ cosign,
4784
+ refreshWalletBalance
4785
+ } = usePollar();
4529
4786
  const client = getClient();
4530
4787
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
4531
4788
  const [step, setStep] = react.useState("input");
4532
- const [direction, setDirection] = react.useState("onramp");
4533
- const [amount, setAmount] = react.useState("");
4534
- const [currency, setCurrency] = react.useState("");
4789
+ const [direction, setDirection] = react.useState("ONRAMP");
4790
+ const [routes, setRoutes] = react.useState([]);
4791
+ const [routesLoading, setRoutesLoading] = react.useState(true);
4535
4792
  const [country, setCountry] = react.useState("");
4536
- const [fieldValues, setFieldValues] = react.useState({});
4537
- const setFieldValue = (key, value) => setFieldValues((v) => ({ ...v, [key]: value }));
4538
- const [countries, setCountries] = react.useState([]);
4539
- const [countriesLoading, setCountriesLoading] = react.useState(true);
4540
- const [refreshing, setRefreshing] = react.useState(false);
4793
+ const [fiatChoice, setFiatChoice] = react.useState("");
4794
+ const [assetChoice, setAssetChoice] = react.useState("");
4795
+ const [amount, setAmount] = react.useState("");
4541
4796
  const [quotes, setQuotes] = react.useState([]);
4542
- const [requirementsRequired, setRequirementsRequired] = react.useState([]);
4543
- const [selectedQuote, setSelectedQuote] = react.useState(null);
4797
+ const [rejected, setRejected] = react.useState([]);
4798
+ const [selected, setSelected] = react.useState(null);
4799
+ const [startingQuoteId, setStartingQuoteId] = react.useState(null);
4800
+ const [fieldValues, setFieldValues] = react.useState({});
4801
+ const [order, setOrder] = react.useState(null);
4802
+ const [pendingOrders, setPendingOrders] = react.useState([]);
4544
4803
  const [isLoading, setIsLoading] = react.useState(false);
4545
- const [pendingKyc, setPendingKyc] = react.useState(null);
4546
- const [noticeMsg, setNoticeMsg] = react.useState(null);
4547
- const kycAttempt = react.useRef(null);
4548
- react.useEffect(
4549
- () => () => {
4550
- kycAttempt.current = null;
4551
- },
4552
- []
4553
- );
4554
- const [txId, setTxId] = react.useState(null);
4555
- const [provider, setProvider] = react.useState("");
4556
- const [kycUrl, setKycUrl] = react.useState(null);
4557
- const [tosUrl, setTosUrl] = react.useState(null);
4558
- const [kycPending, setKycPending] = react.useState(false);
4559
- const [kycApproved, setKycApproved] = react.useState(false);
4560
- const [onboardingStatus, setOnboardingStatus] = react.useState(void 0);
4561
- const [txStatus, setTxStatus] = react.useState(null);
4562
- const [stellarTxHash, setStellarTxHash] = react.useState(null);
4563
- const [txHash, setTxHash] = react.useState(null);
4564
- const [txChain, setTxChain] = react.useState(null);
4565
- const [fundsInTransit, setFundsInTransit] = react.useState(false);
4566
- const [retrying, setRetrying] = react.useState(false);
4567
- const [depositInstructions, setDepositInstructions] = react.useState(null);
4568
- const [completing, setCompleting] = react.useState(false);
4804
+ const [refreshing, setRefreshing] = react.useState(false);
4805
+ const [signing, setSigning] = react.useState(false);
4569
4806
  const [errorMsg, setErrorMsg] = react.useState(null);
4570
- const signingRef = react.useRef(false);
4571
- const directionRef = react.useRef(direction);
4572
- directionRef.current = direction;
4573
- function applySettlement(tx) {
4574
- if (tx.stellarTxHash) setStellarTxHash(tx.stellarTxHash);
4575
- if (tx.txHash) setTxHash(tx.txHash);
4576
- if (tx.chain) setTxChain(tx.chain);
4577
- setFundsInTransit(tx.fundsInTransit === true);
4578
- }
4579
- async function handleRetryTransit() {
4580
- if (!txId || retrying) return;
4581
- setRetrying(true);
4582
- setErrorMsg(null);
4583
- try {
4584
- const tx = await client.retryRampTransaction(txId);
4585
- setTxStatus(tx.status);
4586
- applySettlement(tx);
4587
- } catch (e) {
4588
- setErrorMsg(e instanceof Error && e.message ? e.message : "Could not retry the transfer. Try again in a moment.");
4589
- } finally {
4590
- setRetrying(false);
4591
- }
4592
- }
4593
- react.useEffect(() => {
4594
- if (step !== "status" || !txId) return;
4595
- if (txStatus && TERMINAL.includes(txStatus)) return;
4596
- let active = true;
4597
- const id = setInterval(async () => {
4598
- try {
4599
- const tx = await client.getRampTransaction(txId);
4600
- if (!active) return;
4601
- setTxStatus(tx.status);
4602
- 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
- }
4610
- if (tx.kycUrl) setKycUrl(tx.kycUrl);
4611
- if (tx.depositInstructions) setDepositInstructions(tx.depositInstructions);
4612
- if (TERMINAL.includes(tx.status)) clearInterval(id);
4613
- } catch {
4614
- }
4615
- }, 5e3);
4616
- return () => {
4617
- active = false;
4618
- clearInterval(id);
4619
- };
4620
- }, [step, txId, txStatus, client]);
4807
+ const [verifying, setVerifying] = react.useState(null);
4808
+ const [trustingAsset, setTrustingAsset] = react.useState(null);
4809
+ const [walletBusy, setWalletBusy] = react.useState(null);
4810
+ const [retrying, setRetrying] = react.useState(false);
4811
+ const [awaitingAccount, setAwaitingAccount] = react.useState(null);
4812
+ const [pollTrouble, setPollTrouble] = react.useState(false);
4813
+ const [pollKey, setPollKey] = react.useState(0);
4814
+ const [paymentCheckUntil, setPaymentCheckUntil] = react.useState(null);
4815
+ const promptedApproval = react.useRef(null);
4816
+ const custodyChoice = appConfig.application?.walletCustodyMode === "USER_CHOICE";
4817
+ const appChains = appConfig.application?.chains;
4818
+ const verifyAttempt = react.useRef(null);
4819
+ const countries = react.useMemo(() => core.rampCountries(routes), [routes]);
4820
+ const fiats = countries.find((c) => c.country === country)?.fiats ?? [];
4821
+ const fiat = fiats.find((f) => fiatKey(f) === fiatChoice) ?? fiats[0] ?? null;
4822
+ const assets = react.useMemo(() => {
4823
+ const seen = /* @__PURE__ */ new Map();
4824
+ for (const route of routes)
4825
+ if (fiat && route.fiat.country === country && fiatKey(route.fiat) === fiatKey(fiat))
4826
+ seen.set(assetKey3(route.asset), route.asset);
4827
+ return [...seen.values()];
4828
+ }, [routes, country, fiat]);
4829
+ const asset = assets.find((a) => assetKey3(a) === assetChoice) ?? assets[0] ?? null;
4830
+ const fields = selected ? core.rampQuoteFields(selected) : [];
4831
+ const sellAvailable = useWalletAvailable(
4832
+ direction === "OFFRAMP" && asset ? { chain: asset.chain, code: asset.code, issuer: asset.identifier } : null
4833
+ );
4621
4834
  react.useEffect(() => {
4622
- if (step !== "status" || !kycPending || kycApproved) return;
4623
- if (onboardingStatus === "awaiting_provider") return;
4624
- let active = true;
4625
- const check = async () => {
4626
- try {
4627
- const { hasApproved } = await client.getRampKycStatus();
4628
- if (active && hasApproved) setKycApproved(true);
4629
- } catch {
4630
- }
4631
- };
4632
- void check();
4633
- const id = setInterval(check, 1e4);
4634
- return () => {
4635
- active = false;
4636
- clearInterval(id);
4637
- };
4638
- }, [step, kycPending, kycApproved, onboardingStatus, client]);
4639
- async function loadCountries(resetSelection) {
4640
- setCountriesLoading(true);
4835
+ if (direction === "OFFRAMP") void refreshWalletBalance();
4836
+ }, [direction, refreshWalletBalance]);
4837
+ async function loadRoutes(next) {
4838
+ setRoutesLoading(true);
4641
4839
  try {
4642
- const { countries: list } = await client.getRampCountries();
4643
- setCountries(list);
4644
- const first = list[0];
4645
- const stillValid = list.some((c) => c.code === country);
4646
- if (first && (resetSelection || !stillValid)) {
4647
- setCountry(first.code);
4648
- if (first.currency) setCurrency(first.currency);
4649
- }
4650
- } catch {
4651
- setCountries([]);
4840
+ const { routes: found } = await client.getRampRoutes(next);
4841
+ setRoutes(found);
4842
+ const first = core.rampCountries(found)[0]?.country ?? "";
4843
+ setCountry((current) => found.some((r) => r.fiat.country === current) ? current : first);
4844
+ } catch (error) {
4845
+ setRoutes([]);
4846
+ setErrorMsg(rampErrorMessage(error, "Could not load the ramp routes."));
4652
4847
  } finally {
4653
- setCountriesLoading(false);
4848
+ setRoutesLoading(false);
4654
4849
  }
4655
4850
  }
4656
- react.useEffect(() => {
4657
- loadCountries(true);
4658
- }, []);
4659
- function handleCountryChange(code) {
4660
- setCountry(code);
4661
- const match = countries.find((c) => c.code === code);
4662
- if (match?.currency) setCurrency(match.currency);
4663
- }
4664
- async function handleRefresh() {
4665
- if (refreshing) return;
4666
- setRefreshing(true);
4851
+ async function loadPendingOrders() {
4667
4852
  try {
4668
- await loadCountries(false);
4669
- if (step === "select_route") {
4670
- const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
4671
- setQuotes(result.quotes ?? []);
4672
- setRequirementsRequired(result.requirementsRequired ?? []);
4673
- } else if (step === "status" && txId) {
4674
- const tx = await client.getRampTransaction(txId);
4675
- setTxStatus(tx.status);
4676
- applySettlement(tx);
4677
- if (tx.kycUrl) setKycUrl(tx.kycUrl);
4678
- if (tx.depositInstructions) setDepositInstructions(tx.depositInstructions);
4679
- }
4853
+ const page = await client.listRampOrders({ open: "true", limit: 5 });
4854
+ setPendingOrders(page.orders);
4680
4855
  } catch {
4681
- } finally {
4682
- setRefreshing(false);
4856
+ setPendingOrders([]);
4683
4857
  }
4684
4858
  }
4685
- function resetToInput({ keepMessage = false } = {}) {
4686
- setStep("input");
4687
- setQuotes([]);
4688
- setRequirementsRequired([]);
4689
- setSelectedQuote(null);
4690
- setFieldValues({});
4691
- setTxId(null);
4692
- setProvider("");
4693
- setKycUrl(null);
4694
- setTosUrl(null);
4695
- setTxStatus(null);
4696
- setStellarTxHash(null);
4697
- setTxHash(null);
4698
- setTxChain(null);
4699
- setFundsInTransit(false);
4700
- setDepositInstructions(null);
4701
- setKycPending(false);
4702
- setKycApproved(false);
4703
- setNoticeMsg(null);
4704
- if (!keepMessage) setErrorMsg(null);
4705
- }
4706
- async function resumeWithSignature(id, ps) {
4707
- const outcome = await signTx(ps.unsignedXdr);
4708
- if (outcome.status === "cosign_pending") {
4709
- setErrorMsg("Approve the signature on your device, then resume.");
4710
- setStep("error");
4711
- return;
4712
- }
4713
- if (outcome.status !== "signed") {
4714
- setErrorMsg(outcome.message ?? outcome.details ?? "Signing was cancelled.");
4715
- setStep("error");
4716
- return;
4717
- }
4718
- const result = await client.submitRampSignature(id, {
4719
- signedXdr: outcome.signedXdr,
4720
- // The CCTP actions are not in the generated schema until it is regenerated.
4721
- action: ps.action
4859
+ react.useEffect(() => {
4860
+ void loadRoutes(direction);
4861
+ }, [client, direction]);
4862
+ react.useEffect(() => {
4863
+ void loadPendingOrders();
4864
+ }, [client]);
4865
+ const orderId = order?.id ?? null;
4866
+ react.useEffect(() => {
4867
+ if (step !== "status" || !orderId) return;
4868
+ const controller = new AbortController();
4869
+ client.pollRampOrder(orderId, {
4870
+ signal: controller.signal,
4871
+ onUpdate: (next) => {
4872
+ setOrder(next);
4873
+ setPollTrouble(false);
4874
+ },
4875
+ onError: () => setPollTrouble(true)
4876
+ }).catch((error) => {
4877
+ if (!controller.signal.aborted) setErrorMsg(rampErrorMessage(error, "This operation could not be read."));
4722
4878
  });
4723
- await applyResult(result);
4724
- }
4725
- const resumeRef = react.useRef(resumeWithSignature);
4726
- resumeRef.current = resumeWithSignature;
4727
- async function applyResult(result) {
4728
- setTxId(result.txId);
4729
- setProvider(result.provider);
4730
- if (result.pendingSignature) {
4731
- await resumeWithSignature(result.txId, result.pendingSignature);
4879
+ return () => controller.abort();
4880
+ }, [client, step, orderId, pollKey]);
4881
+ const awaitingPayment = step === "status" && order?.pendingAction?.kind === "FIAT_PAYMENT";
4882
+ react.useEffect(() => {
4883
+ if (paymentCheckUntil === null || !orderId) return;
4884
+ if (!awaitingPayment) {
4885
+ setPaymentCheckUntil(null);
4886
+ setPollKey((key) => key + 1);
4732
4887
  return;
4733
4888
  }
4734
- setKycUrl(result.kycUrl ?? null);
4735
- setTosUrl(result.tosUrl ?? null);
4736
- setOnboardingStatus(result.onboardingStatus);
4737
- setKycPending(result.kycRequired === true);
4738
- if (result.kycRequired) setKycApproved(false);
4739
- setTxStatus(result.status);
4740
- setStellarTxHash(result.stellarTxHash ?? null);
4741
- setDepositInstructions(result.depositInstructions ?? null);
4742
- setStep("status");
4743
- }
4744
- async function loadQuotes() {
4745
- const result = await client.getRampsQuote({ country, amount: Number(amount), currency, direction });
4746
- const list = result.quotes ?? [];
4747
- const locked = result.requirementsRequired ?? [];
4748
- 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
- );
4753
- setStep("error");
4754
- return false;
4889
+ const timer = setInterval(() => {
4890
+ if (Date.now() >= paymentCheckUntil) {
4891
+ setPaymentCheckUntil(null);
4892
+ return;
4893
+ }
4894
+ client.checkRampOrder(orderId).then(setOrder).catch(() => void 0);
4895
+ }, PAYMENT_CHECK_EVERY_MS);
4896
+ return () => clearInterval(timer);
4897
+ }, [client, orderId, awaitingPayment, paymentCheckUntil]);
4898
+ const approval = order?.pendingAction?.kind === "COSIGN_APPROVAL" ? order.pendingAction.actionId : null;
4899
+ react.useEffect(() => {
4900
+ if (!approval || promptedApproval.current === approval) return;
4901
+ promptedApproval.current = approval;
4902
+ void client.cosign.pending().catch(() => void 0).then(() => openCosignModal());
4903
+ }, [approval, client, openCosignModal]);
4904
+ const nextApproval = step === "status" && order ? core.rampNextApproval(order) : null;
4905
+ react.useEffect(() => {
4906
+ setCosignNextStep(nextApproval);
4907
+ }, [nextApproval, setCosignNextStep]);
4908
+ react.useEffect(() => () => setCosignNextStep(null), [setCosignNextStep]);
4909
+ const resolvedRequest = cosign.lastResolved;
4910
+ react.useEffect(() => {
4911
+ if (!awaitingAccount || resolvedRequest?.id !== awaitingAccount.requestId) return;
4912
+ setAwaitingAccount(null);
4913
+ if (resolvedRequest.status !== "APPROVED") {
4914
+ setErrorMsg("The new account was not approved, so this route cannot start.");
4915
+ return;
4755
4916
  }
4756
- setQuotes(list);
4757
- setRequirementsRequired(locked);
4758
- return true;
4917
+ void resumeRoute(awaitingAccount.routeKey);
4918
+ }, [awaitingAccount, resolvedRequest]);
4919
+ function handleDirectionChange(next) {
4920
+ setDirection(next);
4921
+ setErrorMsg(null);
4922
+ setAwaitingAccount(null);
4759
4923
  }
4760
4924
  async function handleFindRoute() {
4925
+ if (!fiat || !asset) return [];
4926
+ if (!DECIMAL.test(amount.trim()) || Number(amount) <= 0) {
4927
+ setErrorMsg("Enter an amount greater than zero.");
4928
+ return [];
4929
+ }
4761
4930
  setStep("loading_quote");
4762
- setIsLoading(true);
4763
4931
  setErrorMsg(null);
4932
+ setSelected(null);
4764
4933
  try {
4765
- if (!await loadQuotes()) return;
4934
+ const result = await client.quoteRamp({
4935
+ direction,
4936
+ fiat: { currency: fiat.currency, rail: fiat.rail, country: fiat.country },
4937
+ asset: { code: asset.code, identifier: asset.identifier, chain: asset.chain },
4938
+ amount: { value: amount.trim(), denomination: "in" }
4939
+ });
4940
+ setQuotes(result.quotes);
4941
+ setRejected(result.rejected);
4766
4942
  setStep("select_route");
4767
- } catch (e) {
4768
- setErrorMsg(rampErrorMessage(e, "Failed to fetch quotes."));
4943
+ return result.quotes;
4944
+ } catch (error) {
4945
+ if (core.isPollarApiError(error) && error.code === "SDK_RAMPS_NO_ROUTE") {
4946
+ setQuotes([]);
4947
+ setRejected(Array.isArray(error.body.rejected) ? error.body.rejected : []);
4948
+ setStep("select_route");
4949
+ return [];
4950
+ }
4951
+ setErrorMsg(rampErrorMessage(error, "Could not find a route. Please try again."));
4769
4952
  setStep("error");
4953
+ return [];
4954
+ }
4955
+ }
4956
+ function handleVerifyRoute(requirement) {
4957
+ setErrorMsg(null);
4958
+ verifyAttempt.current = requirement;
4959
+ setVerifying(requirement);
4960
+ }
4961
+ function closeVerification() {
4962
+ verifyAttempt.current = null;
4963
+ setVerifying(null);
4964
+ }
4965
+ function verificationDone(requirement) {
4966
+ if (verifyAttempt.current !== requirement) return;
4967
+ closeVerification();
4968
+ void handleFindRoute();
4969
+ }
4970
+ async function handleTrustAsset(asset2) {
4971
+ if (trustingAsset) return;
4972
+ setTrustingAsset(`${asset2.code}:${asset2.issuer}`);
4973
+ setErrorMsg(null);
4974
+ try {
4975
+ const outcome = await client.setTrustline(asset2);
4976
+ if (outcome.status === "error") {
4977
+ setErrorMsg(core.rampTrustlineErrorText(outcome, asset2.code));
4978
+ return;
4979
+ }
4980
+ if (outcome.status === "cosign_pending") {
4981
+ setErrorMsg(`Approve the ${asset2.code} trustline, then refresh the routes.`);
4982
+ openCosignModal();
4983
+ return;
4984
+ }
4985
+ await client.refreshAssets().catch(() => {
4986
+ });
4987
+ await handleFindRoute();
4988
+ } catch (error) {
4989
+ setErrorMsg(rampErrorMessage(error, `Could not add the ${asset2.code} trustline. Please try again.`));
4770
4990
  } finally {
4771
- setIsLoading(false);
4991
+ setTrustingAsset(null);
4772
4992
  }
4773
4993
  }
4774
- function handleSelectQuote(quote) {
4775
- setSelectedQuote(quote);
4994
+ async function handleWalletStep(walletStep, index, custody) {
4995
+ if (walletBusy !== null) return;
4996
+ setWalletBusy(index);
4776
4997
  setErrorMsg(null);
4777
- setNoticeMsg(null);
4778
- const broken = brokenLimitOf(Number(amount), quote);
4779
- if (broken) {
4780
- setErrorMsg(limitMessage(broken.limit, broken.value, currency));
4781
- return;
4998
+ try {
4999
+ const { recoveryRequired } = await client.runRampWalletStep(walletStep, custody);
5000
+ if (recoveryRequired) {
5001
+ setErrorMsg("Add a way to recover your wallet, then find the route again.");
5002
+ openRecoveryModal();
5003
+ return;
5004
+ }
5005
+ await handleFindRoute();
5006
+ } catch (error) {
5007
+ const code = core.isPollarApiError(error) ? error.code : null;
5008
+ if (code === "SDK_RAMPS_WALLET_NOT_READY") {
5009
+ setErrorMsg("Approve the new account, then find the route again.");
5010
+ openCosignModal();
5011
+ return;
5012
+ }
5013
+ setErrorMsg(
5014
+ code === "SDK_WALLET_PROVISIONING_DISABLED" ? "This app does not create wallets." : rampErrorMessage(error, "Could not prepare the wallet. Please try again.")
5015
+ );
5016
+ } finally {
5017
+ setWalletBusy(null);
4782
5018
  }
4783
- const fields = requiredFieldsOf(quote);
4784
- const missing = fields.some((f) => !f.optional && !(fieldValues[f.key] ?? "").trim());
4785
- if (fields.length > 0 && missing) {
4786
- setStep("contact");
5019
+ }
5020
+ async function resumeRoute(key) {
5021
+ const same = (await handleFindRoute()).find((q) => routeKey(q) === key);
5022
+ if (same) handleSelectQuote(same, true);
5023
+ else setErrorMsg("That route is no longer available. Pick another one.");
5024
+ }
5025
+ function handleSelectQuote(quote, fresh = false) {
5026
+ if (!fresh && Date.parse(quote.expiresAt) <= Date.now()) {
5027
+ void resumeRoute(routeKey(quote));
4787
5028
  return;
4788
5029
  }
4789
- void startRamp(quote);
4790
- }
4791
- function handleContactContinue() {
4792
- if (selectedQuote) void startRamp(selectedQuote);
5030
+ setSelected(quote);
5031
+ setErrorMsg(null);
5032
+ const missing = core.rampQuoteFields(quote).some((f) => !f.optional && !(fieldValues[f.key] ?? "").trim());
5033
+ if (missing) setStep("contact");
5034
+ else void startOrder(quote, fresh);
4793
5035
  }
4794
- async function startRamp(quote) {
5036
+ async function startOrder(quote, fresh = false) {
4795
5037
  setIsLoading(true);
5038
+ setStartingQuoteId(quote.quoteId);
4796
5039
  setErrorMsg(null);
5040
+ setAwaitingAccount(null);
4797
5041
  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"]);
4801
- const extraFields = {};
4802
- for (const f of requiredFieldsOf(quote)) {
4803
- const val = (fieldValues[f.key] ?? "").trim();
4804
- 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;
4807
- else extraFields[f.key] = val;
5042
+ const payee = Object.fromEntries(
5043
+ core.rampQuoteFields(quote).map((f) => [f.key, (fieldValues[f.key] ?? "").trim()]).filter(([, value]) => value)
5044
+ );
5045
+ const created = await client.createRampOrder({
5046
+ quoteId: quote.quoteId,
5047
+ ...Object.keys(payee).length ? { payee } : {}
5048
+ });
5049
+ setOrder(created);
5050
+ setStep("status");
5051
+ } catch (error) {
5052
+ if (core.isPollarApiError(error) && error.code === "SDK_RAMPS_WALLET_NOT_READY") {
5053
+ const pending = await client.cosign.pending().catch(() => []);
5054
+ const request = pending.find((r) => r.intent === "add_accounts");
5055
+ if (request) setAwaitingAccount({ routeKey: routeKey(quote), requestId: request.id });
5056
+ else setErrorMsg(rampErrorMessage(error, "Could not start the operation. Please try again."));
5057
+ openCosignModal();
5058
+ setStep("select_route");
5059
+ return;
4808
5060
  }
4809
- if (Object.keys(extraFields).length > 0) base.fields = extraFields;
4810
- const result = direction === "onramp" ? await client.createOnRamp(base) : await client.createOffRamp(base);
4811
- await applyResult(result);
4812
- } catch (e) {
4813
- const requirement = requiredRampKyc(e);
4814
- if (requirement) {
4815
- setStep(requiredFieldsOf(quote).length ? "contact" : "select_route");
4816
- setErrorMsg(
4817
- requirement.type === "KYC" ? "Identity verification is required before continuing." : "A few more details are required before continuing."
4818
- );
4819
- kycAttempt.current = requirement;
4820
- setPendingKyc(requirement);
5061
+ if (core.isPollarApiError(error) && error.code === "SDK_RAMPS_QUOTE_EXPIRED" && !fresh) {
5062
+ void resumeRoute(routeKey(quote));
4821
5063
  return;
4822
5064
  }
4823
- setErrorMsg(rampErrorMessage(e, "Failed to start the ramp."));
4824
- setStep("error");
5065
+ setErrorMsg(rampErrorMessage(error, "Could not start the operation. Please try again."));
5066
+ if (core.isPollarApiError(error) && error.code === "VALIDATION_ERROR" && core.rampQuoteFields(quote).length) setStep("contact");
5067
+ else setStep("select_route");
4825
5068
  } finally {
4826
5069
  setIsLoading(false);
5070
+ setStartingQuoteId(null);
4827
5071
  }
4828
5072
  }
4829
- async function requoteAfterKyc(completed = "KYC") {
4830
- setStep("loading_quote");
5073
+ async function handleSign() {
5074
+ const action = order?.pendingAction;
5075
+ if (!order || action?.kind !== "SIGNATURE" || signing) return;
5076
+ setSigning(true);
5077
+ setErrorMsg(null);
5078
+ try {
5079
+ setOrder(await signLegWithWallet(client, order.id, action.actionId));
5080
+ } catch (error) {
5081
+ setErrorMsg(rampErrorMessage(error, error instanceof Error && error.message ? error.message : "Signing was cancelled."));
5082
+ setOrder(await client.getRampOrder(order.id).catch(() => order));
5083
+ } finally {
5084
+ setSigning(false);
5085
+ }
5086
+ }
5087
+ async function handleResumeOrder(id) {
4831
5088
  setIsLoading(true);
4832
5089
  setErrorMsg(null);
4833
- setSelectedQuote(null);
4834
5090
  try {
4835
- if (!await loadQuotes()) return;
4836
- setNoticeMsg(
4837
- completed === "KYC" ? "Your identity is verified. Prices may have changed, so choose a route to continue." : "Thanks, your details are saved. Prices may have changed, so choose a route to continue."
4838
- );
4839
- setStep("select_route");
4840
- } catch (e) {
4841
- setErrorMsg(rampErrorMessage(e, "Failed to fetch quotes."));
4842
- setStep("error");
5091
+ const found = await client.getRampOrder(id);
5092
+ setDirection(found.direction);
5093
+ setOrder(found);
5094
+ setStep("status");
5095
+ } catch (error) {
5096
+ setErrorMsg(rampErrorMessage(error, "Could not resume the operation."));
4843
5097
  } finally {
4844
5098
  setIsLoading(false);
4845
5099
  }
4846
5100
  }
4847
- function handleVerifyRoute(requirement) {
4848
- const attempt = pendingFromQuote(requirement);
5101
+ function handleNewOrder() {
5102
+ setAwaitingAccount(null);
5103
+ setOrder(null);
5104
+ setSelected(null);
5105
+ setQuotes([]);
5106
+ setRejected([]);
4849
5107
  setErrorMsg(null);
4850
- setNoticeMsg(null);
4851
- kycAttempt.current = attempt;
4852
- setPendingKyc(attempt);
5108
+ setStep("input");
5109
+ void loadPendingOrders();
4853
5110
  }
4854
- function handleOpenKyc() {
4855
- if (kycUrl) window.open(kycUrl, "_blank", "noopener,noreferrer");
5111
+ function handleBack() {
5112
+ setErrorMsg(null);
5113
+ setAwaitingAccount(null);
5114
+ setStep(step === "contact" ? "select_route" : "input");
4856
5115
  }
4857
- function handleOpenTos() {
4858
- if (tosUrl) window.open(tosUrl, "_blank", "noopener,noreferrer");
5116
+ function handleStepSelect(target) {
5117
+ setErrorMsg(null);
5118
+ setAwaitingAccount(null);
5119
+ setStep(target === "Route" ? "select_route" : "input");
4859
5120
  }
4860
- async function handleCompleteWithdraw() {
4861
- if (!txId) return;
4862
- setCompleting(true);
5121
+ async function handleCheckPayment() {
5122
+ if (!order || paymentCheckUntil !== null) return;
4863
5123
  setErrorMsg(null);
5124
+ setPaymentCheckUntil(Date.now() + PAYMENT_CHECK_WINDOW_MS);
4864
5125
  try {
4865
- const result = await client.completeWithdraw(txId);
4866
- if (result.pendingSignature) {
4867
- await resumeWithSignature(txId, result.pendingSignature);
4868
- return;
4869
- }
4870
- setTxStatus(result.status);
4871
- setStellarTxHash(result.stellarTxHash ?? null);
4872
- } catch (e) {
4873
- const msg = e instanceof Error ? e.message : "";
4874
- setErrorMsg(
4875
- msg.includes("KYC") ? "Finish KYC at the provider first, then try again." : msg || "Failed to complete the withdrawal."
4876
- );
5126
+ setOrder(await client.checkRampOrder(order.id));
5127
+ } catch (error) {
5128
+ setPaymentCheckUntil(null);
5129
+ setErrorMsg(rampErrorMessage(error, "Could not check the payment. Please try again."));
5130
+ }
5131
+ }
5132
+ async function handleRetryOrder() {
5133
+ if (!order || retrying) return;
5134
+ setRetrying(true);
5135
+ setErrorMsg(null);
5136
+ try {
5137
+ setOrder(await client.retryRampOrder(order.id));
5138
+ setPollKey((key) => key + 1);
5139
+ } catch (error) {
5140
+ setErrorMsg(rampErrorMessage(error, "Could not try again. Please try again."));
4877
5141
  } finally {
4878
- setCompleting(false);
5142
+ setRetrying(false);
4879
5143
  }
4880
5144
  }
4881
- const kycBlocking = kycPending && !kycApproved;
4882
- const canComplete = direction === "offramp" && step === "status" && txStatus !== "completed" && !stellarTxHash && !txHash && !kycPending;
4883
- const flowSteps = flowStepsOf(quotes, selectedQuote);
4884
- const flowStepIndex = flowSteps.indexOf(STEP_LABEL[step] ?? "");
4885
- if (pendingKyc?.type === "FORM") {
4886
- return /* @__PURE__ */ jsxRuntime.jsx(
4887
- RequirementFormModal,
4888
- {
4889
- formId: pendingKyc.optionId,
4890
- ...pendingKyc.progress ? { progress: pendingKyc.progress } : {},
4891
- onClose: () => {
4892
- kycAttempt.current = null;
4893
- setPendingKyc(null);
4894
- },
4895
- onSubmitted: () => {
4896
- if (kycAttempt.current !== pendingKyc) return;
4897
- kycAttempt.current = null;
4898
- setPendingKyc(null);
4899
- void requoteAfterKyc("FORM");
4900
- }
5145
+ const heldForRecovery = step === "status" && order?.failure?.resolutionAction === "set_up_recovery";
5146
+ const wasRecoveryLocked = react.useRef(recoveryLocked);
5147
+ react.useEffect(() => {
5148
+ if (heldForRecovery && wasRecoveryLocked.current && !recoveryLocked && recovery) void handleRetryOrder();
5149
+ wasRecoveryLocked.current = recoveryLocked;
5150
+ }, [heldForRecovery, recoveryLocked, recovery]);
5151
+ async function handleRefresh() {
5152
+ if (refreshing) return;
5153
+ setRefreshing(true);
5154
+ try {
5155
+ if (step === "status" && order) {
5156
+ setOrder(await client.getRampOrder(order.id));
5157
+ setPollKey((key) => key + 1);
5158
+ } else if (step === "select_route") await handleFindRoute();
5159
+ else {
5160
+ await loadRoutes(direction);
5161
+ await loadPendingOrders();
5162
+ if (direction === "OFFRAMP") await refreshWalletBalance();
4901
5163
  }
4902
- );
4903
- }
4904
- if (pendingKyc?.type === "REGISTRY_CHECK" || pendingKyc?.type === "PROVIDER_REGISTRATION") {
4905
- const close = () => {
4906
- kycAttempt.current = null;
4907
- setPendingKyc(null);
4908
- };
4909
- const done = () => {
4910
- if (kycAttempt.current !== pendingKyc) return;
4911
- close();
4912
- void requoteAfterKyc(pendingKyc.type);
4913
- };
4914
- const progress = pendingKyc.progress ? { progress: pendingKyc.progress } : {};
4915
- return pendingKyc.type === "REGISTRY_CHECK" ? /* @__PURE__ */ jsxRuntime.jsx(RegistryCheckModal, { optionId: pendingKyc.optionId, ...progress, onClose: close, onApproved: done }) : /* @__PURE__ */ jsxRuntime.jsx(ProviderRegistrationModal, { corridorId: pendingKyc.corridorId, ...progress, onClose: close, onRegistered: done });
5164
+ } catch (error) {
5165
+ setErrorMsg(rampErrorMessage(error, "Could not refresh."));
5166
+ } finally {
5167
+ setRefreshing(false);
5168
+ }
4916
5169
  }
4917
- if (pendingKyc) {
5170
+ const flowSteps = fields.length || quotes.some((q) => core.rampQuoteFields(q).length) ? ["Amount", "Route", "Details", "Complete"] : ["Amount", "Route", "Complete"];
5171
+ const stepLabel = {
5172
+ input: "Amount",
5173
+ loading_quote: "Amount",
5174
+ select_route: "Route",
5175
+ contact: "Details",
5176
+ status: "Complete"
5177
+ };
5178
+ const label = stepLabel[step];
5179
+ if (verifying) {
5180
+ const progress = { progress: { position: verifying.completed + 1, total: verifying.total } };
5181
+ const done = () => verificationDone(verifying);
5182
+ if (verifying.type === "FORM")
5183
+ return /* @__PURE__ */ jsxRuntime.jsx(RequirementFormModal, { formId: verifying.optionId, ...progress, onClose: closeVerification, onSubmitted: done });
5184
+ if (verifying.type === "REGISTRY_CHECK")
5185
+ return /* @__PURE__ */ jsxRuntime.jsx(RegistryCheckModal, { optionId: verifying.optionId, ...progress, onClose: closeVerification, onApproved: done });
5186
+ if (verifying.type === "PROVIDER_REGISTRATION")
5187
+ return /* @__PURE__ */ jsxRuntime.jsx(
5188
+ ProviderRegistrationModal,
5189
+ {
5190
+ corridorId: verifying.corridorId,
5191
+ ...progress,
5192
+ onClose: closeVerification,
5193
+ onRegistered: done
5194
+ }
5195
+ );
4918
5196
  return /* @__PURE__ */ jsxRuntime.jsx(
4919
5197
  KycModal,
4920
5198
  {
4921
5199
  country,
4922
- corridorId: pendingKyc.corridorId,
4923
- providerId: pendingKyc.optionId,
4924
- onClose: () => {
4925
- kycAttempt.current = null;
4926
- setPendingKyc(null);
4927
- },
4928
- onApproved: () => {
4929
- if (kycAttempt.current !== pendingKyc) return;
4930
- kycAttempt.current = null;
4931
- setPendingKyc(null);
4932
- void requoteAfterKyc();
4933
- }
5200
+ corridorId: verifying.corridorId,
5201
+ providerId: verifying.optionId,
5202
+ onClose: closeVerification,
5203
+ onApproved: done
4934
5204
  }
4935
5205
  );
4936
5206
  }
@@ -4939,59 +5209,79 @@ function RampWidget({ onClose }) {
4939
5209
  {
4940
5210
  theme,
4941
5211
  accentColor,
4942
- styleOverrides,
5212
+ ...styleOverrides ? { styleOverrides } : {},
5213
+ network,
4943
5214
  step,
4944
5215
  flowSteps,
4945
- flowStepIndex,
4946
- startingQuoteId: isLoading && selectedQuote ? selectedQuote.quoteId : null,
5216
+ flowStepIndex: label ? flowSteps.indexOf(label) : -1,
4947
5217
  direction,
4948
- amount,
4949
- currency,
4950
- country,
4951
- requiredFields: selectedQuote ? requiredFieldsOf(selectedQuote) : [],
4952
- fieldValues,
5218
+ routesLoading,
4953
5219
  countries,
4954
- countriesLoading,
4955
- refreshing,
5220
+ country,
5221
+ fiats,
5222
+ fiat,
5223
+ assets,
5224
+ asset,
5225
+ amount,
5226
+ sellAvailable,
4956
5227
  quotes,
4957
- kycRequired: requirementsRequired,
5228
+ rejected,
5229
+ startingQuoteId,
5230
+ fields,
5231
+ fieldValues,
5232
+ order,
5233
+ pendingOrders,
4958
5234
  isLoading,
4959
- provider,
4960
- txStatus,
4961
- kycUrl,
4962
- tosUrl,
4963
- kycBlocking,
4964
- onboardingStatus: onboardingStatus ?? null,
4965
- kycJustApproved: kycPending && kycApproved,
4966
- stellarTxHash,
4967
- txHash,
4968
- txChain,
4969
- explorerUrl: explorerTxUrl2(txChain ?? "STELLAR", txHash ?? stellarTxHash, network),
4970
- fundsInTransit,
4971
- retrying,
4972
- onRetryTransit: handleRetryTransit,
4973
- depositInstructions,
4974
- canComplete,
4975
- completing,
5235
+ refreshing,
5236
+ signing,
4976
5237
  errorMsg,
4977
- noticeMsg,
4978
- onDirectionChange: setDirection,
4979
- onAmountChange: (next) => {
4980
- setAmount(next);
5238
+ notice: awaitingAccount ? "Approve the new account on your device. The order starts once it is approved." : null,
5239
+ onDirectionChange: handleDirectionChange,
5240
+ onCountryChange: (next) => {
5241
+ setCountry(next);
5242
+ setFiatChoice("");
5243
+ setAssetChoice("");
5244
+ },
5245
+ onFiatChange: (key) => {
5246
+ setFiatChoice(key);
5247
+ setAssetChoice("");
5248
+ },
5249
+ onAssetChange: setAssetChoice,
5250
+ onAmountChange: (value) => {
5251
+ setAmount(value);
4981
5252
  setErrorMsg(null);
4982
5253
  },
4983
- onFieldChange: setFieldValue,
4984
- onCountryChange: handleCountryChange,
4985
- onFindRoute: handleFindRoute,
4986
- onSelectQuote: handleSelectQuote,
5254
+ onFindRoute: () => void handleFindRoute(),
5255
+ onSelectQuote: (quote) => handleSelectQuote(quote),
4987
5256
  onVerifyRoute: handleVerifyRoute,
4988
- onContactContinue: handleContactContinue,
4989
- onOpenKyc: handleOpenKyc,
4990
- onOpenTos: handleOpenTos,
4991
- onCompleteWithdraw: handleCompleteWithdraw,
4992
- onBack: () => resetToInput({ keepMessage: true }),
4993
- onRetry: () => resetToInput(),
4994
- onRefresh: handleRefresh,
5257
+ trustingAsset,
5258
+ onTrustAsset: (asset2) => void handleTrustAsset(asset2),
5259
+ walletBusy,
5260
+ custodyChoice,
5261
+ onWalletStep: (walletStep, index, custody) => void handleWalletStep(walletStep, index, custody),
5262
+ noChains: Array.isArray(appChains) && appChains.length === 0,
5263
+ onFieldChange: (key, value) => setFieldValues((current) => ({ ...current, [key]: value })),
5264
+ onContactContinue: () => {
5265
+ if (selected) void startOrder(selected);
5266
+ },
5267
+ onSign: () => void handleSign(),
5268
+ onApprove: openCosignModal,
5269
+ retrying,
5270
+ onRetryOrder: () => void handleRetryOrder(),
5271
+ onSetUpRecovery: openRecoveryModal,
5272
+ recoveryMissing: recovery === null ? null : recoveryLocked,
5273
+ pollTrouble,
5274
+ checkingPayment: paymentCheckUntil !== null,
5275
+ onCheckPayment: () => void handleCheckPayment(),
5276
+ onResumeOrder: (id) => void handleResumeOrder(id),
5277
+ onNewOrder: handleNewOrder,
5278
+ onBack: handleBack,
5279
+ onStepSelect: handleStepSelect,
5280
+ onRetry: () => {
5281
+ setErrorMsg(null);
5282
+ setStep("input");
5283
+ },
5284
+ onRefresh: () => void handleRefresh(),
4995
5285
  onClose
4996
5286
  }
4997
5287
  ) });
@@ -5091,6 +5381,8 @@ function ReceiveModalTemplate({
5091
5381
  onSelectChain,
5092
5382
  copied,
5093
5383
  notReadyReason,
5384
+ recoveryLocked,
5385
+ onSetupRecovery,
5094
5386
  onCopy,
5095
5387
  onClose
5096
5388
  }) {
@@ -5109,7 +5401,10 @@ function ReceiveModalTemplate({
5109
5401
  /* @__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
5402
  ] }),
5111
5403
  chains && onSelectChain && /* @__PURE__ */ jsxRuntime.jsx(ChainSelect, { value: selectedChain, options: chains, onChange: onSelectChain }),
5112
- walletAddress ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5404
+ recoveryLocked ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-receive-locked", role: "alert", children: [
5405
+ /* @__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." }),
5406
+ onSetupRecovery && /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-receive-copy-btn", onClick: onSetupRecovery, children: "Set up recovery" })
5407
+ ] }) : walletAddress ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
5113
5408
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-receive-qr", children: [
5114
5409
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-receive-network-badge", children: [
5115
5410
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-receive-network-dot", "aria-hidden": true }),
@@ -5183,7 +5478,7 @@ function ReceiveModalTemplate({
5183
5478
  );
5184
5479
  }
5185
5480
  function ReceiveModal({ onClose }) {
5186
- const { wallet, wallets, styles } = usePollar();
5481
+ const { wallet, wallets, styles, recoveryLocked, openRecoveryModal } = usePollar();
5187
5482
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
5188
5483
  const [copied, setCopied] = react.useState(false);
5189
5484
  const copyTimerRef = react.useRef(null);
@@ -5225,6 +5520,11 @@ function ReceiveModal({ onClose }) {
5225
5520
  setSelectedChain(chain);
5226
5521
  },
5227
5522
  notReadyReason,
5523
+ recoveryLocked,
5524
+ onSetupRecovery: () => {
5525
+ onClose();
5526
+ openRecoveryModal();
5527
+ },
5228
5528
  copied,
5229
5529
  onCopy: handleCopy,
5230
5530
  onClose
@@ -5440,7 +5740,7 @@ function formatBalance(balance) {
5440
5740
  const n = parseFloat(balance);
5441
5741
  return isNaN(n) ? balance : n.toLocaleString(void 0, { maximumFractionDigits: 7 });
5442
5742
  }
5443
- function assetKey3(record) {
5743
+ function assetKey4(record) {
5444
5744
  return `${record.code}:${record.issuer ?? "native"}`;
5445
5745
  }
5446
5746
  function chainLabel(chain) {
@@ -5511,8 +5811,8 @@ function SendAssetSelector({ assets, selectedAsset, loading, onSelectAsset }) {
5511
5811
  }
5512
5812
  ),
5513
5813
  open && assets.length > 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-send-asset-menu", role: "listbox", "aria-label": "Assets", children: assets.map((asset) => {
5514
- const key = assetKey3(asset);
5515
- const selected = selectedAsset ? key === assetKey3(selectedAsset) : false;
5814
+ const key = assetKey4(asset);
5815
+ const selected = selectedAsset ? key === assetKey4(selectedAsset) : false;
5516
5816
  return /* @__PURE__ */ jsxRuntime.jsxs(
5517
5817
  "button",
5518
5818
  {
@@ -5556,6 +5856,9 @@ function SendModalTemplate({
5556
5856
  destination,
5557
5857
  formError,
5558
5858
  isLoadingBalance,
5859
+ isCheckingDestination = false,
5860
+ minStartingBalance = 1,
5861
+ onCreateAccount,
5559
5862
  transaction,
5560
5863
  showXdr,
5561
5864
  copied,
@@ -5585,8 +5888,10 @@ function SendModalTemplate({
5585
5888
  const cssVars = buildModalCssVars(theme, accentColor, styleOverrides);
5586
5889
  const parsedAmount = amount ? Number(amount) : 0;
5587
5890
  const availableAmount = selectedAsset?.available === null || selectedAsset?.available === void 0 ? 0 : Number(selectedAsset.available);
5588
- const canSubmit = canSendOnChain && !notReadyReason && !!selectedAsset && Number.isFinite(parsedAmount) && parsedAmount > 0 && parsedAmount <= availableAmount && !!destination.trim() && !isLoadingBalance;
5589
- const title = step === "form" ? "Send" : txTitle;
5891
+ const canSubmit = canSendOnChain && !notReadyReason && !!selectedAsset && Number.isFinite(parsedAmount) && parsedAmount > 0 && parsedAmount <= availableAmount && !!destination.trim() && !isLoadingBalance && !isCheckingDestination;
5892
+ const title = step === "form" ? "Send" : step === "create-account" ? "Create account?" : txTitle;
5893
+ const createsWithNative = selectedAsset?.type === "native";
5894
+ const meetsMinimum = Number.isFinite(parsedAmount) && parsedAmount >= minStartingBalance;
5590
5895
  return /* @__PURE__ */ jsxRuntime.jsxs(
5591
5896
  "div",
5592
5897
  {
@@ -5636,51 +5941,25 @@ function SendModalTemplate({
5636
5941
  onSelectAsset
5637
5942
  }
5638
5943
  ),
5944
+ /* @__PURE__ */ jsxRuntime.jsx(
5945
+ AmountField,
5946
+ {
5947
+ id: "pollar-send-amount",
5948
+ label: "Amount",
5949
+ value: amount,
5950
+ onChange: onAmountChange,
5951
+ unit: selectedAsset?.code,
5952
+ available: selectedAsset ? selectedAsset.available : void 0,
5953
+ onMax
5954
+ }
5955
+ ),
5639
5956
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-field", children: [
5640
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-label-row", children: [
5641
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-send-label", children: "Amount" }),
5642
- selectedAsset && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-send-hint", children: [
5643
- "Available: ",
5644
- formatBalance(selectedAsset.available),
5645
- " ",
5646
- selectedAsset.code
5647
- ] })
5648
- ] }),
5957
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-send-label", children: "Destination wallet" }),
5649
5958
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-input-shell", children: [
5650
5959
  /* @__PURE__ */ jsxRuntime.jsx(
5651
5960
  "input",
5652
5961
  {
5653
- className: "pollar-input pollar-send-amount-input",
5654
- type: "text",
5655
- inputMode: "decimal",
5656
- placeholder: "0.00",
5657
- value: amount,
5658
- onChange: (e) => onAmountChange(e.target.value),
5659
- "aria-label": "Amount"
5660
- }
5661
- ),
5662
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-input-suffix", children: [
5663
- /* @__PURE__ */ jsxRuntime.jsx(
5664
- "button",
5665
- {
5666
- type: "button",
5667
- className: "pollar-send-inline-action",
5668
- onClick: onMax,
5669
- disabled: !selectedAsset?.available,
5670
- children: "Max"
5671
- }
5672
- ),
5673
- /* @__PURE__ */ jsxRuntime.jsx("span", { children: selectedAsset?.code ?? "" })
5674
- ] })
5675
- ] })
5676
- ] }),
5677
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-field", children: [
5678
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-send-label", children: "Destination wallet" }),
5679
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-input-shell", children: [
5680
- /* @__PURE__ */ jsxRuntime.jsx(
5681
- "input",
5682
- {
5683
- className: "pollar-input pollar-send-destination-input",
5962
+ className: "pollar-input pollar-send-destination-input",
5684
5963
  type: "text",
5685
5964
  placeholder: destinationPlaceholder(selectedChain),
5686
5965
  value: destination,
@@ -5725,7 +6004,41 @@ function SendModalTemplate({
5725
6004
  ] })
5726
6005
  ] }),
5727
6006
  formError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-error", children: formError }),
5728
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-btn-primary", onClick: onSubmit, disabled: !canSubmit, children: "Continue" }) })
6007
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-btn-primary", onClick: onSubmit, disabled: !canSubmit, children: isCheckingDestination ? "Checking destination\u2026" : "Continue" }) })
6008
+ ] }),
6009
+ step === "create-account" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
6010
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-notice", children: [
6011
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-send-notice-title", children: "This account doesn't exist yet" }),
6012
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-send-notice-address", children: destination.trim() }),
6013
+ createsWithNative ? /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
6014
+ "A Stellar account has to be created and funded before it can receive payments. Instead of a payment, this will",
6015
+ " ",
6016
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "create the account" }),
6017
+ " with ",
6018
+ amount,
6019
+ " XLM as its starting balance."
6020
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs("p", { children: [
6021
+ "It can't receive ",
6022
+ selectedAsset?.code ?? "this asset",
6023
+ " yet: it has to be created and funded with XLM first, and then add a trustline for ",
6024
+ selectedAsset?.code ?? "the asset",
6025
+ "."
6026
+ ] }),
6027
+ createsWithNative && !meetsMinimum && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-send-notice-warning", children: [
6028
+ "A new account needs a starting balance of at least ",
6029
+ minStartingBalance,
6030
+ " XLM. Go back and raise the amount to create it."
6031
+ ] }),
6032
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "Or wait until another wallet funds it, and send afterwards." })
6033
+ ] }),
6034
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-account-actions", children: [
6035
+ createsWithNative && onCreateAccount && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-btn-primary", onClick: onCreateAccount, disabled: !meetsMinimum, children: [
6036
+ "Create account with ",
6037
+ amount,
6038
+ " XLM"
6039
+ ] }),
6040
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-btn-secondary", onClick: onBack, children: "I'll wait until it's funded" })
6041
+ ] })
5729
6042
  ] }),
5730
6043
  step === "tx" && /* @__PURE__ */ jsxRuntime.jsx(
5731
6044
  TxStatusView,
@@ -5748,6 +6061,11 @@ function SendModalTemplate({
5748
6061
  );
5749
6062
  }
5750
6063
  var MAX_MEMO_ID = 18446744073709551615n;
6064
+ var SOLANA_FEE_MARGIN_LAMPORTS = 10000n;
6065
+ function solanaMaxAfterFee(available) {
6066
+ const lamports = BigInt(core.toBaseUnits(available, 9)) - SOLANA_FEE_MARGIN_LAMPORTS;
6067
+ return core.fromBaseUnits((lamports > 0n ? lamports : 0n).toString(), 9);
6068
+ }
5751
6069
  function parseMemo(type, raw) {
5752
6070
  const value = type === "id" ? raw.trim() : raw;
5753
6071
  if (value.trim() === "") return void 0;
@@ -5757,6 +6075,7 @@ function parseMemo(type, raw) {
5757
6075
  if (!/^\d+$/.test(value) || BigInt(value) > MAX_MEMO_ID) return "The memo ID must be a whole number up to 2^64 - 1.";
5758
6076
  return { type, value };
5759
6077
  }
6078
+ var MIN_STARTING_BALANCE_XLM = 1;
5760
6079
  function assetParam(record) {
5761
6080
  if (record.type === "native") return { type: "native" };
5762
6081
  if (record.type === "credit_alphanum4") {
@@ -5774,11 +6093,14 @@ function SendModal({ onClose }) {
5774
6093
  tx: transaction,
5775
6094
  wallet,
5776
6095
  network,
5777
- styles
6096
+ styles,
6097
+ getClient
5778
6098
  } = usePollar();
5779
6099
  const walletType = wallet?.custody === "external" ? wallet.provider : null;
5780
6100
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
5781
6101
  const [step, setStep] = react.useState("form");
6102
+ const [createsAccount, setCreatesAccount] = react.useState(false);
6103
+ const [checkingDestination, setCheckingDestination] = react.useState(false);
5782
6104
  const [amount, setAmount] = react.useState("");
5783
6105
  const [destination, setDestination] = react.useState("");
5784
6106
  const [memo, setMemo] = react.useState("");
@@ -5834,8 +6156,15 @@ function SendModal({ onClose }) {
5834
6156
  "building-signing-submitting"
5835
6157
  ];
5836
6158
  const isInProgress = IN_FLIGHT_STEPS3.includes(transaction.step);
5837
- const showBack = step === "tx" && (transaction.step === "error" || transaction.step === "success") && !isInProgress;
5838
- const txTitle = isInProgress ? "Sending\u2026" : transaction.step === "success" ? "Sent!" : transaction.step === "error" ? "Send failed" : "Confirm Send";
6159
+ const showBack = step === "create-account" || step === "tx" && (transaction.step === "error" || transaction.step === "success") && !isInProgress;
6160
+ const txTitle = createsAccount ? isInProgress ? "Creating account\u2026" : transaction.step === "success" ? "Account created!" : transaction.step === "error" ? "Create account failed" : "Confirm Create Account" : isInProgress ? "Sending\u2026" : transaction.step === "success" ? "Sent!" : transaction.step === "error" ? "Send failed" : "Confirm Send";
6161
+ async function stellarAccountExists(address) {
6162
+ try {
6163
+ return (await getClient().getWalletBalance(address)).exists;
6164
+ } catch {
6165
+ return null;
6166
+ }
6167
+ }
5839
6168
  async function handleSubmit() {
5840
6169
  setFormError("");
5841
6170
  if (!canSendOnChain) {
@@ -5882,6 +6211,16 @@ function SendModal({ onClose }) {
5882
6211
  return;
5883
6212
  }
5884
6213
  }
6214
+ if (selectedChain === "STELLAR") {
6215
+ setCheckingDestination(true);
6216
+ const exists = await stellarAccountExists(destination.trim());
6217
+ setCheckingDestination(false);
6218
+ if (exists === false) {
6219
+ setStep("create-account");
6220
+ return;
6221
+ }
6222
+ }
6223
+ setCreatesAccount(false);
5885
6224
  setStep("tx");
5886
6225
  if (selectedChain === "SOLANA" || selectedChain === "POLYGON") {
5887
6226
  await sendPayment({
@@ -5900,6 +6239,17 @@ function SendModal({ onClose }) {
5900
6239
  parsedMemo ? { memo: parsedMemo } : void 0
5901
6240
  );
5902
6241
  }
6242
+ async function handleCreateAccount() {
6243
+ const parsedMemo = parseMemo(memoType, memo);
6244
+ if (typeof parsedMemo === "string" || parseFloat(amount) < MIN_STARTING_BALANCE_XLM) return;
6245
+ setCreatesAccount(true);
6246
+ setStep("tx");
6247
+ await buildTx(
6248
+ "create_account",
6249
+ { destination: destination.trim(), startingBalance: amount },
6250
+ parsedMemo ? { memo: parsedMemo } : void 0
6251
+ );
6252
+ }
5903
6253
  function handleSignAndSend() {
5904
6254
  if (transaction.step === "built") {
5905
6255
  void signAndSubmitTx(transaction.buildData.unsignedXdr);
@@ -5907,7 +6257,15 @@ function SendModal({ onClose }) {
5907
6257
  }
5908
6258
  function handleMax() {
5909
6259
  if (selectedAsset?.available !== null && selectedAsset?.available !== void 0) {
5910
- setAmount(selectedAsset.available);
6260
+ const paysOwnSolFee = selectedChain === "SOLANA" && selectedAsset.type === "native" && selectedAsset.sponsored !== true;
6261
+ let max = selectedAsset.available;
6262
+ if (paysOwnSolFee) {
6263
+ try {
6264
+ max = solanaMaxAfterFee(selectedAsset.available);
6265
+ } catch {
6266
+ }
6267
+ }
6268
+ setAmount(max);
5911
6269
  setFormError("");
5912
6270
  }
5913
6271
  }
@@ -5946,6 +6304,7 @@ function SendModal({ onClose }) {
5946
6304
  }
5947
6305
  function handleBack() {
5948
6306
  setStep("form");
6307
+ setCreatesAccount(false);
5949
6308
  setShowXdr(false);
5950
6309
  setCopied(false);
5951
6310
  }
@@ -5968,6 +6327,9 @@ function SendModal({ onClose }) {
5968
6327
  destination,
5969
6328
  formError,
5970
6329
  isLoadingBalance: walletBalance.step === "loading",
6330
+ isCheckingDestination: checkingDestination,
6331
+ minStartingBalance: MIN_STARTING_BALANCE_XLM,
6332
+ onCreateAccount: () => void handleCreateAccount(),
5971
6333
  transaction,
5972
6334
  showXdr,
5973
6335
  copied,
@@ -6255,7 +6617,7 @@ function SwapModalTemplate({
6255
6617
  ] })
6256
6618
  ] }),
6257
6619
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-swap-panel-main", children: [
6258
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: `pollar-swap-output${quoteLoading ? " is-loading" : ""}`, "aria-live": "polite", children: quoteLoading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }) : formatAmount2(quote?.amountOut) }),
6620
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: `pollar-swap-output${quoteLoading ? " is-loading" : ""}`, "aria-live": "polite", children: quoteLoading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm" }) : quote ? `~${formatAmount2(quote.amountOut)}` : "\u2014" }),
6259
6621
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-swap-panel-selector", children: [
6260
6622
  /* @__PURE__ */ jsxRuntime.jsx(
6261
6623
  AssetPill,
@@ -6719,7 +7081,6 @@ function EarnModal({ onClose }) {
6719
7081
  earnWithdraw,
6720
7082
  tx: transaction,
6721
7083
  wallet,
6722
- walletBalance,
6723
7084
  refreshWalletBalance,
6724
7085
  enabledAssets,
6725
7086
  refreshAssets,
@@ -6801,13 +7162,9 @@ function EarnModal({ onClose }) {
6801
7162
  const withdrawUnit = position?.withdrawUnit ?? "asset";
6802
7163
  const assetCode = selectedOpportunity?.asset.code ?? "";
6803
7164
  const amountUnitLabel = tab === "deposit" ? assetCode : withdrawUnit === "shares" ? "shares" : assetCode;
6804
- const walletAvailable = (() => {
6805
- if (!selectedOpportunity) return null;
6806
- const balances = walletBalance.step === "loaded" ? walletBalance.data.balances : [];
6807
- const issuer = selectedOpportunity.asset.issuer;
6808
- const rec = balances.find((b) => issuer === null ? b.type === "native" : b.code === assetCode && b.issuer === issuer);
6809
- return rec ? rec.available : null;
6810
- })();
7165
+ const walletAvailable = useWalletAvailable(
7166
+ selectedOpportunity ? { chain: "STELLAR", code: assetCode, issuer: selectedOpportunity.asset.issuer } : null
7167
+ );
6811
7168
  const depositAssetRecord = (() => {
6812
7169
  if (!selectedOpportunity?.asset.issuer || enabledAssets.step !== "loaded") return void 0;
6813
7170
  return enabledAssets.data.assets.find(
@@ -7041,37 +7398,17 @@ function EarnModal({ onClose }) {
7041
7398
  ] })
7042
7399
  ] })
7043
7400
  ] }),
7044
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-field", children: [
7045
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-send-label-row", children: [
7046
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "pollar-send-label", children: tab === "deposit" ? "Amount" : "Withdraw amount" }),
7047
- tab === "deposit" && walletAvailable != null && /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "pollar-swap-custom-toggle", onClick: () => setAmount(walletAvailable), children: [
7048
- "Max: ",
7049
- formatAmount3(walletAvailable),
7050
- " ",
7051
- amountUnitLabel
7052
- ] }),
7053
- tab === "withdraw" && position && /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "pollar-swap-custom-toggle", onClick: () => setAmount(position.withdrawable), children: [
7054
- "Max: ",
7055
- formatAmount3(position.withdrawable),
7056
- " ",
7057
- amountUnitLabel
7058
- ] })
7059
- ] }),
7060
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-earn-amount", children: [
7061
- /* @__PURE__ */ jsxRuntime.jsx(
7062
- "input",
7063
- {
7064
- className: "pollar-input",
7065
- type: "text",
7066
- inputMode: "decimal",
7067
- placeholder: "0.00",
7068
- value: amount,
7069
- onChange: (e) => setAmount(e.target.value)
7070
- }
7071
- ),
7072
- amountUnitLabel && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-earn-amount-unit", children: amountUnitLabel })
7073
- ] })
7074
- ] }),
7401
+ /* @__PURE__ */ jsxRuntime.jsx(
7402
+ AmountField,
7403
+ {
7404
+ id: "pollar-earn-amount",
7405
+ label: tab === "deposit" ? "Amount" : "Withdraw amount",
7406
+ value: amount,
7407
+ onChange: setAmount,
7408
+ unit: amountUnitLabel,
7409
+ available: tab === "deposit" ? walletAvailable : position ? position.withdrawable : void 0
7410
+ }
7411
+ ),
7075
7412
  formError && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-error", children: formError }),
7076
7413
  depositNeedsTrustline && selectedOpportunity && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-swap-trustline-notice", children: [
7077
7414
  "To deposit ",
@@ -7103,13 +7440,16 @@ function EarnModal({ onClose }) {
7103
7440
  }
7104
7441
 
7105
7442
  // src/components/card-modal/card-kyc.ts
7106
- var nonEmpty2 = (value) => typeof value === "string" && !!value.trim();
7443
+ var nonEmpty = (value) => typeof value === "string" && !!value.trim();
7107
7444
  function requiredCardStep(error) {
7108
7445
  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;
7112
- if (!nonEmpty2(cardProviderId) || !nonEmpty2(optionId)) return null;
7446
+ if (!("code" in error) || error.code !== "SDK_CARDS_REQUIREMENT_REQUIRED") return null;
7447
+ const body = "body" in error ? error.body : void 0;
7448
+ if (typeof body !== "object" || body === null) return null;
7449
+ const cardProviderId = "cardProviderId" in body ? body.cardProviderId : void 0;
7450
+ const requirementType = "requirementType" in body ? body.requirementType : void 0;
7451
+ const optionId = "optionId" in body ? body.optionId : void 0;
7452
+ if (!nonEmpty(cardProviderId) || !nonEmpty(optionId)) return null;
7113
7453
  if (requirementType === "KYC" || requirementType === "FORM" || requirementType === "REGISTRY_CHECK" || requirementType === "PROVIDER_REGISTRATION") {
7114
7454
  return { cardProviderId, type: requirementType, optionId };
7115
7455
  }
@@ -7264,6 +7604,8 @@ function CardModal({ onClose }) {
7264
7604
  const [error, setError] = react.useState("");
7265
7605
  const revealTimer = react.useRef(null);
7266
7606
  const provider = providers?.[0] ?? null;
7607
+ const fundingAsset = provider?.fundingAssets[0] ?? "USDC";
7608
+ const fundAvailable = useWalletAvailable(panel === "fund" ? { chain: "STELLAR", code: fundingAsset } : null);
7267
7609
  const card = cards?.[0] ?? null;
7268
7610
  const loadHolder = react.useCallback(async () => {
7269
7611
  const h = await getClient().getCardHolder();
@@ -7762,15 +8104,17 @@ function CardModal({ onClose }) {
7762
8104
  transactions === null && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-card-note", children: "Loading..." }),
7763
8105
  transactions?.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-card-note", children: "No movements yet." }),
7764
8106
  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;
8107
+ const localAmount = typeof tx.purchase?.localAmount === "number" ? tx.purchase.localAmount : null;
8108
+ const localCurrency = typeof tx.purchase?.localCurrency === "string" ? tx.purchase.localCurrency : null;
8109
+ const purchaseStatus = typeof tx.purchase?.status === "string" ? tx.purchase.status : null;
8110
+ const local = localAmount && localCurrency && localCurrency !== "USD" ? money(String(localAmount), localCurrency) : null;
7767
8111
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-card-tx", children: [
7768
8112
  /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
7769
8113
  tx.merchantName ?? TX_LABELS[tx.type] ?? tx.type,
7770
8114
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-card-tx-meta", children: [
7771
8115
  new Date(tx.occurredAt).toLocaleDateString(),
7772
8116
  tx.merchantName ? ` \xB7 ${TX_LABELS[tx.type] ?? tx.type}` : "",
7773
- purchase?.status && purchase.status !== "completed" ? ` \xB7 ${purchase.status}` : ""
8117
+ purchaseStatus && purchaseStatus !== "completed" ? ` \xB7 ${purchaseStatus}` : ""
7774
8118
  ] })
7775
8119
  ] }),
7776
8120
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-card-tx-amount", "data-direction": tx.direction ?? void 0, children: [
@@ -7787,19 +8131,18 @@ function CardModal({ onClose }) {
7787
8131
  provider.fundingAssets.join(" or ") || "USDC",
7788
8132
  " from your wallet to your card. It takes a few minutes to cross to the card network."
7789
8133
  ] }),
7790
- /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-send-field", children: [
7791
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-send-label", children: "Amount (USDC)" }),
7792
- /* @__PURE__ */ jsxRuntime.jsx(
7793
- "input",
7794
- {
7795
- className: "pollar-input",
7796
- inputMode: "decimal",
7797
- placeholder: "10",
7798
- value: fundAmount,
7799
- onChange: (e) => setFundAmount(e.target.value.replace(/[^0-9.]/g, ""))
7800
- }
7801
- )
7802
- ] }),
8134
+ /* @__PURE__ */ jsxRuntime.jsx(
8135
+ AmountField,
8136
+ {
8137
+ id: "pollar-card-fund-amount",
8138
+ label: "Amount",
8139
+ value: fundAmount,
8140
+ onChange: (value) => setFundAmount(value.replace(/[^0-9.]/g, "")),
8141
+ unit: fundingAsset,
8142
+ available: fundAvailable,
8143
+ placeholder: "10"
8144
+ }
8145
+ ),
7803
8146
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-actions", children: /* @__PURE__ */ jsxRuntime.jsx(
7804
8147
  "button",
7805
8148
  {
@@ -8169,7 +8512,8 @@ function useCosign() {
8169
8512
  revokeDevice: react.useCallback(
8170
8513
  (deviceId, options) => getClient().cosign.revokeDevice(deviceId, options),
8171
8514
  [getClient]
8172
- )
8515
+ ),
8516
+ switchCustody: react.useCallback((to) => getClient().cosign.switchCustody(to), [getClient])
8173
8517
  };
8174
8518
  }
8175
8519
  var OPERATION_LABELS = {
@@ -8191,6 +8535,15 @@ function expiresIn(request) {
8191
8535
  const m = Math.floor(ms % 36e5 / 6e4);
8192
8536
  return h > 0 ? `${h}h ${m}m` : `${m}m`;
8193
8537
  }
8538
+ function readableAmount(details) {
8539
+ const { amount, decimals, symbol } = details;
8540
+ if (typeof amount !== "string" || typeof decimals !== "number" || typeof symbol !== "string") return null;
8541
+ try {
8542
+ return { amount: core.fromBaseUnits(amount, decimals), symbol };
8543
+ } catch {
8544
+ return null;
8545
+ }
8546
+ }
8194
8547
  function detailRows(request) {
8195
8548
  const details = request.details ?? {};
8196
8549
  const rows = [];
@@ -8202,14 +8555,21 @@ function detailRows(request) {
8202
8555
  ]);
8203
8556
  };
8204
8557
  push("Destination", details.destination);
8205
- push("Amount", details.amount);
8206
- push("Asset", details.asset ?? details.assetCode);
8558
+ const readable = readableAmount(details);
8559
+ push("Amount", readable ? `${readable.amount} ${readable.symbol}` : details.amount);
8560
+ push("Asset", details.asset ?? details.assetCode ?? readable?.symbol);
8207
8561
  push("Network", request.network);
8208
8562
  return rows;
8209
8563
  }
8210
8564
  function CloseIcon2() {
8211
8565
  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" }) });
8212
8566
  }
8567
+ function VerifiedIcon() {
8568
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: [
8569
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }),
8570
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M3.5 7l2.5 2.5 4.5-5", stroke: "white", strokeWidth: "1.4", strokeLinecap: "round", strokeLinejoin: "round" })
8571
+ ] });
8572
+ }
8213
8573
  function CosignModalTemplate({
8214
8574
  theme,
8215
8575
  accentColor,
@@ -8222,6 +8582,8 @@ function CosignModalTemplate({
8222
8582
  approveBlocked = false,
8223
8583
  lastResolved,
8224
8584
  explorerUrl,
8585
+ closesIn = null,
8586
+ nextStep = null,
8225
8587
  onSelect,
8226
8588
  onApprove,
8227
8589
  onReject,
@@ -8230,6 +8592,8 @@ function CosignModalTemplate({
8230
8592
  const cssVars = buildModalCssVars(theme, accentColor, styleOverrides);
8231
8593
  const verified = verification?.ok === true;
8232
8594
  const canVote = !!current && verified && !busy && !approveBlocked;
8595
+ const [verifiedOpenFor, setVerifiedOpenFor] = react.useState(null);
8596
+ const verifiedOpen = !!current && verifiedOpenFor === current.id;
8233
8597
  return /* @__PURE__ */ jsxRuntime.jsxs(
8234
8598
  "div",
8235
8599
  {
@@ -8242,27 +8606,48 @@ function CosignModalTemplate({
8242
8606
  /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-modal-title", children: "Confirm with your device" }),
8243
8607
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-header-actions", children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-modal-close", onClick: onClose, "aria-label": "Close", title: "Close", children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon2, {}) }) })
8244
8608
  ] }),
8245
- pending.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-cosign-empty", children: lastResolved ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8246
- lastResolved.status === "APPROVED" && /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "pollar-cosign-approved-icon", width: "44", height: "44", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: [
8247
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }),
8248
- /* @__PURE__ */ jsxRuntime.jsx(
8249
- "path",
8250
- {
8251
- d: "M3.5 7l2.5 2.5 4.5-5",
8252
- stroke: "white",
8253
- strokeWidth: "1.2",
8254
- strokeLinecap: "round",
8255
- strokeLinejoin: "round"
8256
- }
8257
- )
8609
+ pending.length === 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-empty", children: [
8610
+ lastResolved ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8611
+ lastResolved.status === "APPROVED" && /* @__PURE__ */ jsxRuntime.jsxs("svg", { className: "pollar-cosign-approved-icon", width: "44", height: "44", viewBox: "0 0 14 14", fill: "none", "aria-hidden": true, children: [
8612
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "7", cy: "7", r: "7", fill: "currentColor" }),
8613
+ /* @__PURE__ */ jsxRuntime.jsx(
8614
+ "path",
8615
+ {
8616
+ d: "M3.5 7l2.5 2.5 4.5-5",
8617
+ stroke: "white",
8618
+ strokeWidth: "1.2",
8619
+ strokeLinecap: "round",
8620
+ strokeLinejoin: "round"
8621
+ }
8622
+ )
8623
+ ] }),
8624
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-empty-title", children: lastResolved.status === "APPROVED" ? "Approved" : lastResolved.status === "REJECTED" ? "Rejected" : lastResolved.status.toLowerCase() }),
8625
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-muted", children: lastResolved.submit?.hash ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8626
+ "Sent. Transaction",
8627
+ " ",
8628
+ explorerUrl ? /* @__PURE__ */ jsxRuntime.jsx("a", { className: "pollar-cosign-tx-link", href: explorerUrl, target: "_blank", rel: "noopener noreferrer", children: cropAddress(lastResolved.submit.hash) }) : cropAddress(lastResolved.submit.hash),
8629
+ /* @__PURE__ */ jsxRuntime.jsx(
8630
+ CopyButton,
8631
+ {
8632
+ value: lastResolved.submit.hash,
8633
+ label: "Copy transaction hash",
8634
+ className: "pollar-copy-btn-sm pollar-cosign-copy"
8635
+ }
8636
+ )
8637
+ ] }) : lastResolved.signedXdr ? "Signed. The app will send it." : "Nothing was signed." })
8638
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-muted", children: "Nothing waits for your approval." }),
8639
+ nextStep && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-cosign-next", children: [
8640
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm", "aria-hidden": true }),
8641
+ " Next: ",
8642
+ nextStep,
8643
+ ". It appears here in a few seconds."
8258
8644
  ] }),
8259
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-empty-title", children: lastResolved.status === "APPROVED" ? "Approved" : lastResolved.status === "REJECTED" ? "Rejected" : lastResolved.status.toLowerCase() }),
8260
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-muted", children: lastResolved.submit?.hash ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8261
- "Sent. Transaction",
8262
- " ",
8263
- explorerUrl ? /* @__PURE__ */ jsxRuntime.jsx("a", { className: "pollar-cosign-tx-link", href: explorerUrl, target: "_blank", rel: "noopener noreferrer", children: cropAddress(lastResolved.submit.hash) }) : cropAddress(lastResolved.submit.hash)
8264
- ] }) : lastResolved.signedXdr ? "Signed. The app will send it." : "Nothing was signed." })
8265
- ] }) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-muted", children: "Nothing waits for your approval." }) }),
8645
+ closesIn !== null && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-cosign-muted pollar-cosign-closing", children: [
8646
+ "Closing in ",
8647
+ closesIn,
8648
+ "s"
8649
+ ] })
8650
+ ] }),
8266
8651
  pending.length > 1 && /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "pollar-cosign-list", children: pending.map((r) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
8267
8652
  "button",
8268
8653
  {
@@ -8276,9 +8661,27 @@ function CosignModalTemplate({
8276
8661
  }
8277
8662
  ) }, r.id)) }),
8278
8663
  current && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-detail", children: [
8279
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-detail-title", children: labelOf(current) }),
8664
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-detail-head", children: [
8665
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-detail-title", children: labelOf(current) }),
8666
+ !verification && /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-cosign-chip", children: [
8667
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-spinner pollar-spinner-sm", "aria-hidden": true }),
8668
+ "Checking..."
8669
+ ] }),
8670
+ verification?.ok && /* @__PURE__ */ jsxRuntime.jsxs(
8671
+ "button",
8672
+ {
8673
+ type: "button",
8674
+ className: "pollar-cosign-chip pollar-cosign-chip-ok",
8675
+ "aria-expanded": verifiedOpen,
8676
+ onClick: () => setVerifiedOpenFor(verifiedOpen ? null : current.id),
8677
+ children: [
8678
+ /* @__PURE__ */ jsxRuntime.jsx(VerifiedIcon, {}),
8679
+ "Verified"
8680
+ ]
8681
+ }
8682
+ )
8683
+ ] }),
8280
8684
  current.summary && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-muted", children: current.summary }),
8281
- /* @__PURE__ */ jsxRuntime.jsx(GroupSteps, { request: current }),
8282
8685
  /* @__PURE__ */ jsxRuntime.jsxs("dl", { className: "pollar-cosign-rows", children: [
8283
8686
  detailRows(current).map(([k, v]) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-row", children: [
8284
8687
  /* @__PURE__ */ jsxRuntime.jsx("dt", { children: k }),
@@ -8289,140 +8692,637 @@ function CosignModalTemplate({
8289
8692
  /* @__PURE__ */ jsxRuntime.jsx("dd", { children: expiresIn(current) })
8290
8693
  ] })
8291
8694
  ] }),
8292
- /* @__PURE__ */ jsxRuntime.jsxs(
8293
- "div",
8294
- {
8295
- className: `pollar-cosign-verify pollar-cosign-verify-${verification ? verification.ok ? "ok" : "bad" : "pending"}`,
8296
- children: [
8297
- !verification && "Checking the transaction against Turnkey...",
8298
- verification?.ok && "Verified: this is exactly what will be signed.",
8299
- verification && !verification.ok && `Not verified: ${verification.reason}`
8300
- ]
8301
- }
8302
- ),
8695
+ verification?.ok && verifiedOpen && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-cosign-verify pollar-cosign-verify-ok", children: "Checked against Turnkey: this is exactly what will be signed." }),
8696
+ verification && !verification.ok && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-verify pollar-cosign-verify-bad", children: [
8697
+ "Not verified: ",
8698
+ verification.reason
8699
+ ] }),
8303
8700
  error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-cosign-error", children: error }),
8304
8701
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-actions", children: [
8305
8702
  /* @__PURE__ */ jsxRuntime.jsx(
8306
- "button",
8703
+ "button",
8704
+ {
8705
+ type: "button",
8706
+ className: "pollar-cosign-btn pollar-cosign-btn-secondary",
8707
+ disabled: !!busy,
8708
+ onClick: () => onReject(current),
8709
+ children: busy === "rejecting" ? "Rejecting..." : "Reject"
8710
+ }
8711
+ ),
8712
+ /* @__PURE__ */ jsxRuntime.jsx(
8713
+ "button",
8714
+ {
8715
+ type: "button",
8716
+ className: "pollar-cosign-btn pollar-cosign-btn-primary",
8717
+ disabled: !canVote,
8718
+ onClick: () => onApprove(current),
8719
+ children: busy === "approving" ? "Approving..." : "Approve"
8720
+ }
8721
+ )
8722
+ ] })
8723
+ ] }),
8724
+ /* @__PURE__ */ jsxRuntime.jsx(PollarModalFooter, {})
8725
+ ]
8726
+ }
8727
+ );
8728
+ }
8729
+ var CLOSE_AFTER_SECONDS = 3;
8730
+ var CLOSE_AFTER_SENT_SECONDS = 5;
8731
+ var NEXT_REQUEST_EVERY_MS = 2e3;
8732
+ function CosignModal({ nextStep = null, onClose }) {
8733
+ const { getClient, styles, network } = usePollar();
8734
+ const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
8735
+ const { pending, lastResolved, loaded, refresh, approve, reject, verify, inspectQuorum } = useCosign();
8736
+ const [selectedId, setSelectedId] = react.useState(null);
8737
+ const [verification, setVerification] = react.useState(null);
8738
+ const [busy, setBusy] = react.useState(null);
8739
+ const [error, setError] = react.useState(null);
8740
+ const [approveFailedFor, setApproveFailedFor] = react.useState(null);
8741
+ const [quorumFailure, setQuorumFailure] = react.useState(null);
8742
+ react.useEffect(() => {
8743
+ let cancelled = false;
8744
+ inspectQuorum().then((q) => {
8745
+ if (!cancelled && !q.ok) setQuorumFailure(q.reason);
8746
+ }).catch(() => void 0);
8747
+ return () => {
8748
+ cancelled = true;
8749
+ };
8750
+ }, [inspectQuorum]);
8751
+ const current = pending.find((r) => r.id === selectedId) ?? pending[0] ?? null;
8752
+ const currentRef = react.useRef(current);
8753
+ currentRef.current = current;
8754
+ const currentId = current?.id ?? null;
8755
+ react.useEffect(() => {
8756
+ const request = currentRef.current;
8757
+ if (!request) return;
8758
+ setVerification(null);
8759
+ setError(null);
8760
+ setApproveFailedFor(null);
8761
+ let cancelled = false;
8762
+ verify(request).then((v) => {
8763
+ if (!cancelled) setVerification(v);
8764
+ }).catch((err) => {
8765
+ if (!cancelled) setVerification({ ok: false, reason: err instanceof Error ? err.message : String(err) });
8766
+ });
8767
+ return () => {
8768
+ cancelled = true;
8769
+ };
8770
+ }, [currentId, verify]);
8771
+ const onCloseRef = react.useRef(onClose);
8772
+ onCloseRef.current = onClose;
8773
+ react.useEffect(() => {
8774
+ return getClient().onAuthStateChange((authState) => {
8775
+ if (authState.step === "idle") onCloseRef.current();
8776
+ });
8777
+ }, [getClient]);
8778
+ const silent = pending.length === 0 && (lastResolved ? lastResolved.status === "APPROVED" && !lastResolved.submit?.hash && !lastResolved.signedXdr : loaded);
8779
+ const sent = pending.length === 0 && lastResolved?.status === "APPROVED" && !!lastResolved.submit?.hash;
8780
+ const awaitingNext = sent && !!nextStep;
8781
+ const closeAfter = awaitingNext ? null : sent ? CLOSE_AFTER_SENT_SECONDS : silent ? CLOSE_AFTER_SECONDS : null;
8782
+ react.useEffect(() => {
8783
+ if (!awaitingNext) return;
8784
+ const timer = setInterval(() => void refresh().catch(() => void 0), NEXT_REQUEST_EVERY_MS);
8785
+ return () => clearInterval(timer);
8786
+ }, [awaitingNext, refresh]);
8787
+ const [closesIn, setClosesIn] = react.useState(null);
8788
+ react.useEffect(() => {
8789
+ if (closeAfter === null) {
8790
+ setClosesIn(null);
8791
+ return;
8792
+ }
8793
+ let left = closeAfter;
8794
+ setClosesIn(left);
8795
+ const timer = setInterval(() => {
8796
+ left -= 1;
8797
+ if (left > 0) setClosesIn(left);
8798
+ else {
8799
+ clearInterval(timer);
8800
+ onCloseRef.current();
8801
+ }
8802
+ }, 1e3);
8803
+ return () => clearInterval(timer);
8804
+ }, [closeAfter, lastResolved?.id]);
8805
+ const run = react.useCallback(
8806
+ async (kind, request, action) => {
8807
+ setBusy(kind);
8808
+ setError(null);
8809
+ try {
8810
+ await action(request);
8811
+ } catch (err) {
8812
+ setError(core.transactionErrorMessage(core.isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null));
8813
+ if (kind === "approving") setApproveFailedFor(request.id);
8814
+ } finally {
8815
+ setBusy(null);
8816
+ }
8817
+ },
8818
+ []
8819
+ );
8820
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
8821
+ CosignModalTemplate,
8822
+ {
8823
+ theme,
8824
+ accentColor,
8825
+ styleOverrides,
8826
+ pending,
8827
+ current,
8828
+ verification: current ? quorumFailure ? { ok: false, reason: quorumFailure } : verification : null,
8829
+ busy,
8830
+ error,
8831
+ approveBlocked: !!current && approveFailedFor === current.id,
8832
+ ...lastResolved ? { lastResolved } : {},
8833
+ closesIn,
8834
+ nextStep: awaitingNext ? nextStep : null,
8835
+ explorerUrl: lastResolved?.submit?.hash ? explorerTxUrl(lastResolved.network, lastResolved.submit.hash, network) : null,
8836
+ onSelect: (r) => setSelectedId(r.id),
8837
+ onApprove: (r) => void run("approving", r, approve),
8838
+ onReject: (r) => void run("rejecting", r, reject),
8839
+ onClose
8840
+ }
8841
+ ) });
8842
+ }
8843
+ var CHAIN_NAME3 = { STELLAR: "Stellar", SOLANA: "Solana", POLYGON: "Polygon" };
8844
+ var short = (address) => `${address.slice(0, 6)}...${address.slice(-4)}`;
8845
+ 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.";
8846
+ function CustodyMigrationModalTemplate(p) {
8847
+ const cssVars = buildModalCssVars(p.theme, p.accentColor, p.styleOverrides);
8848
+ const [pick, setPick] = react.useState(p.state.choice ? null : p.state.toCustody);
8849
+ const [risksAccepted, setRisksAccepted] = react.useState(false);
8850
+ const custody = p.state.choice ? pick : p.state.toCustody;
8851
+ const canMove = !!custody && (custody !== "SELF_CUSTODY" || risksAccepted) && !p.busy;
8852
+ const moving = p.state.wallets.filter((w) => w.required);
8853
+ const changing = moving.filter((w) => w.addressChanges);
8854
+ const failed = moving.some((w) => w.status === "FAILED");
8855
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-card pollar-recovery-modal", "data-theme": p.theme, style: cssVars, children: [
8856
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-modal-header", children: /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-modal-title", children: "Move your wallets to this device" }) }),
8857
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-body", children: [
8858
+ /* @__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." }),
8859
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
8860
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "What moves" }),
8861
+ moving.map((w) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item", children: [
8862
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
8863
+ CHAIN_NAME3[w.chain] ?? w.chain,
8864
+ " \xB7 ",
8865
+ short(w.address)
8866
+ ] }),
8867
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-recovery-tag", children: w.addressChanges ? "New address" : "Same address" })
8868
+ ] }, w.chain))
8869
+ ] }),
8870
+ changing.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: [
8871
+ "Your ",
8872
+ changing.map((w) => CHAIN_NAME3[w.chain] ?? w.chain).join(" and "),
8873
+ " 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."
8874
+ ] }),
8875
+ p.state.choice && /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
8876
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Choose how your wallet signs" }),
8877
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-recovery-check", children: [
8878
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "radio", name: "custody", checked: pick === "COSIGNED", onChange: () => setPick("COSIGNED") }),
8879
+ " ",
8880
+ "Co-signed: you and the app approve every signature together, and the wallet can be recovered with help."
8881
+ ] }),
8882
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-recovery-check", children: [
8883
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "radio", name: "custody", checked: pick === "SELF_CUSTODY", onChange: () => setPick("SELF_CUSTODY") }),
8884
+ " ",
8885
+ "Self-custody: only your device signs. Nobody else can move or recover your funds."
8886
+ ] })
8887
+ ] }),
8888
+ custody === "SELF_CUSTODY" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
8889
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: SELF_CUSTODY_RISKS }),
8890
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-recovery-check", children: [
8891
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: risksAccepted, onChange: (e) => setRisksAccepted(e.target.checked) }),
8892
+ " I understand and accept these risks"
8893
+ ] })
8894
+ ] }),
8895
+ 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." }),
8896
+ p.blockedReasons && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-banner pollar-recovery-banner-warn", role: "alert", children: [
8897
+ "A wallet cannot move while something still uses it: ",
8898
+ p.blockedReasons.join(", "),
8899
+ ". Let it finish and try again."
8900
+ ] }),
8901
+ /* @__PURE__ */ jsxRuntime.jsx(
8902
+ "button",
8903
+ {
8904
+ type: "button",
8905
+ className: "pollar-recovery-btn pollar-recovery-btn-primary",
8906
+ onClick: () => custody && p.onMove(p.state.choice ? custody : void 0),
8907
+ disabled: !canMove,
8908
+ children: p.busy ? "Moving your wallets..." : "Move my wallets"
8909
+ }
8910
+ ),
8911
+ 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" }),
8912
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: "pollar-recovery-btn pollar-recovery-btn-quiet", onClick: p.onLogout, disabled: p.busy, children: "Log out" }),
8913
+ p.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-error", role: "alert", children: p.error })
8914
+ ] }),
8915
+ /* @__PURE__ */ jsxRuntime.jsx(PollarModalFooter, {})
8916
+ ] });
8917
+ }
8918
+ function CustodyMigrationModal({ state, onMoved, onLater }) {
8919
+ const { getClient, styles } = usePollar();
8920
+ const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
8921
+ const [busy, setBusy] = react.useState(false);
8922
+ const [error, setError] = react.useState(null);
8923
+ const [blockedReasons, setBlockedReasons] = react.useState(null);
8924
+ async function move(custody) {
8925
+ setBusy(true);
8926
+ setError(null);
8927
+ setBlockedReasons(null);
8928
+ try {
8929
+ await getClient().cosign.migrateWallets(custody);
8930
+ onMoved();
8931
+ } catch (err) {
8932
+ if (core.isPollarApiError(err) && err.code === "SDK_CUSTODY_MIGRATION_BLOCKED") {
8933
+ const reasons = err.body.reasons;
8934
+ setBlockedReasons(Array.isArray(reasons) ? reasons.map(String) : []);
8935
+ return;
8936
+ }
8937
+ setError(core.transactionErrorMessage(core.isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null));
8938
+ } finally {
8939
+ setBusy(false);
8940
+ }
8941
+ }
8942
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-overlay", style: overlayStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
8943
+ CustodyMigrationModalTemplate,
8944
+ {
8945
+ theme,
8946
+ accentColor,
8947
+ styleOverrides,
8948
+ state,
8949
+ busy,
8950
+ error,
8951
+ blockedReasons,
8952
+ onMove: (custody) => void move(custody),
8953
+ onLater,
8954
+ onLogout: () => void getClient().logout()
8955
+ }
8956
+ ) });
8957
+ }
8958
+ function CloseIcon3() {
8959
+ 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" }) });
8960
+ }
8961
+ var shortKey = (key) => `${key.slice(0, 6)}...${key.slice(-4)}`;
8962
+ function Button(props) {
8963
+ const running = props.busy === props.id;
8964
+ return /* @__PURE__ */ jsxRuntime.jsx(
8965
+ "button",
8966
+ {
8967
+ type: "button",
8968
+ className: `pollar-recovery-btn pollar-recovery-btn-${props.kind ?? "quiet"}`,
8969
+ onClick: props.onClick,
8970
+ disabled: props.disabled || props.busy !== null,
8971
+ children: running ? props.busyLabel ?? "Working..." : props.label
8972
+ }
8973
+ );
8974
+ }
8975
+ function CodeForm(props) {
8976
+ const [code, setCode] = react.useState("");
8977
+ return /* @__PURE__ */ jsxRuntime.jsxs(
8978
+ "form",
8979
+ {
8980
+ className: "pollar-recovery-row",
8981
+ onSubmit: (e) => {
8982
+ e.preventDefault();
8983
+ if (code.trim()) props.onSubmit(code);
8984
+ },
8985
+ children: [
8986
+ /* @__PURE__ */ jsxRuntime.jsx(
8987
+ "input",
8988
+ {
8989
+ className: "pollar-recovery-input",
8990
+ value: code,
8991
+ onChange: (e) => setCode(e.target.value),
8992
+ placeholder: "Code from the email",
8993
+ autoComplete: "one-time-code",
8994
+ "aria-label": "Code from the email"
8995
+ }
8996
+ ),
8997
+ /* @__PURE__ */ jsxRuntime.jsx(
8998
+ Button,
8999
+ {
9000
+ busy: props.busy,
9001
+ id: props.id,
9002
+ label: props.label,
9003
+ kind: "primary",
9004
+ onClick: () => code.trim() && props.onSubmit(code)
9005
+ }
9006
+ )
9007
+ ]
9008
+ }
9009
+ );
9010
+ }
9011
+ function RecoveryModalTemplate(p) {
9012
+ const cssVars = buildModalCssVars(p.theme, p.accentColor, p.styleOverrides);
9013
+ const [exportAccepted, setExportAccepted] = react.useState(false);
9014
+ const [risksAccepted, setRisksAccepted] = react.useState(false);
9015
+ const state = p.state;
9016
+ const locked = !!state && state.required && !state.configured;
9017
+ const risksPending = locked && p.selfCustodyRisks && !risksAccepted;
9018
+ const lastMethod = !!state && state.passkeys.length + (state.email.verified ? 1 : 0) + (state.devices.length >= 2 ? 1 : 0) <= 1;
9019
+ let body;
9020
+ if (!state) {
9021
+ body = /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "Loading your wallet's recovery methods..." });
9022
+ } else if (!state.required) {
9023
+ body = /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "This wallet doesn't need recovery methods: it isn't secured by your device." });
9024
+ } else if (!p.thisDeviceEnrolled && p.view !== "recover-email") {
9025
+ body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9026
+ /* @__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." }),
9027
+ 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 }),
9028
+ state.email.verified && /* @__PURE__ */ jsxRuntime.jsx(
9029
+ Button,
9030
+ {
9031
+ busy: p.busy,
9032
+ id: "recover-send",
9033
+ label: `Email a code to ${state.email.address ?? "your email"}`,
9034
+ onClick: () => p.onView("recover-email")
9035
+ }
9036
+ ),
9037
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "enroll", label: "Ask another device to add this one", onClick: p.onRequestEnrollment })
9038
+ ] });
9039
+ } else if (risksPending && p.view === "overview") {
9040
+ body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9041
+ /* @__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." }),
9042
+ /* @__PURE__ */ jsxRuntime.jsx(
9043
+ Button,
9044
+ {
9045
+ busy: p.busy,
9046
+ id: "accept-risks",
9047
+ label: "I understand and accept these risks",
9048
+ kind: "primary",
9049
+ onClick: () => setRisksAccepted(true)
9050
+ }
9051
+ )
9052
+ ] });
9053
+ } else if (p.view === "recover-email") {
9054
+ body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9055
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pollar-recovery-muted", children: [
9056
+ "We'll email a code to ",
9057
+ state.email.address,
9058
+ ". The code goes straight to the wallet provider, encrypted on this device."
9059
+ ] }),
9060
+ !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 }),
9061
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9062
+ ] });
9063
+ } else if (p.view === "email") {
9064
+ body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9065
+ /* @__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." }),
9066
+ !p.codeSent ? /* @__PURE__ */ jsxRuntime.jsxs(
9067
+ "form",
9068
+ {
9069
+ className: "pollar-recovery-row",
9070
+ onSubmit: (e) => {
9071
+ e.preventDefault();
9072
+ p.onSendEmailCode();
9073
+ },
9074
+ children: [
9075
+ /* @__PURE__ */ jsxRuntime.jsx(
9076
+ "input",
8307
9077
  {
8308
- type: "button",
8309
- className: "pollar-cosign-btn pollar-cosign-btn-secondary",
8310
- disabled: !!busy,
8311
- onClick: () => onReject(current),
8312
- children: busy === "rejecting" ? "Rejecting..." : "Reject"
9078
+ className: "pollar-recovery-input",
9079
+ type: "email",
9080
+ value: p.emailDraft,
9081
+ onChange: (e) => p.onEmailDraft(e.target.value),
9082
+ placeholder: "you@example.com",
9083
+ "aria-label": "Email"
8313
9084
  }
8314
9085
  ),
8315
9086
  /* @__PURE__ */ jsxRuntime.jsx(
8316
- "button",
9087
+ Button,
8317
9088
  {
8318
- type: "button",
8319
- className: "pollar-cosign-btn pollar-cosign-btn-primary",
8320
- disabled: !canVote,
8321
- onClick: () => onApprove(current),
8322
- children: busy === "approving" ? "Approving..." : "Approve"
9089
+ busy: p.busy,
9090
+ id: "email-send",
9091
+ label: "Send code",
9092
+ kind: "primary",
9093
+ onClick: p.onSendEmailCode,
9094
+ disabled: !p.emailDraft.trim()
8323
9095
  }
8324
9096
  )
9097
+ ]
9098
+ }
9099
+ ) : /* @__PURE__ */ jsxRuntime.jsx(CodeForm, { busy: p.busy, id: "email-confirm", label: "Confirm", onSubmit: p.onConfirmEmail }),
9100
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9101
+ ] });
9102
+ } else if (p.view === "export") {
9103
+ body = p.phrase ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9104
+ /* @__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." }),
9105
+ /* @__PURE__ */ jsxRuntime.jsx("pre", { className: "pollar-recovery-phrase", children: p.phrase }),
9106
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "hide", label: "Hide", kind: "primary", onClick: () => p.onView("overview") })
9107
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9108
+ /* @__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." }),
9109
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-recovery-check", children: [
9110
+ /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: exportAccepted, onChange: (e) => setExportAccepted(e.target.checked) }),
9111
+ " I understand that whoever sees the phrase controls the wallet"
9112
+ ] }),
9113
+ /* @__PURE__ */ jsxRuntime.jsx(
9114
+ Button,
9115
+ {
9116
+ busy: p.busy,
9117
+ id: "export",
9118
+ label: "Show recovery phrase",
9119
+ kind: "danger",
9120
+ onClick: p.onExport,
9121
+ disabled: !exportAccepted
9122
+ }
9123
+ ),
9124
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "back", label: "Back", onClick: () => p.onView("overview") })
9125
+ ] });
9126
+ } else {
9127
+ const enrollments = p.devices?.enrollments ?? [];
9128
+ body = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9129
+ 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." }),
9130
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
9131
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Passkeys" }),
9132
+ state.passkeys.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-muted", children: "No passkey yet." }),
9133
+ state.passkeys.map((pk) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item", children: [
9134
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: pk.name }),
9135
+ /* @__PURE__ */ jsxRuntime.jsx(
9136
+ Button,
9137
+ {
9138
+ busy: p.busy,
9139
+ id: `passkey:${pk.authenticatorId}`,
9140
+ label: "Remove",
9141
+ kind: "danger",
9142
+ onClick: () => p.onRemovePasskey(pk.authenticatorId),
9143
+ disabled: lastMethod
9144
+ }
9145
+ )
9146
+ ] }, pk.authenticatorId)),
9147
+ 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." })
9148
+ ] }),
9149
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
9150
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Email" }),
9151
+ state.email.verified ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item", children: [
9152
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: state.email.address }),
9153
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-recovery-tag", children: "Confirmed" })
9154
+ ] }) : /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "email", label: "Confirm an email", onClick: () => p.onView("email") })
9155
+ ] }),
9156
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
9157
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Devices" }),
9158
+ (p.devices?.devices ?? []).map((d) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item", children: [
9159
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: d.label ?? shortKey(d.publicKey) }),
9160
+ d.publicKey.toLowerCase() === p.thisDeviceKey && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-recovery-tag", children: "This device" })
9161
+ ] }, d.id)),
9162
+ enrollments.map((e) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-item pollar-recovery-item-pending", children: [
9163
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
9164
+ e.label ?? "A device",
9165
+ " wants to join (",
9166
+ shortKey(e.publicKey),
9167
+ ")"
9168
+ ] }),
9169
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-recovery-actions", children: [
9170
+ /* @__PURE__ */ jsxRuntime.jsx(
9171
+ Button,
9172
+ {
9173
+ busy: p.busy,
9174
+ id: `approve:${e.id}`,
9175
+ label: "Approve",
9176
+ kind: "primary",
9177
+ onClick: () => p.onApproveDevice(e.id)
9178
+ }
9179
+ ),
9180
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: `reject:${e.id}`, label: "Reject", onClick: () => p.onRejectDevice(e.id) })
8325
9181
  ] })
9182
+ ] }, e.id)),
9183
+ /* @__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.' })
9184
+ ] }),
9185
+ /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "pollar-recovery-section", children: [
9186
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { children: "Recovery phrase" }),
9187
+ /* @__PURE__ */ jsxRuntime.jsx(Button, { busy: p.busy, id: "export", label: "Export recovery phrase", onClick: () => p.onView("export") })
9188
+ ] })
9189
+ ] });
9190
+ }
9191
+ return /* @__PURE__ */ jsxRuntime.jsxs(
9192
+ "div",
9193
+ {
9194
+ className: "pollar-modal-card pollar-recovery-modal",
9195
+ "data-theme": p.theme,
9196
+ style: cssVars,
9197
+ onClick: (e) => e.stopPropagation(),
9198
+ children: [
9199
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-header", children: [
9200
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "pollar-modal-title", children: "Wallet recovery" }),
9201
+ /* @__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, {}) }) })
9202
+ ] }),
9203
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-recovery-body", children: [
9204
+ body,
9205
+ p.notice && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-notice", role: "status", children: p.notice }),
9206
+ p.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-recovery-error", role: "alert", children: p.error })
8326
9207
  ] }),
8327
9208
  /* @__PURE__ */ jsxRuntime.jsx(PollarModalFooter, {})
8328
9209
  ]
8329
9210
  }
8330
9211
  );
8331
9212
  }
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
- ] });
9213
+ function errorText2(err) {
9214
+ return core.cosignRefusalMessage(err) ?? core.transactionErrorMessage(core.isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null);
8347
9215
  }
8348
- function CosignModal({ onClose }) {
8349
- const { getClient, styles, network } = usePollar();
9216
+ function RecoveryModal({ onClose }) {
9217
+ const { getClient, styles, refreshRecovery, appConfig } = usePollar();
8350
9218
  const { theme, accentColor, styleOverrides, overlayStyle } = modalChrome(styles);
8351
- const { pending, lastResolved, approve, reject, verify, inspectQuorum } = useCosign();
8352
- const [selectedId, setSelectedId] = react.useState(null);
8353
- const [verification, setVerification] = react.useState(null);
9219
+ const recovery = getClient().cosign.recovery;
9220
+ const [state, setState] = react.useState(null);
9221
+ const [devices, setDevices] = react.useState(null);
9222
+ const [thisDeviceKey, setThisDeviceKey] = react.useState(null);
9223
+ const [view, setView] = react.useState("overview");
8354
9224
  const [busy, setBusy] = react.useState(null);
8355
9225
  const [error, setError] = react.useState(null);
8356
- const [approveFailedFor, setApproveFailedFor] = react.useState(null);
8357
- const [quorumFailure, setQuorumFailure] = react.useState(null);
8358
- react.useEffect(() => {
8359
- let cancelled = false;
8360
- inspectQuorum().then((q) => {
8361
- if (!cancelled && !q.ok) setQuorumFailure(q.reason);
8362
- }).catch(() => void 0);
8363
- return () => {
8364
- cancelled = true;
8365
- };
8366
- }, [inspectQuorum]);
8367
- const current = pending.find((r) => r.id === selectedId) ?? pending[0] ?? null;
8368
- const currentRef = react.useRef(current);
8369
- currentRef.current = current;
8370
- const currentId = current?.id ?? null;
9226
+ const [notice, setNotice] = react.useState(null);
9227
+ const [emailDraft, setEmailDraft] = react.useState("");
9228
+ const [codeSent, setCodeSent] = react.useState(false);
9229
+ const [phrase, setPhrase] = react.useState(null);
9230
+ const load = react.useCallback(async () => {
9231
+ const client = getClient();
9232
+ const [nextState, nextDevices, key] = await Promise.all([
9233
+ client.cosign.recovery.status(),
9234
+ client.cosign.devices(),
9235
+ client.cosign.publicKeyHex()
9236
+ ]);
9237
+ setState(nextState);
9238
+ setDevices(nextDevices);
9239
+ setThisDeviceKey(key.toLowerCase());
9240
+ if (nextState.email.address) setEmailDraft((d) => d || nextState.email.address);
9241
+ refreshRecovery();
9242
+ }, [getClient, refreshRecovery]);
8371
9243
  react.useEffect(() => {
8372
- const request = currentRef.current;
8373
- if (!request) return;
8374
- setVerification(null);
8375
- setError(null);
8376
- setApproveFailedFor(null);
8377
- let cancelled = false;
8378
- verify(request).then((v) => {
8379
- if (!cancelled) setVerification(v);
8380
- }).catch((err) => {
8381
- if (!cancelled) setVerification({ ok: false, reason: err instanceof Error ? err.message : String(err) });
8382
- });
8383
- return () => {
8384
- cancelled = true;
8385
- };
8386
- }, [currentId, verify]);
9244
+ load().catch((err) => setError(errorText2(err)));
9245
+ }, [load]);
8387
9246
  const onCloseRef = react.useRef(onClose);
8388
9247
  onCloseRef.current = onClose;
8389
- react.useEffect(() => {
8390
- return getClient().onAuthStateChange((authState) => {
9248
+ react.useEffect(
9249
+ () => getClient().onAuthStateChange((authState) => {
8391
9250
  if (authState.step === "idle") onCloseRef.current();
8392
- });
8393
- }, [getClient]);
9251
+ }),
9252
+ [getClient]
9253
+ );
8394
9254
  const run = react.useCallback(
8395
- async (kind, request, action) => {
8396
- setBusy(kind);
9255
+ async (key, action, after) => {
9256
+ setBusy(key);
8397
9257
  setError(null);
9258
+ setNotice(null);
8398
9259
  try {
8399
- await action(request);
9260
+ await action();
9261
+ await load();
9262
+ if (after?.notice) setNotice(after.notice);
9263
+ if (after?.view) setView(after.view);
8400
9264
  } catch (err) {
8401
- setError(core.transactionErrorMessage(core.isPollarApiError(err) ? err.code : null, err instanceof Error ? err.message : null));
8402
- if (kind === "approving") setApproveFailedFor(request.id);
9265
+ setError(errorText2(err));
8403
9266
  } finally {
8404
9267
  setBusy(null);
8405
9268
  }
8406
9269
  },
8407
- []
9270
+ [load]
8408
9271
  );
9272
+ const enrolled = !!thisDeviceKey && !!devices?.devices.some((d) => d.publicKey.toLowerCase() === thisDeviceKey);
8409
9273
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-overlay", style: overlayStyle, onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsx(
8410
- CosignModalTemplate,
9274
+ RecoveryModalTemplate,
8411
9275
  {
8412
9276
  theme,
8413
9277
  accentColor,
8414
9278
  styleOverrides,
8415
- pending,
8416
- current,
8417
- verification: current ? quorumFailure ? { ok: false, reason: quorumFailure } : verification : null,
9279
+ state,
9280
+ devices,
9281
+ thisDeviceKey,
9282
+ thisDeviceEnrolled: enrolled,
9283
+ passkeyAvailable: recovery.passkeyAvailable,
9284
+ selfCustodyRisks: appConfig.application?.walletCustodyMode === "SELF_CUSTODY" && devices?.custody === "SELF_CUSTODY",
9285
+ view,
8418
9286
  busy,
8419
9287
  error,
8420
- approveBlocked: !!current && approveFailedFor === current.id,
8421
- ...lastResolved ? { lastResolved } : {},
8422
- explorerUrl: lastResolved?.submit?.hash ? explorerTxUrl(lastResolved.network, lastResolved.submit.hash, network) : null,
8423
- onSelect: (r) => setSelectedId(r.id),
8424
- onApprove: (r) => void run("approving", r, approve),
8425
- onReject: (r) => void run("rejecting", r, reject),
9288
+ notice,
9289
+ emailDraft,
9290
+ codeSent,
9291
+ phrase,
9292
+ onView: (next) => {
9293
+ setError(null);
9294
+ setNotice(null);
9295
+ setCodeSent(false);
9296
+ if (next !== "export") setPhrase(null);
9297
+ setView(next);
9298
+ },
9299
+ onEmailDraft: setEmailDraft,
9300
+ onAddPasskey: () => void run("passkey", () => recovery.addPasskey(), { notice: "Passkey added." }),
9301
+ onRemovePasskey: (id) => void run(`passkey:${id}`, () => recovery.removePasskey(id)),
9302
+ onSendEmailCode: () => void run("email-send", async () => {
9303
+ await recovery.startEmail(emailDraft.trim());
9304
+ setCodeSent(true);
9305
+ }),
9306
+ onConfirmEmail: (code) => void run("email-confirm", () => recovery.confirmEmail(code.trim()), { notice: "Email confirmed.", view: "overview" }),
9307
+ onApproveDevice: (id) => void run(`approve:${id}`, () => recovery.approveDevice(id), { notice: "Device added." }),
9308
+ onRejectDevice: (id) => void run(`reject:${id}`, () => recovery.rejectDevice(id)),
9309
+ onRequestEnrollment: () => void run("enroll", () => recovery.requestDeviceEnrollment(), {
9310
+ notice: "Request sent. Approve it from a device that already has this wallet within 15 minutes."
9311
+ }),
9312
+ onRecoverWithPasskey: () => void run("recover-passkey", () => recovery.recoverWithPasskey(), {
9313
+ notice: "This device is part of your wallet again."
9314
+ }),
9315
+ onSendRecoveryCode: () => void run("recover-send", async () => {
9316
+ await recovery.startEmailRecovery();
9317
+ setCodeSent(true);
9318
+ }),
9319
+ onRecoverWithCode: (code) => void run("recover-code", () => recovery.recoverWithEmailCode(code.trim()), {
9320
+ notice: "This device is part of your wallet again.",
9321
+ view: "overview"
9322
+ }),
9323
+ onExport: () => void run("export", async () => {
9324
+ setPhrase(await recovery.exportWallet());
9325
+ }),
8426
9326
  onClose
8427
9327
  }
8428
9328
  ) });
@@ -8453,7 +9353,7 @@ function detailRows2(confirmation) {
8453
9353
  push("Network", confirmation.network);
8454
9354
  return rows;
8455
9355
  }
8456
- function CloseIcon3() {
9356
+ function CloseIcon4() {
8457
9357
  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
9358
  }
8459
9359
  function ConfirmModal({ confirmation }) {
@@ -8477,7 +9377,7 @@ function ConfirmModal({ confirmation }) {
8477
9377
  children: [
8478
9378
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-modal-header", children: [
8479
9379
  /* @__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, {}) }) })
9380
+ /* @__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
9381
  ] }),
8482
9382
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-cosign-detail", children: [
8483
9383
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "pollar-cosign-detail-title", children: labelOf2(confirmation) }),
@@ -8614,6 +9514,25 @@ function formatDate(iso) {
8614
9514
  function truncateMiddle(value, head = 4, tail = 4) {
8615
9515
  return value.length <= head + tail + 1 ? value : `${value.slice(0, head)}\u2026${value.slice(-tail)}`;
8616
9516
  }
9517
+ function formatFee(fee) {
9518
+ if (fee.unit === "lamports") {
9519
+ try {
9520
+ return `${core.fromBaseUnits(fee.amount, 9)} SOL`;
9521
+ } catch {
9522
+ }
9523
+ }
9524
+ return `${fee.amount} ${fee.unit}`;
9525
+ }
9526
+ function formatAmount4(details) {
9527
+ const { amount, decimals, symbol } = details ?? {};
9528
+ if (typeof amount !== "string" || typeof decimals !== "number") return null;
9529
+ try {
9530
+ const value = core.fromBaseUnits(amount, decimals);
9531
+ return typeof symbol === "string" ? `${value} ${symbol}` : value;
9532
+ } catch {
9533
+ return null;
9534
+ }
9535
+ }
8617
9536
  var STELLAR_ADDRESS = /\b[GC][A-Z2-7]{55}\b/g;
8618
9537
  function AddressChip({ value, label }) {
8619
9538
  return /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-hist-item-issuer", children: [
@@ -8751,15 +9670,17 @@ function TxHistoryModalTemplate({
8751
9670
  ] }),
8752
9671
  /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "pollar-hist-item-meta", children: [
8753
9672
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: record.operation }),
9673
+ record.chain !== "STELLAR" && formatAmount4(record.details) && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
9674
+ "\xB7 ",
9675
+ formatAmount4(record.details)
9676
+ ] }),
8754
9677
  typeof record.details?.sponsored === "boolean" && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
8755
9678
  "\xB7 ",
8756
9679
  record.details.sponsored ? "Sponsored" : "Self-paid"
8757
9680
  ] }),
8758
9681
  record.fee && /* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
8759
9682
  "\xB7 ",
8760
- record.fee.amount,
8761
- " ",
8762
- record.fee.unit
9683
+ formatFee(record.fee)
8763
9684
  ] })
8764
9685
  ] }),
8765
9686
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-hist-item-footer", children: [
@@ -9070,6 +9991,48 @@ var browserPasskeySigner = async ({ credentialId, challenge }) => {
9070
9991
  signature: response.signature
9071
9992
  };
9072
9993
  };
9994
+ var browserRecoveryPasskey = {
9995
+ async create({ challenge, userName }) {
9996
+ const rpId = window.location.hostname;
9997
+ const response = await browser.startRegistration({
9998
+ optionsJSON: {
9999
+ challenge,
10000
+ rp: { id: rpId, name: rpId },
10001
+ user: { id: randomUserId(), name: userName, displayName: userName },
10002
+ pubKeyCredParams: [
10003
+ { type: "public-key", alg: -7 },
10004
+ { type: "public-key", alg: -257 }
10005
+ ],
10006
+ authenticatorSelection: { residentKey: "preferred", userVerification: "preferred" },
10007
+ attestation: "none",
10008
+ timeout: 6e4
10009
+ }
10010
+ });
10011
+ return {
10012
+ credentialId: response.id,
10013
+ clientDataJson: response.response.clientDataJSON,
10014
+ attestationObject: response.response.attestationObject,
10015
+ transports: response.response.transports ?? []
10016
+ };
10017
+ },
10018
+ async get({ challenge, allowCredentials }) {
10019
+ const rpId = window.location.hostname;
10020
+ const response = await browser.startAuthentication({
10021
+ optionsJSON: {
10022
+ challenge,
10023
+ rpId,
10024
+ allowCredentials: allowCredentials.map((id) => ({ id, type: "public-key" })),
10025
+ userVerification: "preferred"
10026
+ }
10027
+ });
10028
+ return {
10029
+ credentialId: response.id,
10030
+ authenticatorData: response.response.authenticatorData,
10031
+ clientDataJson: response.response.clientDataJSON,
10032
+ signature: response.response.signature
10033
+ };
10034
+ }
10035
+ };
9073
10036
  var DEFAULT_APP_CONFIG = {
9074
10037
  application: { name: "", network: "testnet", chains: [] },
9075
10038
  styles: {}
@@ -9094,7 +10057,11 @@ function PollarProvider({
9094
10057
  }) {
9095
10058
  const [pollarClient] = react.useState(() => {
9096
10059
  if (core.isPollarClient(client)) {
9097
- client.setPasskeyDefaults({ passkey: browserPasskeyCeremony, passkeySign: browserPasskeySigner });
10060
+ client.setPasskeyDefaults({
10061
+ passkey: browserPasskeyCeremony,
10062
+ passkeySign: browserPasskeySigner,
10063
+ recoveryPasskey: browserRecoveryPasskey
10064
+ });
9098
10065
  return client;
9099
10066
  }
9100
10067
  const alreadyBuilt = clientByConfig.get(client);
@@ -9102,7 +10069,8 @@ function PollarProvider({
9102
10069
  const built = new core.PollarClient({
9103
10070
  ...client,
9104
10071
  passkey: client.passkey ?? browserPasskeyCeremony,
9105
- passkeySign: client.passkeySign ?? browserPasskeySigner
10072
+ passkeySign: client.passkeySign ?? browserPasskeySigner,
10073
+ recoveryPasskey: client.recoveryPasskey ?? browserRecoveryPasskey
9106
10074
  });
9107
10075
  clientByConfig.set(client, built);
9108
10076
  return built;
@@ -9257,6 +10225,8 @@ function PollarProvider({
9257
10225
  const [receiveModalOpen, setReceiveModalOpen] = react.useState(false);
9258
10226
  const [sessionsModalOpen, setSessionsModalOpen] = react.useState(false);
9259
10227
  const [cosignModalOpen, setCosignModalOpen] = react.useState(false);
10228
+ const [cosignNextStep, setCosignNextStep] = react.useState(null);
10229
+ const [recoveryModalOpen, setRecoveryModalOpen] = react.useState(false);
9260
10230
  const cosignPendingCount = react.useRef(0);
9261
10231
  react.useEffect(() => {
9262
10232
  if (cosign.pending.length > cosignPendingCount.current) setCosignModalOpen(true);
@@ -9279,6 +10249,7 @@ function PollarProvider({
9279
10249
  setReceiveModalOpen(false);
9280
10250
  setSessionsModalOpen(false);
9281
10251
  setCosignModalOpen(false);
10252
+ setRecoveryModalOpen(false);
9282
10253
  setDistributionRulesModalOpen(false);
9283
10254
  }),
9284
10255
  [pollarClient]
@@ -9287,6 +10258,45 @@ function PollarProvider({
9287
10258
  const walletProvisioning = sessionState?.wallet?.provisioning;
9288
10259
  const hasSession = sessionState !== null;
9289
10260
  const getClient = react.useCallback(() => pollarClient, [pollarClient]);
10261
+ const [recovery, setRecovery] = react.useState(null);
10262
+ const [recoveryTick, setRecoveryTick] = react.useState(0);
10263
+ const refreshRecovery = react.useCallback(() => setRecoveryTick((t) => t + 1), []);
10264
+ react.useEffect(() => {
10265
+ if (!hasSession) {
10266
+ setRecovery(null);
10267
+ return;
10268
+ }
10269
+ let cancelled = false;
10270
+ pollarClient.cosign.recovery.status().then((next) => {
10271
+ if (!cancelled) setRecovery(next);
10272
+ }).catch(() => {
10273
+ if (!cancelled) setRecovery(null);
10274
+ });
10275
+ return () => {
10276
+ cancelled = true;
10277
+ };
10278
+ }, [pollarClient, hasSession, walletAddress, recoveryTick]);
10279
+ const recoveryLocked = !!recovery && recovery.required && !recovery.configured;
10280
+ const [custodyMigration, setCustodyMigration] = react.useState(null);
10281
+ const [custodyMigrationTick, setCustodyMigrationTick] = react.useState(0);
10282
+ const refreshCustodyMigration = react.useCallback(() => setCustodyMigrationTick((t) => t + 1), []);
10283
+ const [custodyMigrationLater, setCustodyMigrationLater] = react.useState(false);
10284
+ react.useEffect(() => {
10285
+ if (!hasSession) {
10286
+ setCustodyMigration(null);
10287
+ setCustodyMigrationLater(false);
10288
+ return;
10289
+ }
10290
+ let cancelled = false;
10291
+ pollarClient.cosign.custodyMigration().then((next) => {
10292
+ if (!cancelled) setCustodyMigration(next);
10293
+ }).catch(() => {
10294
+ if (!cancelled) setCustodyMigration(null);
10295
+ });
10296
+ return () => {
10297
+ cancelled = true;
10298
+ };
10299
+ }, [pollarClient, hasSession, walletAddress, custodyMigrationTick]);
9290
10300
  const refreshWalletBalance = react.useCallback(() => pollarClient.refreshBalance(), [pollarClient, walletAddress]);
9291
10301
  const refreshAssets = react.useCallback(() => pollarClient.refreshAssets(), [pollarClient, walletAddress]);
9292
10302
  const contextValue = react.useMemo(() => {
@@ -9297,7 +10307,7 @@ function PollarProvider({
9297
10307
  wallets: pollarClient.getWallets(),
9298
10308
  isAuthenticated: hasSession,
9299
10309
  hasWallet: pollarClient.hasWallet(),
9300
- createWallet: () => pollarClient.createWallet(),
10310
+ createWallet: (options) => pollarClient.createWallet(options),
9301
10311
  verified,
9302
10312
  // client
9303
10313
  getClient,
@@ -9366,6 +10376,13 @@ function PollarProvider({
9366
10376
  // cosign
9367
10377
  cosign,
9368
10378
  openCosignModal: () => setCosignModalOpen(true),
10379
+ setCosignNextStep,
10380
+ recovery,
10381
+ recoveryLocked,
10382
+ refreshRecovery,
10383
+ openRecoveryModal: () => setRecoveryModalOpen(true),
10384
+ custodyMigration,
10385
+ refreshCustodyMigration,
9369
10386
  confirmations,
9370
10387
  // distribution
9371
10388
  openDistributionRulesModal: () => setDistributionRulesModalOpen(true),
@@ -9389,6 +10406,11 @@ function PollarProvider({
9389
10406
  }, [
9390
10407
  walletAddress,
9391
10408
  walletProvisioning,
10409
+ recovery,
10410
+ recoveryLocked,
10411
+ refreshRecovery,
10412
+ custodyMigration,
10413
+ refreshCustodyMigration,
9392
10414
  hasSession,
9393
10415
  verified,
9394
10416
  pollarClient,
@@ -9445,7 +10467,32 @@ function PollarProvider({
9445
10467
  /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setCardModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(CardModal, { onClose: () => setCardModalOpen(false) }) }, `${cardModalKey}:${walletAddress}`),
9446
10468
  receiveModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setReceiveModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(ReceiveModal, { onClose: () => setReceiveModalOpen(false) }) }),
9447
10469
  sessionsModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setSessionsModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(SessionsModal, { onClose: () => setSessionsModalOpen(false) }) }),
9448
- cosignModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setCosignModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(CosignModal, { onClose: () => setCosignModalOpen(false) }) }),
10470
+ cosignModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setCosignModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(CosignModal, { nextStep: cosignNextStep, onClose: () => setCosignModalOpen(false) }) }),
10471
+ custodyMigration?.required && !custodyMigrationLater && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: refreshCustodyMigration, children: /* @__PURE__ */ jsxRuntime.jsx(
10472
+ CustodyMigrationModal,
10473
+ {
10474
+ state: custodyMigration,
10475
+ onMoved: () => {
10476
+ refreshCustodyMigration();
10477
+ refreshRecovery();
10478
+ setRecoveryModalOpen(true);
10479
+ },
10480
+ onLater: () => {
10481
+ setCustodyMigrationLater(true);
10482
+ refreshCustodyMigration();
10483
+ refreshRecovery();
10484
+ }
10485
+ }
10486
+ ) }),
10487
+ recoveryModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setRecoveryModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(
10488
+ RecoveryModal,
10489
+ {
10490
+ onClose: () => {
10491
+ setRecoveryModalOpen(false);
10492
+ refreshRecovery();
10493
+ }
10494
+ }
10495
+ ) }),
9449
10496
  firstConfirmation && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: firstConfirmation.reject, children: /* @__PURE__ */ jsxRuntime.jsx(ConfirmModal, { confirmation: firstConfirmation }, firstConfirmation.id) }),
9450
10497
  distributionRulesModalOpen && /* @__PURE__ */ jsxRuntime.jsx(ModalErrorBoundary, { onClose: () => setDistributionRulesModalOpen(false), children: /* @__PURE__ */ jsxRuntime.jsx(DistributionRulesModal, { onClose: () => setDistributionRulesModalOpen(false) }) })
9451
10498
  ] });
@@ -9477,8 +10524,9 @@ function createPollarAdapterHook(key) {
9477
10524
  );
9478
10525
  };
9479
10526
  }
9480
- function ButtonLogo() {
9481
- return /* @__PURE__ */ jsxRuntime.jsx("img", { src: LOGO_POLLAR, alt: "Pollar", width: 22, height: 22, className: "pollar-wallet-btn-logo" });
10527
+ function ButtonLogo({ accentColor }) {
10528
+ const src = readableTextOn(accentColor) === "#ffffff" ? LOGO_POLLAR_MARK_ON_DARK : LOGO_POLLAR_MARK_ON_LIGHT;
10529
+ return /* @__PURE__ */ jsxRuntime.jsx("img", { src, alt: "Pollar", width: 22, height: 22, className: "pollar-wallet-btn-logo" });
9482
10530
  }
9483
10531
  function cropWallet(address) {
9484
10532
  if (address.length <= 12) return address;
@@ -9486,16 +10534,17 @@ function cropWallet(address) {
9486
10534
  }
9487
10535
  function WalletButtonTemplate({
9488
10536
  walletAddress,
10537
+ theme,
9489
10538
  accentColor,
10539
+ styleOverrides,
9490
10540
  open,
9491
10541
  copied,
9492
- dropdownBg,
9493
- dropdownBorder,
9494
- itemColor,
9495
10542
  wrapperRef,
9496
10543
  isInProgress,
9497
10544
  showCreateAccount,
9498
10545
  notReadyReason,
10546
+ recoveryLocked,
10547
+ onRecovery,
9499
10548
  creatingAccount,
9500
10549
  onToggleOpen,
9501
10550
  onCreateAccount,
@@ -9513,11 +10562,15 @@ function WalletButtonTemplate({
9513
10562
  signedIn,
9514
10563
  creatingWallet,
9515
10564
  createWalletError,
10565
+ custodyChoice,
9516
10566
  onCreateWallet
9517
10567
  }) {
10568
+ const [selfCustodyNotice, setSelfCustodyNotice] = react.useState(false);
10569
+ const [risksAccepted, setRisksAccepted] = react.useState(false);
10570
+ const cssVars = buildModalCssVars(theme, accentColor, styleOverrides);
9518
10571
  if (!walletAddress && signedIn) {
9519
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-wrapper", ref: wrapperRef, children: [
9520
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-btn", style: { backgroundColor: accentColor }, onClick: onToggleOpen, children: [
10572
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-wrapper", ref: wrapperRef, style: cssVars, children: [
10573
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-btn", onClick: onToggleOpen, children: [
9521
10574
  /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pollar-wallet-btn-label", children: onCreateWallet ? "No wallet" : "Signed in" }),
9522
10575
  /* @__PURE__ */ jsxRuntime.jsx(
9523
10576
  "svg",
@@ -9533,42 +10586,93 @@ function WalletButtonTemplate({
9533
10586
  }
9534
10587
  )
9535
10588
  ] }),
9536
- open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-dropdown", style: { backgroundColor: dropdownBg, borderColor: dropdownBorder }, children: [
10589
+ open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-dropdown", children: [
9537
10590
  createWalletError && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9538
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "status", children: createWalletError }),
10591
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", role: "status", children: createWalletError }),
10592
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
10593
+ ] }),
10594
+ onCreateWallet && !custodyChoice && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10595
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-wallet-dropdown-item", onClick: () => onCreateWallet(), disabled: creatingWallet, children: creatingWallet ? "Creating wallet\u2026" : "Create wallet" }),
10596
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
10597
+ ] }),
10598
+ onCreateWallet && custodyChoice && !selfCustodyNotice && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10599
+ /* @__PURE__ */ jsxRuntime.jsx(
10600
+ "button",
10601
+ {
10602
+ className: "pollar-wallet-dropdown-item",
10603
+ onClick: () => onCreateWallet("COSIGNED"),
10604
+ disabled: creatingWallet,
10605
+ children: creatingWallet ? "Creating wallet\u2026" : "Create co-signed wallet"
10606
+ }
10607
+ ),
10608
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", children: "You and the app approve every signature together. Your wallet can be recovered with help." }),
10609
+ /* @__PURE__ */ jsxRuntime.jsx(
10610
+ "button",
10611
+ {
10612
+ className: "pollar-wallet-dropdown-item",
10613
+ onClick: () => setSelfCustodyNotice(true),
10614
+ disabled: creatingWallet,
10615
+ children: "Create self-custody wallet"
10616
+ }
10617
+ ),
10618
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", children: "Only your device signs. Nobody else can move or recover your funds." }),
9539
10619
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
9540
10620
  ] }),
9541
- onCreateWallet && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10621
+ onCreateWallet && custodyChoice && selfCustodyNotice && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10622
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", 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." }),
10623
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "pollar-wallet-dropdown-notice", children: [
10624
+ /* @__PURE__ */ jsxRuntime.jsx(
10625
+ "input",
10626
+ {
10627
+ type: "checkbox",
10628
+ checked: risksAccepted,
10629
+ onChange: (e) => setRisksAccepted(e.target.checked),
10630
+ disabled: creatingWallet
10631
+ }
10632
+ ),
10633
+ " ",
10634
+ "I understand and accept these risks"
10635
+ ] }),
10636
+ /* @__PURE__ */ jsxRuntime.jsx(
10637
+ "button",
10638
+ {
10639
+ className: "pollar-wallet-dropdown-item",
10640
+ onClick: () => onCreateWallet("SELF_CUSTODY"),
10641
+ disabled: creatingWallet || !risksAccepted,
10642
+ children: creatingWallet ? "Creating wallet\u2026" : "Create self-custody wallet"
10643
+ }
10644
+ ),
9542
10645
  /* @__PURE__ */ jsxRuntime.jsx(
9543
10646
  "button",
9544
10647
  {
9545
10648
  className: "pollar-wallet-dropdown-item",
9546
- style: { color: itemColor },
9547
- onClick: onCreateWallet,
10649
+ onClick: () => {
10650
+ setSelfCustodyNotice(false);
10651
+ setRisksAccepted(false);
10652
+ },
9548
10653
  disabled: creatingWallet,
9549
- children: creatingWallet ? "Creating wallet\u2026" : "Create wallet"
10654
+ children: "Back"
9550
10655
  }
9551
10656
  ),
9552
10657
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
9553
10658
  ] }),
9554
- /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onSessions, children: "Active sessions" }),
10659
+ /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-wallet-dropdown-item", onClick: onSessions, children: "Active sessions" }),
9555
10660
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" }),
9556
10661
  /* @__PURE__ */ jsxRuntime.jsx("button", { className: "pollar-wallet-dropdown-item pollar-danger", onClick: onLogout, children: "Log out" })
9557
10662
  ] })
9558
10663
  ] });
9559
10664
  }
9560
10665
  if (!walletAddress) {
9561
- return /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "pollar-wallet-login-btn", style: { backgroundColor: accentColor }, onClick: onLogin, children: [
9562
- /* @__PURE__ */ jsxRuntime.jsx(ButtonLogo, {}),
10666
+ return /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", className: "pollar-wallet-login-btn", style: cssVars, onClick: onLogin, children: [
10667
+ /* @__PURE__ */ jsxRuntime.jsx(ButtonLogo, { accentColor }),
9563
10668
  "Login with Pollar"
9564
10669
  ] });
9565
10670
  }
9566
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-wrapper", ref: wrapperRef, children: [
10671
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-wrapper", ref: wrapperRef, style: cssVars, children: [
9567
10672
  /* @__PURE__ */ jsxRuntime.jsxs(
9568
10673
  "button",
9569
10674
  {
9570
10675
  className: "pollar-wallet-btn",
9571
- style: { backgroundColor: accentColor },
9572
10676
  onClick: isInProgress ? void 0 : onToggleOpen,
9573
10677
  disabled: isInProgress,
9574
10678
  "aria-busy": isInProgress,
@@ -9605,46 +10709,41 @@ function WalletButtonTemplate({
9605
10709
  ]
9606
10710
  }
9607
10711
  ),
9608
- open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-dropdown", style: { backgroundColor: dropdownBg, borderColor: dropdownBorder }, children: [
10712
+ open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pollar-wallet-dropdown", children: [
10713
+ recoveryLocked && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
10714
+ /* @__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." }),
10715
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
10716
+ ] }),
9609
10717
  notReadyReason && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9610
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", style: { color: itemColor }, role: "status", children: notReadyReason }),
10718
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-notice", role: "status", children: notReadyReason }),
9611
10719
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
9612
10720
  ] }),
9613
10721
  showCreateAccount && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
9614
- /* @__PURE__ */ jsxRuntime.jsxs(
9615
- "button",
9616
- {
9617
- className: "pollar-wallet-dropdown-item",
9618
- style: { color: itemColor },
9619
- onClick: onCreateAccount,
9620
- disabled: creatingAccount,
9621
- children: [
9622
- /* @__PURE__ */ jsxRuntime.jsxs(
9623
- "svg",
9624
- {
9625
- width: "14",
9626
- height: "14",
9627
- viewBox: "0 0 24 24",
9628
- fill: "none",
9629
- stroke: "currentColor",
9630
- strokeWidth: "2",
9631
- strokeLinecap: "round",
9632
- strokeLinejoin: "round",
9633
- children: [
9634
- /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
9635
- /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "7", r: "4" }),
9636
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "19", y1: "8", x2: "19", y2: "14" }),
9637
- /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "22", y1: "11", x2: "16", y2: "11" })
9638
- ]
9639
- }
9640
- ),
9641
- creatingAccount ? "Creating account\u2026" : "Create account"
9642
- ]
9643
- }
9644
- ),
10722
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onCreateAccount, disabled: creatingAccount, children: [
10723
+ /* @__PURE__ */ jsxRuntime.jsxs(
10724
+ "svg",
10725
+ {
10726
+ width: "14",
10727
+ height: "14",
10728
+ viewBox: "0 0 24 24",
10729
+ fill: "none",
10730
+ stroke: "currentColor",
10731
+ strokeWidth: "2",
10732
+ strokeLinecap: "round",
10733
+ strokeLinejoin: "round",
10734
+ children: [
10735
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" }),
10736
+ /* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "9", cy: "7", r: "4" }),
10737
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "19", y1: "8", x2: "19", y2: "14" }),
10738
+ /* @__PURE__ */ jsxRuntime.jsx("line", { x1: "22", y1: "11", x2: "16", y2: "11" })
10739
+ ]
10740
+ }
10741
+ ),
10742
+ creatingAccount ? "Creating account\u2026" : "Create account"
10743
+ ] }),
9645
10744
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" })
9646
10745
  ] }),
9647
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onSend, children: [
10746
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onSend, children: [
9648
10747
  /* @__PURE__ */ jsxRuntime.jsxs(
9649
10748
  "svg",
9650
10749
  {
@@ -9664,7 +10763,7 @@ function WalletButtonTemplate({
9664
10763
  ),
9665
10764
  "Send"
9666
10765
  ] }),
9667
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onReceive, children: [
10766
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onReceive, children: [
9668
10767
  /* @__PURE__ */ jsxRuntime.jsxs(
9669
10768
  "svg",
9670
10769
  {
@@ -9686,7 +10785,7 @@ function WalletButtonTemplate({
9686
10785
  "Receive"
9687
10786
  ] }),
9688
10787
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" }),
9689
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onCopy, children: [
10788
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onCopy, children: [
9690
10789
  /* @__PURE__ */ jsxRuntime.jsxs(
9691
10790
  "svg",
9692
10791
  {
@@ -9706,7 +10805,7 @@ function WalletButtonTemplate({
9706
10805
  ),
9707
10806
  copied ? "Copied!" : "Copy address"
9708
10807
  ] }),
9709
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onWalletBalance, children: [
10808
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onWalletBalance, children: [
9710
10809
  /* @__PURE__ */ jsxRuntime.jsxs(
9711
10810
  "svg",
9712
10811
  {
@@ -9727,7 +10826,7 @@ function WalletButtonTemplate({
9727
10826
  ),
9728
10827
  "Wallet balance"
9729
10828
  ] }),
9730
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onTxHistory, children: [
10829
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onTxHistory, children: [
9731
10830
  /* @__PURE__ */ jsxRuntime.jsxs(
9732
10831
  "svg",
9733
10832
  {
@@ -9751,7 +10850,7 @@ function WalletButtonTemplate({
9751
10850
  "Transaction history"
9752
10851
  ] }),
9753
10852
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" }),
9754
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onRamp, children: [
10853
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onRamp, children: [
9755
10854
  /* @__PURE__ */ jsxRuntime.jsxs(
9756
10855
  "svg",
9757
10856
  {
@@ -9773,7 +10872,7 @@ function WalletButtonTemplate({
9773
10872
  ),
9774
10873
  "Buy / Sell"
9775
10874
  ] }),
9776
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onKyc, children: [
10875
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onKyc, children: [
9777
10876
  /* @__PURE__ */ jsxRuntime.jsxs(
9778
10877
  "svg",
9779
10878
  {
@@ -9794,7 +10893,7 @@ function WalletButtonTemplate({
9794
10893
  ),
9795
10894
  "Identity verification"
9796
10895
  ] }),
9797
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onDistributionRules, children: [
10896
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onDistributionRules, children: [
9798
10897
  /* @__PURE__ */ jsxRuntime.jsxs(
9799
10898
  "svg",
9800
10899
  {
@@ -9817,7 +10916,7 @@ function WalletButtonTemplate({
9817
10916
  ),
9818
10917
  "Distribution rules"
9819
10918
  ] }),
9820
- /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", style: { color: itemColor }, onClick: onSessions, children: [
10919
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item", onClick: onSessions, children: [
9821
10920
  /* @__PURE__ */ jsxRuntime.jsxs(
9822
10921
  "svg",
9823
10922
  {
@@ -9838,6 +10937,23 @@ function WalletButtonTemplate({
9838
10937
  ),
9839
10938
  "Active sessions"
9840
10939
  ] }),
10940
+ onRecovery && /* @__PURE__ */ jsxRuntime.jsxs("button", { className: `pollar-wallet-dropdown-item${recoveryLocked ? " pollar-danger" : ""}`, onClick: onRecovery, children: [
10941
+ /* @__PURE__ */ jsxRuntime.jsx(
10942
+ "svg",
10943
+ {
10944
+ width: "14",
10945
+ height: "14",
10946
+ viewBox: "0 0 24 24",
10947
+ fill: "none",
10948
+ stroke: "currentColor",
10949
+ strokeWidth: "2",
10950
+ strokeLinecap: "round",
10951
+ strokeLinejoin: "round",
10952
+ 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" })
10953
+ }
10954
+ ),
10955
+ recoveryLocked ? "Set up wallet recovery" : "Wallet recovery"
10956
+ ] }),
9841
10957
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pollar-wallet-dropdown-divider" }),
9842
10958
  /* @__PURE__ */ jsxRuntime.jsxs("button", { className: "pollar-wallet-dropdown-item pollar-danger", onClick: onLogout, children: [
9843
10959
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -9880,7 +10996,10 @@ function WalletButton() {
9880
10996
  tx: transaction,
9881
10997
  isAuthenticated,
9882
10998
  createWallet,
9883
- appConfig
10999
+ appConfig,
11000
+ recovery,
11001
+ recoveryLocked,
11002
+ openRecoveryModal
9884
11003
  } = usePollar();
9885
11004
  const { primaryChain, primaryAddress } = useChains();
9886
11005
  const walletAddress = primaryAddress || (wallet?.address ?? "");
@@ -9895,11 +11014,7 @@ function WalletButton() {
9895
11014
  const isInProgress = transaction.step === "building" || transaction.step === "signing";
9896
11015
  const canCreateAccount = !created && wallet?.custody === "external" && wallet.existsOnStellar === false && wallet.fundingMode === "IMMEDIATE";
9897
11016
  const notReadyReason = walletNotReadyReason(wallet, primaryChain);
9898
- const { theme = "light", accentColor = "#005DB4" } = styles;
9899
- const isDark = theme === "dark";
9900
- const dropdownBg = isDark ? "#18181b" : "#fff";
9901
- const dropdownBorder = isDark ? "#3f3f46" : "#e4e4e7";
9902
- const itemColor = isDark ? "#fafafa" : "#18181b";
11017
+ const { theme, accentColor, styleOverrides } = modalChrome(styles);
9903
11018
  react.useEffect(() => {
9904
11019
  function handleClickOutside(e) {
9905
11020
  if (wrapperRef.current && !wrapperRef.current.contains(e.target)) {
@@ -9943,12 +11058,12 @@ function WalletButton() {
9943
11058
  setCreating(false);
9944
11059
  }
9945
11060
  }
9946
- async function handleCreateWallet() {
11061
+ async function handleCreateWallet(custody) {
9947
11062
  if (creatingWallet) return;
9948
11063
  setCreatingWallet(true);
9949
11064
  setCreateWalletError(null);
9950
11065
  try {
9951
- await createWallet();
11066
+ await createWallet(custody ? { custody } : void 0);
9952
11067
  setOpen(false);
9953
11068
  } catch (err) {
9954
11069
  setCreateWalletError(
@@ -9978,6 +11093,10 @@ function WalletButton() {
9978
11093
  setOpen(false);
9979
11094
  openSessionsModal();
9980
11095
  }
11096
+ function handleRecovery() {
11097
+ setOpen(false);
11098
+ openRecoveryModal();
11099
+ }
9981
11100
  function handleKyc() {
9982
11101
  setOpen(false);
9983
11102
  openKycModal();
@@ -9994,18 +11113,18 @@ function WalletButton() {
9994
11113
  WalletButtonTemplate,
9995
11114
  {
9996
11115
  walletAddress: walletAddress ?? null,
11116
+ theme,
9997
11117
  accentColor,
11118
+ styleOverrides,
9998
11119
  open,
9999
11120
  copied,
10000
- dropdownBg,
10001
- dropdownBorder,
10002
- itemColor,
10003
11121
  wrapperRef,
10004
11122
  isInProgress,
10005
11123
  showCreateAccount: canCreateAccount,
10006
11124
  signedIn: isAuthenticated,
10007
11125
  creatingWallet,
10008
11126
  createWalletError,
11127
+ custodyChoice: appConfig.application?.walletCustodyMode === "USER_CHOICE",
10009
11128
  ...appConfig.application?.walletProvisioning === "NONE" ? {} : { onCreateWallet: handleCreateWallet },
10010
11129
  notReadyReason,
10011
11130
  creatingAccount: creating,
@@ -10017,6 +11136,8 @@ function WalletButton() {
10017
11136
  onSend: handleSend,
10018
11137
  onReceive: handleReceive,
10019
11138
  onSessions: handleSessions,
11139
+ recoveryLocked,
11140
+ ...recovery?.required ? { onRecovery: handleRecovery } : {},
10020
11141
  onKyc: handleKyc,
10021
11142
  onRamp: handleRamp,
10022
11143
  onDistributionRules: handleDistributionRules,
@@ -10030,6 +11151,8 @@ exports.CardModal = CardModal;
10030
11151
  exports.ChainSelect = ChainSelect;
10031
11152
  exports.CosignModal = CosignModal;
10032
11153
  exports.CosignModalTemplate = CosignModalTemplate;
11154
+ exports.CustodyMigrationModal = CustodyMigrationModal;
11155
+ exports.CustodyMigrationModalTemplate = CustodyMigrationModalTemplate;
10033
11156
  exports.DistributionRulesModal = DistributionRulesModal;
10034
11157
  exports.DistributionRulesModalTemplate = DistributionRulesModalTemplate;
10035
11158
  exports.EarnModal = EarnModal;
@@ -10045,7 +11168,10 @@ exports.RampWidget = RampWidget;
10045
11168
  exports.RampWidgetTemplate = RampWidgetTemplate;
10046
11169
  exports.ReceiveModal = ReceiveModal;
10047
11170
  exports.ReceiveModalTemplate = ReceiveModalTemplate;
11171
+ exports.RecoveryModal = RecoveryModal;
11172
+ exports.RecoveryModalTemplate = RecoveryModalTemplate;
10048
11173
  exports.RegistryCheckModal = RegistryCheckModal;
11174
+ exports.RejectedRouteDisplay = RejectedRouteDisplay;
10049
11175
  exports.RequirementFormModal = RequirementFormModal;
10050
11176
  exports.RouteDisplay = RouteDisplay;
10051
11177
  exports.SendModal = SendModal;
@@ -10064,6 +11190,7 @@ exports.WalletButtonTemplate = WalletButtonTemplate;
10064
11190
  exports.addressForChain = addressForChain;
10065
11191
  exports.browserPasskeyCeremony = browserPasskeyCeremony;
10066
11192
  exports.browserPasskeySigner = browserPasskeySigner;
11193
+ exports.browserRecoveryPasskey = browserRecoveryPasskey;
10067
11194
  exports.buildModalCssVars = buildModalCssVars;
10068
11195
  exports.chainsOf = chainsOf;
10069
11196
  exports.createPollarAdapterHook = createPollarAdapterHook;