Cavos

React

Drop-in React bindings for @cavos/kit — provider, hooks, and the customizable login modal.

@cavos/kit ships React bindings under the @cavos/kit/react subpath (same package). Wrap your app once in <CavosProvider> and every descendant can call useCavos() to read wallet state and trigger actions — no prop drilling, no imperative connect calls.

Terminal
npm install @cavos/kit
app/provider.tsx
import { CavosProvider } from "@cavos/kit/react";

export function Providers({ children }) {
  return (
    <CavosProvider
      config={{
        appId: process.env.NEXT_PUBLIC_CAVOS_APP_ID,
        chains: ["solana", "stellar"],
        defaultChain: "solana",
        network: "testnet",
        appSalt: "my-app",
        socialRecovery: true,       // enclave DEK wrap (multi-chain OK)
        deviceApproval: "enclave",
      }}
      modal={{
        appName: "My App",
        theme: "light",
      }}
    >
      {children}
    </CavosProvider>
  );
}

The provider manages one or more chains. Behind the scenes it resolves the identity, derives addresses for all configured chains, and keeps the wallet handles ready for sponsored transactions. Deployment happens lazily on first execute per chain — see Concepts.

Props

PropDescription
configChains, network, appSalt, deviceApproval ("enclave" | "passkey" — passkey is one chain), socialRecovery, vault (on by default with appId; see Cavos vault).
modalLogin modal appearance and providers. Omit to render no modal.
identityBring your own auth. Pass your signed-in user and Cavos login turns off. null while your auth loads or after sign-out. See Authentication.

In development the provider checks the config when it mounts and reports problems in the console — a missing appSalt, a paymaster key on a chain that ignores it, and in particular an appSalt that changed since this browser last connected, which silently moves every existing user to a new, empty wallet. Run the same checks yourself with validateCavosConfig(config).

useCavos()

The primary hook. Returns the full wallet surface:

import { useCavos } from "@cavos/kit/react";

function Wallet() {
  const { address, isAuthenticated, walletStatus, openModal, logout } = useCavos();
  if (!isAuthenticated) return <button onClick={openModal}>Sign in</button>;
  return <div>{address} · ready={walletStatus.isReady}</div>;
}

State

FieldDescription
isAuthenticated: booleanWhether a wallet is connected.
user: UserInfo | null{ userId, email?, provider? } from the login.
wallet: CavosWallet | nullThe connected wallet for the selected chain, discriminated by wallet.chain. Narrow on it before chain-native calls.
session: CavosWallet & CavosSession | nullThe full multi-chain session. Use session.wallet("stellar") to access other chains.
address: string | nullThe account address for the selected chain.
chain: ChainThe active chain ("starknet" | "solana" | "stellar").
configuredChains: Chain[]All chains configured for this session.
walletStatus: WalletStatusDeployment / device-approval / passkey flags (see below).
isLoading: booleanTrue during an in-flight login or connect.
authError: string | nullLast unrecoverable auth/connect error. Null while healthy. CavosAuthModal shows it on the sign-in screen; from 0.2.3 that includes a connect that fails while the "Connecting with…" spinner is up.
passkeySupported: booleanWhether this device/browser can use a platform passkey.

WalletStatus: { isDeploying, isReady, isUndeployed, needsDeviceApproval, hasPasskey, isNewAccount, recovery }.

  • isUndeployed: True when the account has not been deployed on-chain yet. First execute will deploy + run atomically.
  • isReady: True when this device holds the wallet key.
  • needsDeviceApproval: True when the wallet exists but this device holds no key yet.

Both isUndeployed and isReady allow execution. Only needsDeviceApproval blocks transactions. An undeployed wallet can sign messages and execute — the first execute deploys the account.

walletStatus.recovery is the one flag most apps actually read: { protected: boolean; methods: ("passkey" | "social")[] } — can this user get back in from a device they do not have yet? The other flags describe work in flight; this describes the standing guarantee.

const { walletStatus } = useCavos();
if (walletStatus.isReady && !walletStatus.recovery.protected) {
  // Nudge: "Add a passkey so you can sign in on a new device."
}

recovery.protected starts false and flips once the lookup that fills it returns. Read it as "not known to be protected" during the first moments of a connect, not as "known to be unprotected" — don't show a scary warning on frame one.

Chain switching

Switch the selected chain without re-authenticating or remounting:

const { chain, setChain, configuredChains, session } = useCavos();

// Switch to Stellar
await setChain("stellar");

// Now `chain` is "stellar", `wallet` is the Stellar wallet
// The walletStatus updates to reflect the Stellar account's state

setChain does not re-deploy or re-authenticate — it just changes which wallet is active. The session keeps all chains connected.

Login

MethodDescription
login(provider)OAuth social login ("google" | "apple") — opens the hosted flow.
sendOtp(email)Email a 9-character sign-in code.
verifyOtp(email, code)Verify the emailed code and deploy the wallet.
handleCallback(authData, redirectUri?)Resolve identity from an OAuth callback (?cavos_auth_code=…) and deploy. Pass the callback URL as redirectUri if it differs from the current one.

Transactions

MethodDescription
execute(calls, opts?)Starknet only. Multicall signed by the stark key, gasless by default. Returns { transactionHash }. Works on undeployed (deploys first) and ready wallets.
signMessage(message)All chains. Sign an arbitrary message off-chain; returns a uniform MessageSignature. Works on undeployed and ready wallets. See Wallet actions.

On Solana and Stellar, call the wallet directly for chain-native actions instead of the context's execute: narrow on wallet.chain, then wallet.execute(amount, destination). For signTransaction (sign without submit), also call wallet.signTransaction(...) directly after narrowing — the args differ per chain. See Wallet actions.

Device & recovery

MethodDescription
enrollPasskeyDefault()Add a passkey after sign-up. Stores a PRF-encrypted copy of the MasterDEK. Sign-up never prompts.
approveDeviceWithPasskey()New device with no wrap, when the user dismissed the prompt: opens the passkey's encrypted copy of the MasterDEK (every chain) or approves the grandfathered-Stellar path.
setupRecovery()Stellar only (grandfathered wallets). Starknet and Solana have nothing to register. Resolves with the code when it applies.
submitSocialRecoveryToken(idToken)Drive enclave enroll/unwrap with the provider's raw id_token. Starknet, Solana, and Stellar. Not accepted for Google and Apple tokens in 0.2.6. See Hardware-isolated recovery.
recover(code)Recover access after losing every device. Grandfathered Stellar only. Starknet, Solana, and native Stellar restore with enclave login or approveDeviceWithPasskey().

When hardware-isolated social recovery is enabled, <CavosProvider> enrolls after a fresh login (reseals the MasterDEK if this device already has it) and unwraps on a device that does not. Passkey is opt-in via enrollPasskeyDefault() — secureStep defaults to 'off'.

These wrappers present a shared product flow across the current adapters. Their cryptographic implementation and some availability constraints remain chain-specific. See Passkeys and Recovery for the capability details.

MethodDescription
openModal() / closeModal()Open/close the built-in auth modal (the provider mounts it automatically when modal is set).
logout()Clears the persisted identity and session token, and tells the vault to let go of the keys it unlocked. Stored keys stay, so the next login is silent. Does not sign the user out of Google or Apple.
clearAuthError()Clear authError (e.g. when the user starts a new login attempt).

useCavosAuth()

A deliberately thin subset for components that only need to open the modal and read basic auth state — no wallet, no transactions:

{
  openModal, closeModal,
  isAuthenticated, address, user, walletStatus, logout,
}

<CavosAuthModal>

A fully customizable, themeable login modal. The provider mounts it automatically when you pass modal={...}; you can also render it directly (for a live preview, or to control its lifecycle yourself).

import { CavosAuthModal } from "@cavos/kit/react";

<CavosAuthModal
  open={open}
  onClose={() => setOpen(false)}
  appName="My App"
  appLogo="https://myapp.com/logo.png"
  appLogoSize={56}
  providers={["email", "google", "apple"]}
  primaryColor="#402AFF"
  theme="light"
  radius={16}
  secureStep="optional"
/>

Props

PropTypeDescription
openbooleanWhether the modal is shown (ignored when inline).
onClose() => voidCalled when the user dismisses the modal.
appNamestring?Shown in the heading ("Sign in to {appName}").
appLogostring?Image URL for the logo. If omitted, the Cavos star is shown. The provider also loads it from your app's dashboard config automatically; a local appLogo overrides that.
appLogoSizenumber?Logo height in px. Defaults to 40 (image) / 34 (Cavos star).
providers("google" | "apple" | "email")[]?Which login buttons to show. Defaults to all three.
primaryColorstring?Accent color for buttons / focus rings (hex).
theme"light" | "dark"?Card theme. Defaults to light.
backgroundColorstring?Override the card background (defaults to white / #111 per theme).
radiusnumber?Card & button corner radius in px (card defaults to 16).
inlineboolean?Render the card in-flow (no overlay/backdrop) — for live previews. When true, open is ignored.
secureStep"optional" | "required" | "off"?Passkey screen after a new account. 'off' is the default — login never prompts.
onSuccess(address: string) => void?Fired when authentication + deployment succeed.

CavosModalConfig

The object you pass as modal={...} to <CavosProvider>. Same fields as CavosAuthModalProps except no open / onClose / inline (the provider manages those), plus onSuccess.

Theming

All visual props (primaryColor, theme, backgroundColor, radius, appLogo, appLogoSize) apply identically whether you set them on <CavosProvider modal={...}> or on a direct <CavosAuthModal ... />.

<CavosProvider
  config={{ chains: ["solana"], defaultChain: "solana", network: "testnet", appSalt: "my-app", appId }}
  modal={{
    appName: "My App",
    appLogo: "/logo.png",
    appLogoSize: 48,
    primaryColor: "#7C3AED",
    theme: "dark",
    backgroundColor: "#0A0A0F",
    radius: 20,
  }}
>
  <App />
</CavosProvider>

Mobile: automatic bottom sheet

On viewports max-width: 640px the modal becomes a bottom sheet — it slides up from the bottom with rounded top corners and a grab handle, overlaying the page with a blurred backdrop. This is fully automatic; there is no prop to toggle. On desktop the modal renders centered. The inline mode disables the overlay/sheet entirely (the card is embedded in your layout).

New devices

There are no device approval or revocation pages to host. A new device restores the MasterDEK with the enclave or a passkey and derives the same account key; nothing is added on-chain. See Multi-device.

Multi-chain in React

The provider handles multiple chains natively. Configure them all up front:

<CavosProvider
  config={{
    chains: ["solana", "stellar", "starknet"],
    defaultChain: "solana",
    network: "testnet",
    appSalt: "my-app",
    appId,
    paymasterApiKey, // for Starknet
  }}
  modal={{ appName: "My App" }}
>
  <App />
</CavosProvider>

Then switch chains without remounting:

function ChainSwitcher() {
  const { chain, setChain, configuredChains, walletStatus } = useCavos();

  return (
    <select value={chain} onChange={(e) => setChain(e.target.value as Chain)}>
      {configuredChains.map((c) => (
        <option key={c} value={c}>{c}</option>
      ))}
    </select>
  );
}

setChain updates the active chain, and walletStatus reflects the new chain's status. No re-authentication, no remount, no lost state.

Each chain has its own address, deployment status, and device authorization. Switching chains may show a different walletStatus — one chain might be ready while another is undeployed or needs-device-approval.

Chain and NetworkEnv types are exported from the core entry (import type { Chain } from "@cavos/kit"), not from @cavos/kit/react.

On this page