Documentation Index
Fetch the complete documentation index at: https://docs.cavos.xyz/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Cavos uses session keys for all transactions. After creating a session, users can execute unlimited transactions without any signature prompts.
[!NOTE]
All transactions are signed with the session key, not the user’s private key. The private key is cleared from memory after session creation.
Prerequisites
Before executing transactions, you need to be authenticated. After login(), account deployment and session registration happen automatically:
import { useCavos } from '@cavos/react';
function App() {
const { login, walletStatus } = useCavos();
const handleSetup = async () => {
await login('google'); // Deploy + register session happen automatically
};
if (!walletStatus.isReady) {
return <button onClick={handleSetup}>Login with Google</button>;
}
return <TransactionUI />;
}
Basic Transaction
Once a session is active, transactions execute without prompts:
import { useCavos } from '@cavos/react';
function TransferButton() {
const { execute, walletStatus } = useCavos();
const handleTransfer = async () => {
// No signature popup! Session key signs automatically. Gas is sponsored.
const txHash = await execute({
contractAddress: '0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7',
entrypoint: 'transfer',
calldata: ['0xrecipient', '1000000000000000000', '0'],
});
console.log('Transaction hash:', txHash);
};
return (
<button onClick={handleTransfer} disabled={!walletStatus.isReady}>
Transfer (Signature-Free)
</button>
);
}
Each call follows the starknet.js Call interface:
interface Call {
contractAddress: string; // Contract to call
entrypoint: string; // Function name (must be in session policy)
calldata?: string[]; // Function arguments
}
[!IMPORTANT]
The call must match a contract in your session policy’s allowedContracts. Calls to unauthorized contracts will fail.
Multiple Calls (Multicall)
Execute multiple calls atomically in a single transaction:
const txHash = await execute([
{
contractAddress: tokenAddress,
entrypoint: 'approve',
calldata: [spenderAddress, amount, '0'],
},
{
contractAddress: dexAddress,
entrypoint: 'swap',
calldata: [tokenIn, tokenOut, amountIn, minAmountOut],
},
]);
All calls succeed or fail together.
Gas Options
execute() accepts an optional options object to control gas sponsorship:
execute(calls, options?: { gasless?: boolean })
Gasless (Default)
Gas is sponsored by the Cavos Paymaster. Users pay nothing and need no STRK balance.
// Both are equivalent — gasless is the default
await execute(calls);
await execute(calls, { gasless: true });
User Pays Gas
Pass { gasless: false } to have the wallet pay its own gas from its STRK balance.
await execute(calls, { gasless: false });
Requirements for user-paid transactions:
- The session must already be registered on-chain (at least one gasless tx must have been executed first, or
registerCurrentSession() called explicitly).
- The wallet must hold enough STRK to cover the transaction fee.
[!NOTE]
The SDK estimates fees using a raw starknet_estimateFee call (not starknet.js’s built-in estimator, which is incompatible with custom account signatures). A 5M L2-gas overhead is added on top to cover __validate__ execution, which is not included in SKIP_VALIDATE estimates.
// Example: opt out of sponsorship for a specific action
const handleBridgeTx = async () => {
try {
const txHash = await execute(bridgeCall, { gasless: false });
console.log('Bridge tx:', txHash);
} catch (err) {
if (err.message.includes('non-sponsored transaction without a registered session')) {
// Must execute one sponsored tx first to register the session
await execute(someOtherCall); // sponsored — registers session
// then retry
}
}
};
Session Policy Enforcement
Transactions are validated against your session policy:
const policy = {
allowedContracts: [TOKEN_A],
maxCallsPerTx: 5,
};
// ✅ This works (in policy):
await execute({ contractAddress: TOKEN_A, entrypoint: 'transfer', calldata: [...] });
// ❌ This fails (not in policy):
await execute({ contractAddress: TOKEN_B, entrypoint: 'transfer', calldata: [...] });
Error Handling
try {
const txHash = await execute(calls);
console.log('Success:', txHash);
} catch (error) {
if (error.message.includes('SESSION_EXPIRED')) {
// Session expired beyond grace period — user must re-login
await login('google');
} else if (error.message.includes('non-sponsored transaction without a registered session')) {
// Execute one sponsored tx first, or call registerCurrentSession()
await registerCurrentSession();
} else if (error.message.includes('Spending limit exceeded')) {
alert('This transaction exceeds your spending limit.');
} else {
console.error('Transaction failed:', error);
}
}
Transaction Status
The execute() function returns the transaction hash immediately. To track status:
import { RpcProvider } from 'starknet';
const provider = new RpcProvider({ nodeUrl: 'https://...' });
await provider.waitForTransaction(txHash);
const receipt = await provider.getTransactionReceipt(txHash);
console.log('Status:', receipt.finality_status);
Common Patterns
ERC20 Transfer
const TOKEN_ADDRESS = '0x04718f5a0Fc34cC1AF16A1cdee98fFB20C31f5cD61D6Ab07201858f4287c938D';
await execute({
contractAddress: TOKEN_ADDRESS,
entrypoint: 'transfer',
calldata: [recipientAddress, amountLow, amountHigh], // u256 split
});
Approve + Action
await execute([
{
contractAddress: tokenAddress,
entrypoint: 'approve',
calldata: [protocolAddress, amount, '0'],
},
{
contractAddress: protocolAddress,
entrypoint: 'deposit',
calldata: [amount, '0'],
},
]);
User-Paid DeFi Action
// For apps where the developer doesn't want to sponsor certain high-value operations
await execute(
[approveCall, depositCall],
{ gasless: false }
);
Session Lifecycle
| State | execute() Behavior |
|---|
| Not authenticated | Throws “Wallet not initialized” |
| Session not registered | Gasless only — uses JWT signature (registers + executes atomically) |
| Session registered + active | Signs with session key ✅ (gasless or user-pays) |
| Expired (within grace) | Auto-renews session, then executes |
| Expired (beyond grace) | Throws “SESSION_EXPIRED” — re-login required |
const { walletStatus } = useCavos();
if (!walletStatus.isReady) {
return <p>Setting up wallet...</p>;
}
await execute(calls);