Cavos

Embedded Starknet Wallet

A device-native, self-custodial embedded wallet for Starknet. The signing key is created and used on the user's device — Cavos cannot see it, cannot sign with it, and cannot move funds. Available via @cavos/kit for web and @cavos/kit/react-native for mobile (Expo Development Builds, EAS, or bare React Native — Expo Go is not supported).

The stark key is derived from a MasterDEK that stays protected at rest by the device's non-extractable key (WebCrypto in the browser, the OS keystore on React Native). WebCrypto cannot hold a stark key, so outside the Cavos vault it is re-derived into memory for the session. The SDK does not enforce non-extractability on Node or other server runtimes.

Evaluate a Starknet embedded wallet

This page is for teams shipping on Starknet, not for an EVM-wide wallet layer. If your users should never install a browser extension or handle a seed phrase, an embedded wallet is the product model: login selects the user, the SDK resolves an on-chain account, and approval happens in your UI. Cavos is the device-native version of that model. The signing key is derived on the device from a MasterDEK. Cavos cannot reconstruct it with MPC and cannot spend.

Authentication is separate from signing. Hosted login is Google, Apple, or an enclave-verified 9-character email code (sendOtp / verifyOtp). If you already have Clerk, Auth0, or your own session, pass a stable userId. Login never authorizes a spend. Use an immutable primary key, not an email that can change: a different userId is a different registry lookup. Email sign-in is not available on React Native yet; use Google or Apple there.

How the Starknet account signs

Cavos provisions an audited OpenZeppelin account contract — AccountUpgradeable v3.0.0. The audit covers OpenZeppelin's contract, not Cavos. It is an upgradeable smart contract account controlled by one stark key, the same shape as Argent or Braavos, not a custom Cavos contract. The key is grindKey(HKDF(MasterDEK, appSalt, "cavos-stark-starknet-v1")). The address is calculateContractAddressFromHash of that public key and the pinned class hash 0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381, declared on mainnet and Sepolia. Changing appSalt derives a different key and address that will not match the registry row.

When you call execute, starknet.js computes the v3 transaction hash, the stark key signs it as a 2-felt [r, s], and the account's __validate__ checks that signature against the account's public key. There is no on-chain device list: a new device restores the MasterDEK off-chain and derives the same key. The kit refuses set_public_key on the account itself — a rotated key would no longer be controlled by the DEK. Later implementation changes go through wallet.upgrade(newClassHash), which keeps the address.

On the web the key lives inside the Cavos vault, an iframe on a Cavos origin. The vault reads the calls before signing: transfer on ETH, STRK, and USDC counts against your app's limits, and calls into the account itself always go to your over-limit rule. Connect never deploys. An undeployed wallet already has an address in the registry; the first execute deploys the account and runs the user's calls in one sponsored transaction. After that, status is ready. An undeployed wallet can also signMessage (SNIP-12 revision 1 typed data, domain Cavos / 1).

Paymaster sponsorship

execute routes through the Cavos paymaster (SNIP-29, with SRC-9 outside execution). Account deployment and every later execution are sponsored by default, so the user pays no gas and never needs ETH or STRK. After the account is deployed, fee: 'self' pays from the account's own balance. A self-funded first execute is refused, because an undeployed account has no balance to deploy itself with.

Starknet is the only adapter that takes paymasterApiKey. The current web SDK receives that value in browser code, so treat it as a client-visible, app-scoped sponsorship credential — never as an operator or treasury secret. Bind and limit it by app, environment, origin, rate, and spend. Add it only when Starknet is in chains; a paymaster key on a chain that ignores it is a config error. The backend can transport, sponsor, or submit a transaction. It does not hold the user authority required to move funds.

Recovery on a new device

A Starknet wallet is one key, derived from the MasterDEK. Losing the device loses that wrap unless a second path was set up first. After login, call enrollPasskeyDefault(). Signing up never prompts for a passkey. The passkey stores an encrypted copy of the MasterDEK under its WebAuthn PRF. Cavos stores the ciphertext and cannot open it. Anyone with that synced passkey (iCloud Keychain, Google Password Manager) can open the wallet. It is not 2FA.

Hardware-isolated recovery is opt-in per environment, not on by default. When it is enabled, an AWS Nitro enclave seals the MasterDEK and unwraps it on a later sign-in with Google, Apple, or an enclave-verified email code. The enclave does not sign transfers. Email sign-in uses a 9-character code the enclave generates, mails, and verifies; magic links are not part of this path. Recovery codes do not restore Starknet wallets. If every device and every restore factor is gone, Cavos cannot recover the wallet.

Install

Create an app in the Cavos dashboard and copy its App ID. Register callback URLs exactly. Never change appSalt after users exist. The React walkthrough lives in How to add an embedded wallet.

npm install @cavos/kit

Connect and execute

Use Cavos.connect with chains: ["starknet"]. Connect never deploys the account. You can call execute when status is "undeployed" or "ready". Narrow on wallet.chain so TypeScript exposes the multicall signature. In React, wrap the tree in CavosProvider from @cavos/kit/react and use the context execute(calls) for Starknet.

import { Cavos } from "@cavos/kit";

const session = await Cavos.connect({
  chains: ["starknet"],
  defaultChain: "starknet",
  network: "mainnet",
  identity: { userId: user.id, email: user.email },
  appSalt: "my-app",
  appId: process.env.NEXT_PUBLIC_CAVOS_APP_ID,
  paymasterApiKey: process.env.NEXT_PUBLIC_CAVOS_PAYMASTER_API_KEY,
});

const wallet = session.wallet("starknet");

const { transactionHash } = await wallet.execute([
  {
    contractAddress: TOKEN_ADDRESS,
    entrypoint: "transfer",
    calldata: [recipient, amountLow, amountHigh],
  },
]);

For UI apps, the same package exports the provider. Pass identity if you already authenticate users; Cavos login then turns off. Hosted login is Google, Apple, or sendOtp / verifyOtp. Email is not available on React Native yet.

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

export function Providers({ children }) {
  return (
    <CavosProvider
      config={{
        chains: ["solana", "stellar", "starknet"],
        defaultChain: "solana",
        network: "testnet",
        appSalt: "my-app",
        appId: process.env.NEXT_PUBLIC_CAVOS_APP_ID,
        paymasterApiKey: process.env.NEXT_PUBLIC_CAVOS_PAYMASTER_API_KEY,
      }}
      modal={{ appName: "My App" }}
    >
      {children}
    </CavosProvider>
  );
}

What is shared, what is Starknet-only

Login, registry lookup on (userId, appId, chain, network), the MasterDEK, passkey and enclave restore, and the kit packages are shared with the Solana embedded wallet and the Stellar embedded wallet. One Cavos.connect can configure all three chains. Each chain still has its own address and deployment status.

Starknet-only: the OpenZeppelin account, the stark curve, multicall execute(calls), the paymaster and paymasterApiKey, SNIP-12 signMessage, and upgrade. Solana uses lamport execute and a relayer fee payer. Stellar uses stroops and a relayer that sponsors reserves. Cavos is not an EVM-wide wallet layer. If you are still choosing a provider, the compare pages state when another product is the better fit. Pricing publishes the free 1,000 wallet creates and paid recovery options.

What you get

Self-custodial

Cavos never holds a key that can move user funds. The account key is derived from a MasterDEK that only the user's devices can open.

Audited OpenZeppelin account

AccountUpgradeable v3.0.0 (class hash 0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381). The audit covers OpenZeppelin’s contract, not Cavos.

Gasless execution

The Cavos paymaster sponsors deployment and every transaction by default. Users never need ETH or STRK. After deploy, fee: "self" is available.

Arbitrary multicalls

Execute any contract calls: token transfers, DeFi interactions, NFT mints — all in one transaction. The kit refuses set_public_key on the account itself.

Passkey and enclave restore

A synced passkey or an enclave sign-in restores the MasterDEK on a new device, so the same wallet opens everywhere.

Lazy deploy

Connect derives the address immediately. The account is deployed on first execute, atomically.

Frequently asked

What is a Starknet embedded wallet?

A Starknet embedded wallet is an on-chain Starknet account provisioned inside your application. The user signs in with an identity you already have. @cavos/kit looks up or claims a deterministic address and signs from the device. Cavos cannot see the key, sign with it, or move funds.

Does Cavos use a custom Starknet account contract?

No. Each Starknet wallet is an audited OpenZeppelin account contract: AccountUpgradeable v3.0.0, class hash 0x01d1777db36cdd06dd62cfde77b1b6ae06412af95d57a13dc40ac77b8a702381. The audit covers OpenZeppelin’s contract, not Cavos. One stark key, derived from the MasterDEK and app salt, controls the account. There is no on-chain device list and no custom Cavos account class.

How does gas work on a Starknet embedded wallet?

execute routes through the Cavos paymaster (SNIP-29, with SRC-9 outside execution). Account deployment and later executions are sponsored by default, so the user does not need ETH or STRK. After the account is deployed, fee: "self" pays from the account’s own balance. A self-funded first execute is refused.

How do I add a Starknet embedded wallet with @cavos/kit?

Install @cavos/kit, create an app in the Cavos dashboard, and pass the App ID plus a paymaster API key into Cavos.connect or CavosProvider. Call session.wallet("starknet"), then execute a multicall. Connect never deploys; the first execute deploys and runs atomically.

How does recovery work on a Starknet embedded wallet?

Recovery is off-chain. A new device restores the MasterDEK with a Nitro enclave or a synced passkey and derives the same stark key. There are no on-chain device signers and no on-chain social recovery, backup signer, or recovery delay. After login, enrollPasskeyDefault() stores an encrypted copy of that DEK under a passkey. Hardware-isolated recovery, opt-in per environment, unwraps the DEK when the user signs in again with Google, Apple, or an enclave-verified email code. Recovery codes do not restore Starknet wallets.

What is Starknet-specific versus shared across chains?

Shared: login, registry lookup on (userId, appId, chain, network), a MasterDEK, passkey and enclave restore, and @cavos/kit on web and React Native. Starknet-specific: the OpenZeppelin account, the stark curve, multicall execute, the paymaster, and SNIP-12 message signatures.

Start building on Starknet.

Follow the quickstart to reach a sponsored transaction.

We use anonymous analytics to understand how developers use Cavos. No personal data is collected. Privacy Policy