Skip to main content

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
    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>
  );
}

Call Format

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 method in your session policy’s allowedMethods. Calls to unauthorized methods 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. This is useful for:
  • Approve + Swap
  • Multi-token transfers
  • Complex DeFi operations

Gasless by Default

All transactions through session keys are gasless:
  • User pays nothing
  • Gas is sponsored by AVNU Paymaster
  • No ETH balance required
// All of these are gasless:
await execute({ contractAddress: '...', entrypoint: 'transfer', calldata: [...] });
await execute([call1, call2, call3]);

Session Policy Enforcement

Transactions are validated against your session policy:
// Session created with:
const policy = {
  allowedMethods: [
    { contractAddress: TOKEN_A, selector: 'transfer' },
    { contractAddress: TOKEN_A, selector: 'approve' },
  ],
  expiresAt: Date.now() + 60 * 60 * 1000,
};

// ✅ 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: "Method transfer on TOKEN_B not allowed by session"

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('Spending limit exceeded')) {
    alert('This transaction exceeds your spending limit.');
  } else {
    // Contract execution error
    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://...' });

// Wait for transaction to be accepted
await provider.waitForTransaction(txHash);

// Get receipt
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'],
  },
]);

Session Lifecycle

Stateexecute() Behavior
Not authenticatedThrows “Wallet not initialized”
Session registeredSigns with session key ✅
Session not registered yetAuto-uses JWT signature (registers + executes atomically)
Expired (within grace)Auto-renews session, then executes
Expired (beyond grace)Throws “SESSION_EXPIRED” — re-login required
To check wallet state before executing:
const { walletStatus } = useCavos();

if (!walletStatus.isReady) {
  // Wallet still setting up (deploying or registering session)
  return <p>Setting up wallet...</p>;
}

// Safe to execute
await execute(calls);