Cavos

Embedded Stellar Wallet

A device-native, self-custodial embedded wallet for Stellar. 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).

On the current path the G… is HKDF of the same MasterDEK as Solana. In the browser that Ed25519 control key is a non-extractable WebCrypto key held by the Cavos vault. On React Native it uses the OS keystore. The SDK does not enforce non-extractability on Node or other server runtimes.

Evaluate a Stellar embedded wallet

This page is for teams that need a Stellar embedded wallet inside the product — a classic G… the rest of the ecosystem already understands, not a Soroban contract account and not a Freighter connect flow. Login selects the user. @cavos/kit looks up (userId, appId, chain, network) in the registry and returns that address, or claims a new one. The first device to register wins. Signing is local. Cavos cannot see the key, cannot sign with it, and cannot move funds.

Authentication is separate from signing. Hosted login is Google, Apple, or an enclave-verified 9-character email code the recovery enclave mails and verifies. If you already authenticate users, pass a stable userId. Use an immutable primary key, not an email that can change. Email sign-in is not available on React Native yet; use Google or Apple there. The address is available before the account exists on-chain. Share it, fund it, then execute.

How the Stellar account signs

Cavos provisions a classic G… Stellar account — not a Soroban contract. It is the same address format that wallets, exchanges, and every Stellar tool already understand. With enclave or passkey, that G… is HKDF of the MasterDEK (cavos-ed25519-stellar-v1, salted by appSalt). A new device unwraps the DEK. It does not add a second Horizon signer. wallet.nativeDek is true on this path. Changing appSalt derives a different address that will not match the registry row.

Grandfathered wallets (no enclave, no passkey at connect) still use extra weight-1 Horizon signers: the first device's random control key is the G…, and a recovery code or passkey can be an additional signer. That path is load-only. New apps should use enclave or passkey.

Connect never creates the account. An undeployed wallet already has a G… from the registry; the first execute creates the account (sponsored, 0 XLM cost to the user) and runs the operation. After that, status is ready. execute(amount, destination) moves amount stroops of native XLM (1 XLM = 10,000,000 stroops). wallet.balance() returns that native balance. signMessage works while undeployed. An App ID activates the gasless relayer.

Reserves, fees, and Soroban

Every Stellar account locks XLM reserves (a base reserve plus about 0.5 XLM per subentry). With the relayer, those reserves and all fees are sponsored, so the user locks no XLM. The relayer is only a fee payer and reserve sponsor — never a custodian or identity authority. A bad or absent relayer can cost fees; it cannot move funds or squat an address. Pass { sponsored: false } to submit directly from the account's own XLM. Without an App ID, a funded stellarSourceKeypair can fund reserves and pay fees.

Because the control key is a real Stellar signer, the same G… can invoke Soroban contracts and satisfy require_auth. invokeContract simulates the call, re-signs only this account's auth entry, and submits it — sponsored by default. invokeContract and addTrustline require status === "ready"; call execute first if the account does not exist yet. signXdr signs an unsigned envelope without submitting it. The kit also ships deployEscrow and TrustlessWorkEscrow for milestone escrows. Paying reserves in a held token (USDC, not sponsored XLM) is a separate package, @cavos/reserve, signed with wallet.signXdr.

Recovery on a new device

On the native path there is one spend key. After login, call enrollPasskeyDefault(). Signing up never prompts. The passkey stores an encrypted copy of the MasterDEK; it is not 2FA and not an extra Horizon signer. wallet.enrollPasskey(...) is a no-op on native accounts. Hardware-isolated recovery, opt-in per environment, unwraps the same DEK when the user signs in again with Google, Apple, or an enclave-verified email code.

Recovery codes restore grandfathered wallets only. On that older path, generateRecoveryCode() plus wallet.setupRecovery(code) adds an extra Horizon signer. Do not use a recovery code to restore a nativeDek wallet. 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: ["stellar"]. network: "testnet" resolves to stellar-testnet. execute works when status is "undeployed" or "ready". Narrow on wallet.chain === "stellar". In React, call wallet.execute on the Stellar handle; the context execute is Starknet-only.

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

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

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

const hash = await wallet.execute(
  10_000_000n, // 1 XLM in stroops
  "GDESTINATION...ADDRESS",
);

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.

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 Stellar-only

Login, registry lookup, the MasterDEK, passkey and enclave restore, and the kit packages are shared with the Starknet embedded wallet and the Solana embedded wallet. One Cavos.connect can configure all three. Each chain still has its own address.

Stellar-only: the classic G…, stroop execute, reserve sponsorship, invokeContract, trustlines, signXdr, and grandfathered Horizon signers on older wallets. 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 on-chain account is the sole authority over signers.

Classic G… address

Standard Stellar address format compatible with exchanges, wallets, and ecosystem tools. Not a Soroban contract.

Sponsored reserves

The Cavos relayer sponsors account reserves and pays transaction fees when an App ID is set, so users hold no XLM to start.

Soroban support

The account can invoke Soroban contracts and satisfy require_auth for on-chain apps. Trustlines and Trustless Work wrappers ship in the kit.

Passkey and enclave restore

On the native path a passkey stores an encrypted copy of the MasterDEK. Enclave sign-in unwraps the same DEK. Recovery codes are grandfathered only.

Lazy deploy

Connect derives the address immediately. The on-chain account is created on first execute, atomically.

Frequently asked

What is a Stellar embedded wallet?

A Stellar embedded wallet is a classic G… Stellar account provisioned inside your application. On the current path the spend key is HKDF of the same MasterDEK as Solana. It is not a Soroban contract, so exchanges and existing tools already understand the address.

Does Cavos use extra Horizon signers on new Stellar wallets?

Not on the current path. New wallets derive one Ed25519 spend key from the MasterDEK. A new device restores that DEK; it does not add a second Horizon signer. Grandfathered wallets that never enrolled still load with extra weight-1 Horizon signers, including recovery codes. That path is load-only.

How do reserves and fees work on a Stellar embedded wallet?

When an App ID is set, the Cavos relayer is the transaction source and fee payer and sponsors the account’s XLM reserves. The user does not need to hold lumens to start. Pass { sponsored: false } to have the account pay its own fee. The relayer cannot move funds or squat an address.

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

Install @cavos/kit, create an app in the Cavos dashboard, and pass the App ID into Cavos.connect or CavosProvider with chains: ["stellar"]. Narrow on wallet.chain === "stellar", then call execute(amount, destination) in stroops. Connect never creates the account; the first execute creates it and runs atomically.

Can a Stellar embedded wallet invoke Soroban contracts?

Yes. invokeContract simulates the call, re-signs this account’s Soroban auth entry with the control key, and submits it. The G… can satisfy require_auth in on-chain apps. invokeContract and addTrustline require a deployed account (status ready). Native XLM transfers can create the account on first execute.

How does recovery work on a Stellar embedded wallet?

On the native path, restore the MasterDEK with a passkey (enrollPasskeyDefault after login) or by signing in again when hardware-isolated recovery is enabled. Recovery codes restore grandfathered wallets only; they do not restore native accounts. If every device and restore factor is gone, Cavos cannot recover the wallet.

Start building on Stellar.

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