An embedded wallet lives inside your application. The user signs in the way they already do. The product resolves an on-chain account they can pay, receive, and sign with — no browser extension, no seed phrase, no trip to another app.
The embedded wallet API is the developer surface that does that job. This guide is the docs-style map: what those APIs cover, what to evaluate before you pick one, and how Cavos exposes the same contract in @cavos/kit 0.4.4. The React integration is the install. The providers comparison is the shortlist. Adapters ship today for Starknet, Solana, and Stellar.
Cavos, LLC is a software and SaaS company. The wallet is the main product: a device-native, self-custodial embedded wallet. Signing keys are created and used on the user’s device. Cavos cannot see them, sign with them, or move funds.
What an embedded wallet API is
An embedded wallet API is not a wallet the user downloads. It is the library — and the hosted services behind it — that your product calls to:
- Authenticate a session. Login selects which user this is. It should not, by itself, authorize a spend.
- Create or look up a wallet. Given a stable user id, return the on-chain address for this app and chain, or claim a new one.
- Handle keys. Create the signing key, store it, and decide who can produce a signature.
- Sign. Prove ownership off-chain, or authorize a transaction without broadcasting it.
- Send or execute. Submit a native transfer, a multicall, or a program/contract invocation.
- Sponsor gas. Let a first-time user transact without holding the chain’s fee token.
- Recover. Let a new device restore the means to sign after a lost, wiped, or replaced phone.
Vendors package those steps differently. Some ship a finished client SDK (auth modal, wallet handle, sponsored execute). Some ship a REST or RPC signing layer and leave auth and UX to you. The label “embedded wallet API” covers both. It does not decide custody.
What to look for when picking one
Write the answers down before you compare SDKs.
Custody model. Who can produce a valid signature without the user present? Device-native keys, MPC shares, a provider-operated HSM or TEE, and a remote enclave are different answers. “Non-custodial” is a claim. The test is who can move the assets.
Chains. Which networks are native — that chain’s account model, signatures, and fee path — and which are a compatibility layer around an EOA-shaped key. Cavos is Starknet, Solana, and Stellar only. It is not an EVM-wide wallet layer.
Client SDK vs REST. A finished SDK gives you connect and execute inside the app. A signing API gives you keys, policies, and an HTTP surface; you assemble login and approval. Confirm which layer you are buying. Cavos is the first. Turnkey-style infrastructure is the second.
Rate limits and pricing. Units differ: wallet creates, monthly active users, monthly active wallets, signatures, volume. Ask what is metered, what is rate-limited, and whether gas sponsorship is on the public plan. Cavos pricing publishes a free 1,000 wallet creates, Essential at $59/mo, and Complete at $139/mo with enclave recovery. Bind any client-visible sponsorship credential by app, environment, origin, rate, and spend.
Export. Can the user export a private key or seed, or only restore the same address on a new device? Addresses are derived differently across vendors. Migration is a project. Cavos restores the MasterDEK on a new device. It does not advertise a seed or private-key export.
If you still need the named-vendor table, use the providers guide. Worked one-to-one pages live under compare.
How Cavos exposes this
The public developer surface is @cavos/kit. React bindings live on the same package under @cavos/kit/react. Mobile is @cavos/kit/react-native. The API reference lists every export.
There is no public “create wallet” REST endpoint for your backend to call. Cavos.connect and CavosProvider talk to the hosted registry, paymaster, and relayers behind an App ID. Your code stays on the kit.
The onboarding contract is the same on every adapter: authenticate, connect, then execute. See the quickstart.
Auth and session
Authentication exists for one reason: a stable userId (plus optional email) that the registry keys on. The login never signs transactions. That is always the device key.
Hosted login is Google, Apple, or email. In React:
import { useCavos } from "@cavos/kit/react";
const { openModal, login, sendOtp, verifyOtp, logout } = useCavos();
openModal(); // hosted modal
login("google"); // or login("apple")
await sendOtp(email);
await verifyOtp(email, code); // 9 characters, lowercase letters and digits
Without the provider, CavosAuth is the same contract: getGoogleOAuthUrl / getAppleOAuthUrl, handleCallback, sendOtp, verifyOtp. If you already authenticate users, pass a stable userId as identity. Cavos login turns off: the modal is not mounted and login() throws. Use an immutable primary key, not an email that can change.
See authentication.
Wallet creation
Cavos.connect looks up the address, or claims one. It never deploys Starknet or Stellar.
(userId, appId, chain, network) → registry lookup → address
import { Cavos } from "@cavos/kit";
const session = await Cavos.connect({
chains: ["solana", "stellar", "starknet"],
defaultChain: "solana",
network: "testnet",
appSalt: "my-app",
identity,
appId: process.env.NEXT_PUBLIC_CAVOS_APP_ID,
paymasterApiKey: process.env.NEXT_PUBLIC_CAVOS_PAYMASTER_API_KEY, // Starknet only
});
const solana = session.wallet("solana");
const stellar = session.wallet("stellar");
const starknet = session.wallet("starknet");
The registry is insert-only. The first device to register wins. A second device sees the existing address and enters needs-device-approval instead of creating a duplicate wallet.
| Status | Meaning | Can execute? |
|--------|---------|--------------|
| undeployed | Address derived but no on-chain account yet. | Yes — first execute deploys and runs atomically. |
| ready | Account deployed and this device holds the key. | Yes |
| needs-device-approval | This device has no key yet. Restore the MasterDEK. | No — restore with a passkey or the enclave. |
In React, wrap the tree once in CavosProvider and read address, walletStatus, and wallet from useCavos(). Never change appSalt after users exist: on Solana and Stellar it is also the HKDF salt for the spend key.
Key handling
The wallet’s account key is derived on the user’s device from a MasterDEK. At rest that DEK is protected by the platform’s non-extractable key: WebCrypto in the browser, the OS keystore on React Native. Cavos cannot see it.
Each adapter keeps the chain’s native account model:
- Starknet. An audited OpenZeppelin account contract — AccountUpgradeable v3.0.0 — controlled by one stark key derived from the MasterDEK and
appSalt. Cavos does not ship its own account implementation. There are no on-chain device signers.
- Solana. A native Ed25519 system account. The spend key is HKDF of the same MasterDEK. A relayer co-signs as fee payer. The address is an ordinary Solana public key, not a PDA.
- Stellar. A classic G… account, not a Soroban contract account. On the current path the spend key is HKDF of the MasterDEK (
cavos-ed25519-stellar-v1, salted by appSalt). A new device restores that DEK; it does not add a second Horizon signer.
Signing and executing
Narrow on wallet.chain so TypeScript exposes the native method. On Solana and Stellar, call the wallet directly. On Starknet, the context execute(calls) is the multicall.
const { wallet, execute, signMessage } = useCavos();
if (wallet?.status === "needs-device-approval") {
throw new Error("This device has no key yet. Restore the MasterDEK.");
}
if (wallet?.chain === "solana") {
const txHash = await wallet.execute(1_000_000n, destinationAddress); // lamports
} else if (wallet?.chain === "stellar") {
const txHash = await wallet.execute(1_000_000n, destinationAddress); // stroops
} else if (wallet?.chain === "starknet") {
const { transactionHash } = await execute([
{ contractAddress: TOKEN, entrypoint: "transfer", calldata: [to, amountLow, amountHigh] },
]);
}
const { signature, publicKey, curve } = await signMessage("Sign in to dapp.com");
signMessage works on undeployed and ready wallets. Nothing is submitted. Starknet signs SNIP-12 typed data (curve: "stark"). Solana and Stellar sign a prefixed message (curve: "ed25519"). Reconstruct the prefix with prefixedMessageBytes from @cavos/kit before verifying.
wallet.signTransaction(...) signs without broadcasting. The args differ per chain: Starknet takes a multicall; Solana and Stellar take (amount, destination). Outputs bind to on-chain state (nonce, sequence, recent blockhash). Broadcast promptly.
For arbitrary execution, Solana exposes executeInstructions (allowlisted programs) and Stellar exposes invokeContract. See wallet actions.
Gas sponsorship
Users should not need a native token to start. Sponsorship is chain-specific. Relayers and paymasters transport, sponsor, or submit. They are not signers.
| Adapter | Required configuration | Sponsored execution |
|---|---|---|
| Starknet | App ID, network, app salt, identity, paymasterApiKey | Paymaster sponsors deploy and execute. |
| Solana | App ID, network, app salt, identity | Hosted relayer becomes fee payer; arbitrary programs are allowlisted per app. |
| Stellar | App ID, network, app salt, identity | Hosted relayer can sponsor reserves and fee-bump submission. |
Add paymasterApiKey only when Starknet is in chains. A paymaster key on a chain that ignores it is a config error.
Recovery
A device that is lost, wiped, or replaced takes its key with it. Recovery is a second authority set up before that happens.
On Starknet, Solana, and native Stellar, a new device restores the MasterDEK. It does not add an on-chain signer. There is no on-chain device-signer list and no on-chain social recovery.
const { walletStatus, enrollPasskeyDefault, approveDeviceWithPasskey } = useCavos();
if (walletStatus.needsDeviceApproval) {
await approveDeviceWithPasskey(); // unwraps the MasterDEK; not an on-chain device list
}
if (!walletStatus.hasPasskey) {
await enrollPasskeyDefault(); // after login — sign-up never prompts
}
walletStatus.recovery is { protected: boolean; methods: ("passkey" | "social")[] }. It starts false and flips once the lookup returns. Read it as “not known to be protected” during the first moments of a connect.
The kit documents four paths: another signed-in device, a passkey, a recovery code (grandfathered Stellar only), and opt-in hardware-isolated social recovery in an AWS Nitro Enclave that can only unwrap a MasterDEK. If every device and every restore factor is gone, Cavos cannot recover the wallet. See recovery.
What to read next
- What is an embedded wallet? — custody, key models, and how to choose an SDK.
- How to add an embedded wallet to a React app —
CavosProvider, first sponsored transaction.
- Embedded wallet providers compared — pricing, chains, and key models.
- Chain pages: Starknet, Solana, Stellar.
- Quickstart, React, and API reference.
- Pricing for the free 1,000 creates and paid recovery options.