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

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 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

Stateexecute() Behavior
Not authenticatedThrows “Wallet not initialized”
Session not registeredGasless only — uses JWT signature (registers + executes atomically)
Session registered + activeSigns 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);