Wallet actions
What you do with a connected wallet — read balances, send tokens, sign messages, and sign without submitting.
Once connected, this page covers the common things you do — all via useCavos()
inside a <CavosProvider>. See React for the provider/hook setup.
When can you execute? Both undeployed and ready wallets can execute. The
first execution on an undeployed wallet deploys the account atomically. Only
needs-device-approval blocks execution — route that to a passkey or
multi-device approval.
Every chain-specific call narrows on wallet.chain first, because execute
keeps each chain's native signature. See Chains.
Read the native balance
The kit exposes each chain's balance read differently.
Solana
CavosSolana exposes the underlying @solana/web3.js Connection, so you read
SOL directly. Balance is in lamports (1 SOL = 10⁹ lamports).
const { wallet, address } = useCavos();
if (wallet?.chain === "solana") {
const lamports = await wallet.connection.getBalance(address); // number
const sol = lamports / 1e9;
}Stellar
CavosStellar has a balance() method. It returns native XLM in stroops
(1 XLM = 10⁷ stroops).
if (wallet?.chain === "stellar") {
const stroops = await wallet.balance(); // bigint
const xlm = Number(stroops) / 1e7;
}Starknet
There is no single "native balance" call in the kit for Starknet (the fee token
is an ERC-20). Read a specific token with a balanceOf call via
execute, or query the RPC directly with starknet.js.
Fund from a testnet faucet
Only on testnet. Handy for demos and first-run UX.
Solana — airdrop
if (wallet?.chain === "solana") {
const sig = await wallet.connection.requestAirdrop(address, 1_000_000_000); // 1 SOL
// confirm with connection.getSignatureStatus(sig) if you need to wait
}Public Solana devnet rate-limits airdrops. For reliable funding, use a private
RPC (Helius/Alchemy) as your rpcUrl, or the official Solana faucet.
Stellar — friendbot
Stellar testnet is funded by the public friendbot:
await fetch(`https://friendbot.stellar.org?addr=${encodeURIComponent(address)}`);Send native tokens
Solana — SOL transfer
CavosSolana.execute(amount, destination) transfers SOL, sponsored by the
relayer when appId is set. amount is in lamports (bigint).
if (wallet?.chain === "solana") {
const txHash = await wallet.execute(1_000_000_000n, destinationAddress); // 1 SOL
}Stellar — XLM transfer
Same signature, amount in stroops (bigint).
if (wallet?.chain === "stellar") {
const txHash = await wallet.execute(10_000_0000n, destinationAddress); // 1 XLM
}Both return the transaction hash as a string. Build explorer links per chain —
Solana: https://explorer.solana.com/tx/${hash}?cluster=devnet; Stellar testnet:
https://stellar.expert/explorer/testnet/tx/${hash}.
Starknet — arbitrary calls
On Starknet, use the context's execute(calls) for a multicall (transfers,
swaps, any contract interaction). It's sponsored by default.
const { execute } = useCavos();
const { transactionHash } = await execute([
{ contractAddress: TOKEN, entrypoint: "transfer", calldata: [to, amountLow, amountHigh] },
]);Formatting amounts? Parse a human input ("1.5") to base units by multiplying
by 10 ** decimals and rounding; format a base-unit bigint back by dividing.
Decimals per chain: Solana 9, Stellar 7, Starknet fee token 18.
Security after sign-in
These are shared wrappers on useCavos() for the three current adapters. Their
cryptographic implementation remains chain-specific. They are the same primitives documented in
Passkeys and Recovery, surfaced through the
React hook.
Passkey (secure account access)
const { walletStatus, enrollPasskeyDefault, passkeySupported } = useCavos();
if (passkeySupported && !walletStatus.hasPasskey) {
await enrollPasskeyDefault(); // prompts the platform authenticator once
}walletStatus.hasPasskey flips to true once enrolled.
Recovery code
Starknet (and grandfathered Stellar). Native Solana/Stellar: setupRecovery is
a no-op — restore with the enclave or approveDeviceWithPasskey().
const { setupRecovery } = useCavos();
const code = await setupRecovery(); // string | undefined — show it ONCE when presentThe recovery code is returned once. Display it immediately and tell the user to store it somewhere safe. Only its derived public key is registered on-chain — the code itself never leaves the device.
Approve a new device
When a user signs in on a fresh device, walletStatus.needsDeviceApproval is
true and they can't transact yet. If they have a synced passkey:
const { walletStatus, approveDeviceWithPasskey } = useCavos();
if (walletStatus.needsDeviceApproval) {
await approveDeviceWithPasskey(); // one passkey prompt authorizes this device
}See Multi-device for the full device-approval flow.
Sign messages & transactions
Beyond sending transactions, a wallet needs to sign data off-chain (prove ownership without moving funds) and sign transactions without submitting (hand them to your own relayer). Both are available on every current adapter; future adapters must document and implement them before they are advertised.
signMessage — off-chain signature
Sign an arbitrary message with the wallet's signing key. Nothing is submitted,
no gas is paid, no state changes. Returns a uniform MessageSignature:
const { signMessage } = useCavos(); // or wallet.signMessage(...)
const { signature, publicKey, curve } = await signMessage("Sign in to dapp.com");| Field | Meaning |
|---|---|
signature | 64-byte signature (r‖s for secp256r1, ed25519 raw). |
publicKey | The verifier input — Starknet: device P-256 hex; Solana/Stellar: the account address (base58 / G…). |
curve | "secp256r1" (Starknet) or "ed25519" (Solana, Stellar). |
Per-chain details:
- Starknet — signs
sha256("Cavos Signed Message:\n<len>\n" + message)with the device P-256 key.publicKeyis the uncompressed04‖x‖yhex. - Solana — signs the prefixed message with the Ed25519 spend key.
publicKeyis the base58 address.curveis"ed25519". - Stellar — the signing key is the ed25519 control key (not P-256), so
curveis"ed25519"andpublicKeyis theG…control address.
Verifying off-chain (what a third party / backend does):
import { p256 } from "@noble/curves/p256";
import { sha256 } from "@noble/hashes/sha256";
import { Keypair } from "@stellar/stellar-sdk";
if (curve === "secp256r1") {
// Starknet — verify the P-256 signature over sha256(prefixedMessage).
const valid = p256.verify(signature, sha256(prefixedMessage), publicKeyHex);
} else {
// Solana / Stellar — ed25519.
}The verifier must reconstruct the same prefixed message the signer used (see the prefix per chain above). For Sign-In-with-X flows, embed a nonce + domain in the message string.
signTransaction — sign without submit
Sign a transaction WITHOUT broadcasting it. Each chain returns its own signed
artifact (narrow on chain):
const { wallet } = useCavos();
if (wallet?.chain === "starknet") {
const signed = await wallet.signTransaction(calls); // → { calldata, signature, nonce, resourceBounds, version }
} else if (wallet?.chain === "stellar") {
const signed = await wallet.signTransaction(amount, destination); // → { xdr }
} else if (wallet?.chain === "solana") {
const signed = await wallet.signTransaction(amount, destination); // → { message, signature, publicKey }
}What you get back, and what to do with it:
- Starknet — the signed invoke (
calldata, the 5-felt devicesignature,nonce,resourceBounds,version). A relayer broadcasts it via the account'sinvokeFunction. - Stellar — the control-signed inner transaction as base64 XDR. Submit it directly via Horizon, or hand it to a relayer to fee-bump (the control signature stays valid through the wrap).
- Solana —
{ message, signature, publicKey }wheresignatureis Ed25519 over the Solana message. A relayer can fee-pay; it must not replace the user signature.
Solana signTransaction is a user-signed message, not a fully submitted
transaction. Attach the relayer (or feePayer) signature and broadcast.
All signTransaction outputs are single-use and bind to on-chain state.
Starknet binds to the nonce + resource bounds; Stellar to the sequence number
(and a 180s timeout); Solana to the on-chain account nonce. If any other
transaction from the same account is submitted first, the signature becomes
invalid. Broadcast promptly.