Concepts
The every-chain architecture, non-custodial invariant, registry-first address model, and lazy deployment.
Every-chain, not one account model everywhere
Cavos standardizes the integration contract, not the blockchain. Every adapter must provide stable identity, local user authority, typed execution, non-custodial recovery, and a documented sponsorship path. The implementation remains native to the selected network.
StarknetAdapter, SolanaAdapter, and StellarAdapter are available today.
Future chains are roadmap adapters until their code, security review, and
end-to-end validation are complete.
Non-custodial invariant
The Cavos backend must not possess authority that can move user funds or enroll itself as a signer.
- Starknet: the Cairo account authorizes P-256 device signers.
- Solana: the device-account program authorizes P-256 keys whose signatures were verified by the native precompile.
- Stellar: the classic account authorizes an ed25519 control key that is encrypted on-chain and unwrapped locally.
Registries and recovery services coordinate public state or messages. Paymasters and relayers fund or submit transactions. None of those roles is a substitute for user authorization.
Address resolution: registry-first
The address is not derived purely from identity. It is looked up in the registry first, and only computed when the user has no existing wallet.
(userId, appId, chain, network) → registry lookup → addressWhen you call connect:
- Registry lookup: The kit asks the Cavos registry: does this user already have a wallet for this app and chain?
- If found: The existing address is returned. The device checks whether it is an authorized signer on-chain.
- If not found: The device computes a candidate address and claims it in the registry. The first device to register wins.
This design prevents a second wallet from being created silently when the same user connects from a different device. The second device sees the existing address and enters device approval instead of minting a duplicate.
Why not deterministic from identity? A purely deterministic address would let any device derive it, but then how would the account know which device is authorized? The address includes the first device's public key (directly on Solana/Starknet, indirectly on Stellar), which is why the registry records which address belongs to which user.
The appSalt parameter
appSalt is not the address name. It is the local device-key slot — a
namespace that keeps device keys separate across different apps on the same
device. The same user on the same device but different apps gets different
device keys, which is correct: each app has its own wallet.
The registry key is (userId, appId, chain, network). The appSalt affects the
device-key derivation, not the address derivation directly.
Lazy deployment
Connect never deploys. It resolves the address and checks on-chain status,
but the account is not created until the first execute.
| Connect result | What happens |
|---|---|
| User has no wallet | Address is computed and claimed in registry. status: "undeployed". |
| User has a wallet, this device is authorized | status: "ready". |
| User has a wallet, this device is not authorized | status: "needs-device-approval". |
An undeployed wallet can:
- Sign messages (
signMessage) — the signature comes from the local device key, which exists regardless of on-chain state. - Execute — the first
executecall deploys the account and runs the user's operation atomically in a single sponsored transaction.
This lazy model means users see their address immediately and can share it before their first transaction. The account is created only when they use it.
Status values
Every wallet has a status property:
| Status | Meaning | Can execute? | Can signMessage? |
|---|---|---|---|
undeployed | Address derived, no on-chain account yet | Yes (first execute deploys) | Yes |
ready | Account deployed, this device authorized | Yes | Yes |
needs-device-approval | Account deployed, this device not authorized | No | No |
The only status that blocks execution is needs-device-approval. Both
undeployed and ready can execute.
Multi-chain sessions
One connect call can configure multiple chains. The session provides access to
all of them without re-authenticating:
const session = await Cavos.connect({
chains: ["solana", "stellar", "starknet"],
defaultChain: "solana",
network: "testnet",
identity,
appSalt: "my-app",
appId,
paymasterApiKey, // Starknet only
});
// The session IS the default chain's wallet
session.address; // Solana address
session.status; // Solana status
// Access other chains
session.wallet("stellar"); // CavosStellar instance
session.chainStatus("stellar");
session.chainAddress("stellar");Each chain has its own address (derived from the same identity), its own deployment status, and its own device authorization state. They are independent accounts sharing one login.
Device-native authority
"Device-native" describes where sensitive authority is created or unlocked, not one universal curve:
- Starknet and Solana use non-extractable P-256 device signers directly.
- Stellar uses a device-bound P-256 unwrap key to recover the active ed25519 control key locally.
On mobile, Cavos prefers hardware-backed key storage and reports the actual capabilities of the device. Hardware claims must be validated on physical devices, not inferred from a simulator.
Network environments
The shared API accepts network: "testnet" | "mainnet" and the selected adapter
resolves the concrete network:
const session = await Cavos.connect({
chains: ["stellar"],
defaultChain: "stellar",
network: "testnet", // → stellar-testnet
identity,
appSalt: "my-app",
appId,
});network | Starknet | Solana | Stellar |
|---|---|---|---|
testnet | sepolia | solana-devnet | stellar-testnet |
mainnet | mainnet | solana-mainnet | stellar-mainnet |
Chain packages also export concrete network constants for advanced use. See the individual chain guides.
Typed native execution
Cavos.connect returns a CavosWallet & CavosSession union. Narrow on
wallet.chain before execution. This keeps onboarding portable without hiding
important differences in calls, units, replay protection, or confirmation.
const wallet = session.wallet("solana");
if (wallet.chain === "starknet") {
await wallet.execute(calls); // Call[]
} else if (wallet.chain === "solana") {
await wallet.execute(1_000_000n, destination); // lamports, base58
} else if (wallet.chain === "stellar") {
await wallet.execute(1_000_000n, destination); // stroops, G…
}That same rule applies to every future adapter: extend the discriminated union, document capabilities, and preserve native semantics.
Stellar
Use @cavos/kit on Stellar — a classic G… multisig account whose control key is sealed on-chain and unlocked by a silent device key, gasless via the Cavos relayer, and able to sign Soroban contract calls and Trustless Work escrows.
API reference
Public exports of @cavos/kit — the surface you build against.