@cavos/kit 0.0.2 → 0.0.4

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.
@@ -1,4 +1,4 @@
1
- import { CavosAuth, HttpRecoveryClient, Cavos, generateRecoveryCode, CavosSolana } from '../chunk-PEUQQZB5.mjs';
1
+ import { CavosAuth, PasskeySigner, HttpRecoveryClient, Cavos, generateRecoveryCode, CavosSolana, CavosStellar } from '../chunk-F2J25XSL.mjs';
2
2
  import { createContext, useState, useRef, useEffect, useCallback, useContext } from 'react';
3
3
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
4
 
@@ -105,6 +105,17 @@ var EmailIcon = () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", v
105
105
  /* @__PURE__ */ jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
106
106
  /* @__PURE__ */ jsx("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 01-2.06 0L2 7" })
107
107
  ] });
108
+ var FingerprintIcon = ({ size = 22, color = "currentColor" }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [
109
+ /* @__PURE__ */ jsx("path", { d: "M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4" }),
110
+ /* @__PURE__ */ jsx("path", { d: "M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2" }),
111
+ /* @__PURE__ */ jsx("path", { d: "M17.29 21.02c.12-.6.43-2.3.5-3.02" }),
112
+ /* @__PURE__ */ jsx("path", { d: "M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" }),
113
+ /* @__PURE__ */ jsx("path", { d: "M8.65 22c.21-.66.45-1.32.57-2" }),
114
+ /* @__PURE__ */ jsx("path", { d: "M14 13.12c0 2.38 0 6.38-1 8.88" }),
115
+ /* @__PURE__ */ jsx("path", { d: "M2 16h.01" }),
116
+ /* @__PURE__ */ jsx("path", { d: "M21.8 16c.2-2 .131-5.354 0-6" }),
117
+ /* @__PURE__ */ jsx("path", { d: "M9 6.8a6 6 0 0 1 9 5.2c0 .47 0 1.17-.02 2" })
118
+ ] });
108
119
  var CavosLogo = ({ invert }) => /* @__PURE__ */ jsx("img", { src: `data:image/png;base64,${cavosLogoBase64}`, alt: "Cavos", style: { width: "auto", height: "16px", objectFit: "contain", opacity: 0.55, flexShrink: 0, filter: invert ? "invert(1)" : "none" } });
109
120
  function Spinner({ size = 16, color = "#888" }) {
110
121
  return /* @__PURE__ */ jsxs(
@@ -255,7 +266,11 @@ function CavosAuthModal({
255
266
  authError,
256
267
  clearAuthError,
257
268
  resendDeviceApproval,
258
- recover
269
+ recover,
270
+ passkeySupported,
271
+ enrollPasskeyDefault,
272
+ approveDeviceWithPasskey,
273
+ setupRecovery
259
274
  } = useCavos();
260
275
  const isMobile = useIsMobile();
261
276
  const isLight = theme !== "dark";
@@ -283,7 +298,12 @@ function CavosAuthModal({
283
298
  const [deviceResendBusy, setDeviceResendBusy] = useState(false);
284
299
  const [recoverCode, setRecoverCode] = useState("");
285
300
  const [recoverBusy, setRecoverBusy] = useState(false);
301
+ const [pkBusy, setPkBusy] = useState(false);
302
+ const [pkError, setPkError] = useState("");
303
+ const [savedRecoveryCode, setSavedRecoveryCode] = useState("");
304
+ const [copied, setCopied] = useState(false);
286
305
  const doneHandledRef = useRef(false);
306
+ const secureHandledRef = useRef(false);
287
307
  const closeTimerRef = useRef(null);
288
308
  const otpBoxRefs = useRef([null, null, null, null, null, null]);
289
309
  const setOtpDigit = (i, raw) => {
@@ -344,6 +364,7 @@ function CavosAuthModal({
344
364
  setOtpCode("");
345
365
  setError("");
346
366
  doneHandledRef.current = false;
367
+ secureHandledRef.current = false;
347
368
  }, 1600);
348
369
  }, [onClose]);
349
370
  useEffect(() => {
@@ -354,13 +375,26 @@ function CavosAuthModal({
354
375
  doneHandledRef.current = false;
355
376
  }
356
377
  if (isAuthenticated && address && walletStatus.isReady) {
357
- triggerDone(address);
378
+ if (walletStatus.isNewAccount && !secureHandledRef.current) {
379
+ if (screen !== "secure-account" && screen !== "recovery-code" && !doneHandledRef.current) {
380
+ setScreen("secure-account");
381
+ }
382
+ } else {
383
+ triggerDone(address);
384
+ }
358
385
  }
359
- if (walletStatus.awaitingApproval && screen !== "device-approval" && screen !== "recover") {
360
- setScreen("device-approval");
361
- doneHandledRef.current = false;
386
+ if (walletStatus.needsDeviceApproval && // A deploy in progress owns the screen; never fight the deploying branch
387
+ // above (both flags true would oscillate deploying ↔ approval → loop).
388
+ !walletStatus.isDeploying && screen !== "recover" && screen !== "device-approval" && screen !== "passkey-approval") {
389
+ if (walletStatus.hasPasskey && passkeySupported) {
390
+ setScreen("passkey-approval");
391
+ doneHandledRef.current = false;
392
+ } else if (walletStatus.awaitingApproval) {
393
+ setScreen("device-approval");
394
+ doneHandledRef.current = false;
395
+ }
362
396
  }
363
- }, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, screen, triggerDone]);
397
+ }, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, walletStatus.needsDeviceApproval, walletStatus.hasPasskey, walletStatus.isNewAccount, passkeySupported, screen, triggerDone]);
364
398
  useEffect(() => () => {
365
399
  if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
366
400
  }, []);
@@ -370,9 +404,61 @@ function CavosAuthModal({
370
404
  setEmail("");
371
405
  setOtpCode("");
372
406
  setError("");
407
+ setPkError("");
408
+ setSavedRecoveryCode("");
409
+ setCopied(false);
373
410
  doneHandledRef.current = false;
411
+ secureHandledRef.current = false;
374
412
  onClose();
375
413
  };
414
+ const finishSecureStep = () => {
415
+ secureHandledRef.current = true;
416
+ if (address) triggerDone(address);
417
+ };
418
+ const handleSetupPasskey = async () => {
419
+ setPkBusy(true);
420
+ setPkError("");
421
+ try {
422
+ await enrollPasskeyDefault();
423
+ finishSecureStep();
424
+ } catch (e) {
425
+ setPkError(e instanceof Error ? e.message : "We couldn't set up your passkey. Try again.");
426
+ } finally {
427
+ setPkBusy(false);
428
+ }
429
+ };
430
+ const handleSaveRecovery = async () => {
431
+ setPkBusy(true);
432
+ setPkError("");
433
+ try {
434
+ const code = await setupRecovery();
435
+ setSavedRecoveryCode(code);
436
+ setScreen("recovery-code");
437
+ } catch (e) {
438
+ setPkError(e instanceof Error ? e.message : "We couldn't create your recovery phrase. Try again.");
439
+ } finally {
440
+ setPkBusy(false);
441
+ }
442
+ };
443
+ const handleCopyRecovery = async () => {
444
+ try {
445
+ await navigator.clipboard.writeText(savedRecoveryCode);
446
+ setCopied(true);
447
+ setTimeout(() => setCopied(false), 2e3);
448
+ } catch {
449
+ }
450
+ };
451
+ const handlePasskeyApprove = async () => {
452
+ setPkBusy(true);
453
+ setPkError("");
454
+ try {
455
+ await approveDeviceWithPasskey();
456
+ setPkBusy(false);
457
+ } catch (e) {
458
+ setPkError(e instanceof Error ? e.message : "We couldn't verify your passkey. Try again or use email.");
459
+ setPkBusy(false);
460
+ }
461
+ };
376
462
  const handleOAuth = async (provider) => {
377
463
  setError("");
378
464
  setBusy(true);
@@ -463,6 +549,85 @@ function CavosAuthModal({
463
549
  setDeviceResendBusy(false);
464
550
  }
465
551
  };
552
+ if (screen === "secure-account") {
553
+ return /* @__PURE__ */ jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
554
+ isMobile && /* @__PURE__ */ jsx("div", { style: handle }),
555
+ /* @__PURE__ */ jsxs("div", { style: { padding: isMobile ? "28px 24px 28px" : "44px 24px 28px", textAlign: "center" }, children: [
556
+ /* @__PURE__ */ jsx("div", { style: { width: 48, height: 48, borderRadius: "50%", margin: "0 auto 16px", background: isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.06)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: textColor, strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [
557
+ /* @__PURE__ */ jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }),
558
+ /* @__PURE__ */ jsx("path", { d: "M9 12l2 2 4-4" })
559
+ ] }) }),
560
+ /* @__PURE__ */ jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Keep your account safe" }),
561
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 24px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Set up a passkey so you can sign in on a new phone or computer in one tap. It takes a few seconds." }),
562
+ pkError && /* @__PURE__ */ jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor, marginBottom: "14px", textAlign: "left" }, children: pkError }),
563
+ passkeySupported ? /* @__PURE__ */ jsxs(Fragment, { children: [
564
+ /* @__PURE__ */ jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "13px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 600, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "9px", opacity: pkBusy ? 0.65 : 1, transition: "opacity 0.15s, transform 0.1s" }, onClick: handleSetupPasskey, disabled: pkBusy, children: [
565
+ pkBusy ? /* @__PURE__ */ jsx(Spinner, { size: 16, color: "#fff" }) : /* @__PURE__ */ jsx(FingerprintIcon, { size: 18, color: "#fff" }),
566
+ pkBusy ? "Setting up\u2026" : "Set up a passkey"
567
+ ] }),
568
+ /* @__PURE__ */ jsx("p", { style: { margin: "10px 0 0", fontSize: "12px", color: subTextColor, lineHeight: 1.5 }, children: "Uses Face ID, Touch ID, or your device PIN." }),
569
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px", margin: "18px 0" }, children: [
570
+ /* @__PURE__ */ jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)" } }),
571
+ /* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: subTextColor, textTransform: "uppercase", letterSpacing: "0.06em" }, children: "or" }),
572
+ /* @__PURE__ */ jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)" } })
573
+ ] }),
574
+ /* @__PURE__ */ jsx("button", { className: "cavos-submit-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: inputBorder, background: "transparent", color: textColor, fontSize: "14px", fontWeight: 500, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", transition: "background 0.15s", opacity: pkBusy ? 0.6 : 1 }, onClick: handleSaveRecovery, disabled: pkBusy, children: "Save a recovery phrase instead" })
575
+ ] }) : /* @__PURE__ */ jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "13px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 600, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "9px", opacity: pkBusy ? 0.65 : 1, transition: "opacity 0.15s" }, onClick: handleSaveRecovery, disabled: pkBusy, children: [
576
+ pkBusy && /* @__PURE__ */ jsx(Spinner, { size: 16, color: "#fff" }),
577
+ pkBusy ? "Setting up\u2026" : "Save a recovery phrase"
578
+ ] }),
579
+ /* @__PURE__ */ jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: pkBusy ? "default" : "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "18px 0 0", fontFamily: "inherit", transition: "color 0.15s", opacity: pkBusy ? 0.6 : 1 }, onClick: finishSecureStep, disabled: pkBusy, children: "Skip for now" })
580
+ ] }),
581
+ /* @__PURE__ */ jsxs("div", { style: footer, children: [
582
+ /* @__PURE__ */ jsx(CavosLogo, { invert: !isLight }),
583
+ /* @__PURE__ */ jsx("span", { children: "Secured by Cavos" })
584
+ ] })
585
+ ] }) });
586
+ }
587
+ if (screen === "recovery-code") {
588
+ return /* @__PURE__ */ jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
589
+ isMobile && /* @__PURE__ */ jsx("div", { style: handle }),
590
+ /* @__PURE__ */ jsxs("div", { style: { padding: isMobile ? "28px 24px 28px" : "44px 24px 28px", textAlign: "center" }, children: [
591
+ /* @__PURE__ */ jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Save your recovery phrase" }),
592
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 18px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Write this down and keep it somewhere safe. It's the only way to get back in if you lose your devices, and we can't recover it for you." }),
593
+ /* @__PURE__ */ jsx("div", { style: { background: isLight ? "rgba(0,0,0,0.03)" : "rgba(255,255,255,0.05)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, borderRadius: "12px", padding: "16px", marginBottom: "12px", fontSize: "14px", fontWeight: 500, color: textColor, wordBreak: "break-word", lineHeight: 1.6, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", textAlign: "center" }, children: savedRecoveryCode }),
594
+ /* @__PURE__ */ jsx("button", { className: "cavos-provider", style: { ...pBtn, marginBottom: "10px" }, onClick: handleCopyRecovery, children: /* @__PURE__ */ jsx("span", { children: copied ? "Copied \u2713" : "Copy to clipboard" }) }),
595
+ /* @__PURE__ */ jsx("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: "inherit", transition: "opacity 0.15s" }, onClick: finishSecureStep, children: "I've saved it" })
596
+ ] }),
597
+ /* @__PURE__ */ jsxs("div", { style: footer, children: [
598
+ /* @__PURE__ */ jsx(CavosLogo, { invert: !isLight }),
599
+ /* @__PURE__ */ jsx("span", { children: "Secured by Cavos" })
600
+ ] })
601
+ ] }) });
602
+ }
603
+ if (screen === "passkey-approval") {
604
+ return /* @__PURE__ */ jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
605
+ isMobile && /* @__PURE__ */ jsx("div", { style: handle }),
606
+ /* @__PURE__ */ jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(CloseX, {}) }),
607
+ /* @__PURE__ */ jsxs("div", { style: { padding: isMobile ? "28px 24px 32px" : "48px 24px 32px", textAlign: "center" }, children: [
608
+ /* @__PURE__ */ jsx("div", { style: { width: 48, height: 48, borderRadius: "50%", margin: "0 auto 16px", background: isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.06)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx(FingerprintIcon, { size: 24, color: textColor }) }),
609
+ /* @__PURE__ */ jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Verify it's you" }),
610
+ /* @__PURE__ */ jsx("p", { style: { margin: "0 0 20px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Confirm with Face ID, Touch ID, or your device PIN to add this device to your account." }),
611
+ pkError && /* @__PURE__ */ jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor, marginBottom: "14px" }, children: pkError }),
612
+ /* @__PURE__ */ jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "8px", opacity: pkBusy ? 0.65 : 1 }, onClick: handlePasskeyApprove, disabled: pkBusy, children: [
613
+ pkBusy && /* @__PURE__ */ jsx(Spinner, { size: 15, color: "#fff" }),
614
+ pkBusy ? "Verifying\u2026" : "Continue with passkey"
615
+ ] }),
616
+ walletStatus.awaitingApproval && /* @__PURE__ */ jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "16px 0 0", fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
617
+ setScreen("device-approval");
618
+ setPkError("");
619
+ }, children: "Approve by email instead" }),
620
+ /* @__PURE__ */ jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: `${walletStatus.awaitingApproval ? "10px" : "16px"} 0 0`, fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
621
+ setScreen("recover");
622
+ setPkError("");
623
+ }, children: "Use a recovery phrase" })
624
+ ] }),
625
+ /* @__PURE__ */ jsxs("div", { style: footer, children: [
626
+ /* @__PURE__ */ jsx(CavosLogo, { invert: !isLight }),
627
+ /* @__PURE__ */ jsx("span", { children: "Secured by Cavos" })
628
+ ] })
629
+ ] }) });
630
+ }
466
631
  if (screen === "device-approval") {
467
632
  const expired = walletStatus.awaitingApproval && !walletStatus.pendingRequestId;
468
633
  return /* @__PURE__ */ jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
@@ -522,7 +687,7 @@ function CavosAuthModal({
522
687
  }, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
523
688
  isMobile && /* @__PURE__ */ jsx("div", { style: handle }),
524
689
  /* @__PURE__ */ jsx("button", { className: "cavos-close", style: { ...close, left: "16px", right: "auto" }, onClick: () => {
525
- setScreen("device-approval");
690
+ setScreen(walletStatus.hasPasskey && passkeySupported ? "passkey-approval" : "device-approval");
526
691
  setError("");
527
692
  setRecoverCode("");
528
693
  }, "aria-label": "Back", children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M19 12H5M12 5l-7 7 7 7" }) }) }),
@@ -761,23 +926,36 @@ var INITIAL_STATUS = {
761
926
  isReady: false,
762
927
  needsDeviceApproval: false,
763
928
  awaitingApproval: false,
764
- pendingRequestId: null
929
+ pendingRequestId: null,
930
+ hasPasskey: false,
931
+ isNewAccount: false
765
932
  };
766
933
  function CavosProvider({ config, modal, children }) {
767
934
  const [auth] = useState(
768
935
  () => new CavosAuth({ appId: config.appId, backendUrl: config.authBackendUrl })
769
936
  );
770
- const [cavos, setCavos] = useState(null);
937
+ const [wallet, setWallet] = useState(null);
771
938
  const [identity, setIdentity] = useState(null);
772
939
  const [walletStatus, setWalletStatus] = useState(INITIAL_STATUS);
773
940
  const [isLoading, setIsLoading] = useState(false);
774
941
  const [authError, setAuthError] = useState(null);
775
942
  const [modalOpen, setModalOpen] = useState(false);
943
+ const [passkeySupported, setPasskeySupported] = useState(false);
776
944
  const [branding, setBranding] = useState({});
945
+ useEffect(() => {
946
+ let cancelled = false;
947
+ PasskeySigner.isSupported().then((ok) => {
948
+ if (!cancelled) setPasskeySupported(ok);
949
+ }).catch(() => {
950
+ });
951
+ return () => {
952
+ cancelled = true;
953
+ };
954
+ }, []);
777
955
  const configRef = useRef(config);
778
956
  useEffect(() => {
779
957
  configRef.current = config;
780
- }, [config]);
958
+ });
781
959
  useEffect(() => {
782
960
  if (!config.appId || typeof window === "undefined") return;
783
961
  const base = config.authBackendUrl ?? "https://cavos.xyz";
@@ -791,41 +969,52 @@ function CavosProvider({ config, modal, children }) {
791
969
  const closeModal = useCallback(() => setModalOpen(false), []);
792
970
  const clearAuthError = useCallback(() => setAuthError(null), []);
793
971
  const resendDeviceApproval = useCallback(async () => {
794
- if (!identity || !cavos || cavos.chain !== "starknet" || !cavos.pendingRequestId) return;
795
- const backendUrl = configRef.current.authBackendUrl ?? "https://cavos.xyz";
796
- if (!configRef.current.appId) return;
797
- const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: configRef.current.appId });
972
+ const cfg = configRef.current;
973
+ if (!identity || !wallet || wallet.chain !== "starknet" || !wallet.pendingRequestId) return;
974
+ const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
975
+ if (!cfg.appId) return;
976
+ const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
798
977
  await recovery.requestDeviceAddition({
799
978
  userId: identity.userId,
800
- accountAddress: cavos.address,
801
- newSigner: cavos.publicKey,
979
+ accountAddress: wallet.address,
980
+ newSigner: wallet.publicKey,
802
981
  ...identity.email ? { email: identity.email } : {}
803
982
  });
804
- }, [identity, cavos]);
805
- const connect = useCallback(async (id) => {
806
- setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
807
- const c = await Cavos.connect({
808
- chain: configRef.current.chain ?? "starknet",
809
- network: configRef.current.network,
983
+ }, [identity, wallet]);
984
+ const connect = useCallback(async (id, opts) => {
985
+ const cfg = configRef.current;
986
+ if (!opts?.silent) setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
987
+ const w = await Cavos.connect({
988
+ chain: cfg.chain ?? "starknet",
989
+ network: cfg.network,
810
990
  identity: id,
811
- appSalt: configRef.current.appSalt,
812
- ...configRef.current.paymasterApiKey ? { paymasterApiKey: configRef.current.paymasterApiKey } : {},
813
- ...configRef.current.appId ? { appId: configRef.current.appId } : {},
814
- ...configRef.current.authBackendUrl ? { backendUrl: configRef.current.authBackendUrl } : {},
815
- ...configRef.current.rpcUrl ? { rpcUrl: configRef.current.rpcUrl } : {}
991
+ appSalt: cfg.appSalt,
992
+ ...cfg.paymasterApiKey ? { paymasterApiKey: cfg.paymasterApiKey } : {},
993
+ ...cfg.appId ? { appId: cfg.appId } : {},
994
+ ...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
995
+ ...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
816
996
  });
817
- setCavos(c);
997
+ setWallet(w);
818
998
  setIdentity(id);
819
- const pendingRequestId = c.chain === "starknet" ? c.pendingRequestId : null;
999
+ const pendingRequestId = w.chain === "starknet" ? w.pendingRequestId : null;
1000
+ let hasPasskey = false;
1001
+ if (w.status === "needs-device-approval") {
1002
+ try {
1003
+ hasPasskey = await w.hasPasskey();
1004
+ } catch {
1005
+ }
1006
+ }
820
1007
  setWalletStatus({
821
1008
  isDeploying: false,
822
- isReady: c.status === "ready",
823
- needsDeviceApproval: c.status === "needs-device-approval",
824
- awaitingApproval: c.status === "needs-device-approval" && !!pendingRequestId,
825
- pendingRequestId
1009
+ isReady: w.status === "ready",
1010
+ needsDeviceApproval: w.status === "needs-device-approval",
1011
+ awaitingApproval: w.status === "needs-device-approval" && !!pendingRequestId,
1012
+ pendingRequestId,
1013
+ hasPasskey,
1014
+ isNewAccount: w.isNewAccount
826
1015
  });
827
- modal?.onSuccess?.(c.address);
828
- return c;
1016
+ modal?.onSuccess?.(w.address);
1017
+ return w;
829
1018
  }, [modal]);
830
1019
  useEffect(() => {
831
1020
  if (typeof window === "undefined") return;
@@ -877,57 +1066,122 @@ function CavosProvider({ config, modal, children }) {
877
1066
  await connect(id);
878
1067
  }, [auth, connect]);
879
1068
  const execute = useCallback(async (calls) => {
880
- if (!cavos) throw new Error("Not logged in");
881
- if (cavos.chain !== "starknet") {
1069
+ if (!wallet) throw new Error("Not logged in");
1070
+ if (wallet.chain !== "starknet") {
882
1071
  throw new Error(
883
- "kit: useCavos().execute(calls) is Starknet-only. On Solana use the `wallet` handle: wallet.execute(amount, dest)."
1072
+ "kit: useCavos().execute(calls) is Starknet-only. On Solana/Stellar use the `wallet` handle: wallet.execute(amount, dest)."
884
1073
  );
885
1074
  }
886
- return cavos.execute(calls);
887
- }, [cavos]);
1075
+ return wallet.execute(calls);
1076
+ }, [wallet]);
888
1077
  const addSigner = useCallback(
889
1078
  async (pubkey) => {
890
- if (!cavos) throw new Error("Not logged in");
891
- if (cavos.chain !== "starknet") {
892
- throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on Solana.");
1079
+ if (!wallet) throw new Error("Not logged in");
1080
+ if (wallet.chain !== "starknet") {
1081
+ throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on other chains.");
893
1082
  }
894
- return cavos.addSigner(pubkey);
1083
+ return wallet.addSigner(pubkey);
895
1084
  },
896
- [cavos]
1085
+ [wallet]
897
1086
  );
1087
+ const enrollPasskey = useCallback(
1088
+ async (passkey, params) => {
1089
+ if (!wallet) throw new Error("Not logged in");
1090
+ return wallet.enrollPasskey(passkey, params);
1091
+ },
1092
+ [wallet]
1093
+ );
1094
+ const rpName = branding.appName ?? modal?.appName ?? "Cavos";
1095
+ const enrollPasskeyDefault = useCallback(async () => {
1096
+ if (!wallet || !identity) throw new Error("Not logged in");
1097
+ if (wallet.status !== "ready") throw new Error("kit: no ready device to enroll a passkey on");
1098
+ const passkey = new PasskeySigner({ rpName });
1099
+ await wallet.enrollPasskey(passkey, {
1100
+ userId: identity.userId,
1101
+ userName: identity.email ?? identity.userId,
1102
+ ...identity.email ? { displayName: identity.email } : {}
1103
+ });
1104
+ }, [wallet, identity, rpName]);
1105
+ const approveDeviceWithPasskey = useCallback(async () => {
1106
+ if (!wallet || !identity) throw new Error("Not logged in");
1107
+ if (wallet.status !== "needs-device-approval") {
1108
+ await connect(identity);
1109
+ return;
1110
+ }
1111
+ const passkey = new PasskeySigner({ rpName });
1112
+ if (wallet.chain === "starknet") {
1113
+ await wallet.approveThisDeviceWithPasskey({ passkey });
1114
+ } else {
1115
+ await wallet.approveThisDeviceWithPasskey(passkey);
1116
+ }
1117
+ setWalletStatus((s) => ({ ...s, isDeploying: true, needsDeviceApproval: false, awaitingApproval: false }));
1118
+ const deadline = Date.now() + 6e4;
1119
+ for (; ; ) {
1120
+ let ready = false;
1121
+ try {
1122
+ ready = await wallet.isReady();
1123
+ } catch {
1124
+ }
1125
+ if (ready) break;
1126
+ if (Date.now() > deadline) {
1127
+ setWalletStatus((s) => ({ ...s, isDeploying: false, needsDeviceApproval: true }));
1128
+ throw new Error(
1129
+ "Your device is being added, but it's taking longer than usual. Please try again in a moment."
1130
+ );
1131
+ }
1132
+ await new Promise((r) => setTimeout(r, 3e3));
1133
+ }
1134
+ await connect(identity, { silent: true });
1135
+ }, [wallet, identity, rpName, connect]);
898
1136
  const setupRecovery = useCallback(async () => {
899
- if (!cavos) throw new Error("Not logged in");
1137
+ if (!wallet) throw new Error("Not logged in");
900
1138
  const code = generateRecoveryCode();
901
- await cavos.setupRecovery(code);
1139
+ await wallet.setupRecovery(code);
902
1140
  return code;
903
- }, [cavos]);
1141
+ }, [wallet]);
904
1142
  const recover = useCallback(async (code) => {
905
1143
  if (!identity) throw new Error("Sign in first so we know which account to recover.");
1144
+ const cfg = configRef.current;
906
1145
  setAuthError(null);
907
1146
  setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
908
1147
  try {
909
- const chain = configRef.current.chain ?? "starknet";
910
- const c = chain === "solana" ? await CavosSolana.recover({
911
- code,
912
- identity,
913
- network: configRef.current.network === "mainnet" ? "solana-mainnet" : "solana-devnet",
914
- appSalt: configRef.current.appSalt,
915
- ...configRef.current.appId ? { appId: configRef.current.appId } : {},
916
- ...configRef.current.authBackendUrl ? { backendUrl: configRef.current.authBackendUrl } : {},
917
- ...configRef.current.rpcUrl ? { rpcUrl: configRef.current.rpcUrl } : {}
918
- }) : await Cavos.recover({
919
- code,
920
- identity,
921
- network: configRef.current.network,
922
- appSalt: configRef.current.appSalt,
923
- paymasterApiKey: configRef.current.paymasterApiKey ?? "",
924
- ...configRef.current.appId ? { appId: configRef.current.appId } : {},
925
- ...configRef.current.authBackendUrl ? { backendUrl: configRef.current.authBackendUrl } : {},
926
- ...configRef.current.rpcUrl ? { rpcUrl: configRef.current.rpcUrl } : {}
927
- });
928
- setCavos(c);
1148
+ const chain = cfg.chain ?? "starknet";
1149
+ let w;
1150
+ if (chain === "solana") {
1151
+ w = await CavosSolana.recover({
1152
+ code,
1153
+ identity,
1154
+ network: cfg.network === "mainnet" ? "solana-mainnet" : "solana-devnet",
1155
+ appSalt: cfg.appSalt,
1156
+ ...cfg.appId ? { appId: cfg.appId } : {},
1157
+ ...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
1158
+ ...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
1159
+ });
1160
+ } else if (chain === "stellar") {
1161
+ w = await CavosStellar.recover({
1162
+ code,
1163
+ identity,
1164
+ network: cfg.network === "mainnet" ? "stellar-mainnet" : "stellar-testnet",
1165
+ appSalt: cfg.appSalt,
1166
+ ...cfg.appId ? { appId: cfg.appId } : {},
1167
+ ...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
1168
+ ...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
1169
+ });
1170
+ } else {
1171
+ w = await Cavos.recover({
1172
+ code,
1173
+ identity,
1174
+ network: cfg.network,
1175
+ appSalt: cfg.appSalt,
1176
+ paymasterApiKey: cfg.paymasterApiKey ?? "",
1177
+ ...cfg.appId ? { appId: cfg.appId } : {},
1178
+ ...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
1179
+ ...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
1180
+ });
1181
+ }
1182
+ setWallet(w);
929
1183
  setWalletStatus({ ...INITIAL_STATUS, isReady: true });
930
- modal?.onSuccess?.(c.address);
1184
+ modal?.onSuccess?.(w.address);
931
1185
  } catch (e) {
932
1186
  const msg = e instanceof Error ? e.message : "Recovery failed. Check your code and try again.";
933
1187
  setAuthError(msg);
@@ -937,9 +1191,10 @@ function CavosProvider({ config, modal, children }) {
937
1191
  }, [identity, modal]);
938
1192
  useEffect(() => {
939
1193
  if (!walletStatus.awaitingApproval || !walletStatus.pendingRequestId || !identity) return;
940
- if (!configRef.current.appId) return;
941
- const backendUrl = configRef.current.authBackendUrl ?? "https://cavos.xyz";
942
- const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: configRef.current.appId });
1194
+ const cfg = configRef.current;
1195
+ if (!cfg.appId) return;
1196
+ const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
1197
+ const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
943
1198
  const requestId = walletStatus.pendingRequestId;
944
1199
  let cancelled = false;
945
1200
  const tick = async () => {
@@ -963,7 +1218,7 @@ function CavosProvider({ config, modal, children }) {
963
1218
  };
964
1219
  }, [walletStatus.awaitingApproval, walletStatus.pendingRequestId, identity, connect]);
965
1220
  const logout = useCallback(() => {
966
- setCavos(null);
1221
+ setWallet(null);
967
1222
  setIdentity(null);
968
1223
  setWalletStatus(INITIAL_STATUS);
969
1224
  setAuthError(null);
@@ -971,11 +1226,11 @@ function CavosProvider({ config, modal, children }) {
971
1226
  const value = {
972
1227
  openModal,
973
1228
  closeModal,
974
- isAuthenticated: !!cavos,
1229
+ isAuthenticated: !!wallet,
975
1230
  user: identity ? { userId: identity.userId, email: identity.email, provider: identity.provider } : null,
976
1231
  chain: config.chain ?? "starknet",
977
- wallet: cavos,
978
- address: cavos?.address ?? null,
1232
+ wallet,
1233
+ address: wallet?.address ?? null,
979
1234
  walletStatus,
980
1235
  isLoading,
981
1236
  authError,
@@ -987,6 +1242,10 @@ function CavosProvider({ config, modal, children }) {
987
1242
  handleCallback,
988
1243
  execute,
989
1244
  addSigner,
1245
+ enrollPasskey,
1246
+ passkeySupported,
1247
+ enrollPasskeyDefault,
1248
+ approveDeviceWithPasskey,
990
1249
  resendDeviceApproval,
991
1250
  setupRecovery,
992
1251
  recover,