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.
npm install @cavos/kitimport { 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
| Prop | Description |
|---|---|
config | Chains, network, appSalt, deviceApproval ("enclave" | "passkey" — passkey is one chain), socialRecovery, vault (on by default with appId; see Cavos vault). |
modal | Login modal appearance and providers. Omit to render no modal. |
identity | Bring 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
| Field | Description |
|---|---|
isAuthenticated: boolean | Whether a wallet is connected. |
user: UserInfo | null | { userId, email?, provider? } from the login. |
wallet: CavosWallet | null | The connected wallet for the selected chain, discriminated by wallet.chain. Narrow on it before chain-native calls. |
session: CavosWallet & CavosSession | null | The full multi-chain session. Use session.wallet("stellar") to access other chains. |
address: string | null | The account address for the selected chain. |
chain: Chain | The active chain ("starknet" | "solana" | "stellar"). |
configuredChains: Chain[] | All chains configured for this session. |
walletStatus: WalletStatus | Deployment / device-approval / passkey flags (see below). |
isLoading: boolean | True during an in-flight login or connect. |
authError: string | null | Last 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: boolean | Whether 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 statesetChain does not re-deploy or re-authenticate — it just changes which wallet
is active. The session keeps all chains connected.
Login
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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
| Method | Description |
|---|---|
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.
Modal & session
| Method | Description |
|---|---|
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
| Prop | Type | Description |
|---|---|---|
open | boolean | Whether the modal is shown (ignored when inline). |
onClose | () => void | Called when the user dismisses the modal. |
appName | string? | Shown in the heading ("Sign in to {appName}"). |
appLogo | string? | 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. |
appLogoSize | number? | Logo height in px. Defaults to 40 (image) / 34 (Cavos star). |
providers | ("google" | "apple" | "email")[]? | Which login buttons to show. Defaults to all three. |
primaryColor | string? | Accent color for buttons / focus rings (hex). |
theme | "light" | "dark"? | Card theme. Defaults to light. |
backgroundColor | string? | Override the card background (defaults to white / #111 per theme). |
radius | number? | Card & button corner radius in px (card defaults to 16). |
inline | boolean? | 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.