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 an active session:
import { useCavos, useSession } from '@cavos/react';

function App() {
  const { login } = useCavos();
  const { createSession, hasActiveSession } = useSession();

  const handleSetup = async () => {
    await login('google');
    await createSession({
      allowedMethods: [
        { contractAddress: '0x...', selector: 'transfer' },
        { contractAddress: '0x...', selector: 'approve' },
      ],
      expiresAt: Date.now() + 60 * 60 * 1000, // 1 hour
    });
  };

  if (!hasActiveSession) {
    return <button onClick={handleSetup}>Setup Wallet</button>;
  }

  return <TransactionUI />;
}

Basic Transaction

Once a session is active, transactions execute without prompts:
import { useCavos, useSession } from '@cavos/react';

function TransferButton() {
  const { execute } = useCavos();
  const { hasActiveSession } = useSession();

  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={!hasActiveSession}>
      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('not allowed by session')) {
    // Call not in session policy
    console.error('This action is not authorized by your session');
  } else if (error.message.includes('No active session')) {
    // Session expired or not created
    await createSession(policy);
  } else if (error.message.includes('Session has expired')) {
    // Re-authenticate and create new session
    await login('google');
    await createSession(policy);
  } 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
No sessionThrows “No active session”
Active sessionSigns with session key ✅
Expired sessionThrows “Session has expired”
Method not in policyThrows “not allowed by session”
To check session state before executing:
const { hasActiveSession } = useSession();

if (!hasActiveSession) {
  // Prompt user to create session
  await createSession(policy);
}

// Now safe to execute
await execute(calls);