Skip to main content

What are Session Keys?

Session keys allow your application to execute transactions on behalf of a user without requiring their owner private key for every action. Instead, a temporary keypair is created and registered on-chain with specific permissions (time limit, call limit, allowed functions). This enables gasless, frictionless UX for gaming, DeFi automation, social apps, and more.
Session keys only work with CHIPI wallets - OpenZeppelin accounts extended with SNIP-9 session key support.

When to Use Session Keys

Use CaseWhy Sessions Help
GamingPlayers make moves without wallet popups
DeFi AutomationYield optimizers execute strategies automatically
Social dAppsPost, like, comment without friction
SubscriptionsRecurring payments without manual approval
AirdropsZero-friction claiming for users

Requirements

  • A CHIPI wallet (not ARGENT)
  • Valid bearer token from your auth provider
  • User’s encryptKey (PIN) for signing

1

Install the Backend SDK

npm install @chipi-stack/backend
2

Initialize the SDK

import { ChipiSDK } from "@chipi-stack/backend";

const sdk = new ChipiSDK({
  apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
  apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});
3

Create a Session Key

Generate a session keypair locally. The SDK returns the data but does NOT store it - you must persist it on the client side.
// Create session key (self-custodial - store in client localStorage)
const session = sdk.sessions.createSessionKey({
  encryptKey: "user-secure-pin",
  durationSeconds: 3600, // 1 hour (default: 6 hours)
});

console.log(session);
// {
//   publicKey: "0x...",           // Session public key
//   encryptedPrivateKey: "...",   // AES encrypted with encryptKey
//   validUntil: 1702500000        // Unix timestamp (seconds)
// }

// Store in client-side secure storage (NOT your database!)
localStorage.setItem('chipiSession', JSON.stringify(session));
Never store session keys in your backend database. Store only in client-side secure storage (localStorage, Secure Enclave, Keychain).
4

Register Session On-Chain

Before using a session key, you must register it on the CHIPI wallet smart contract. This requires the owner’s signature (one-time setup).
// Get user's wallet
const wallet = await sdk.getWallet({ externalUserId: "user-123" });

// Register session on contract (uses owner signature via paymaster)
const txHash = await sdk.sessions.addSessionKeyToContract({
  encryptKey: "user-secure-pin",
  wallet: {
    publicKey: wallet.publicKey,
    encryptedPrivateKey: wallet.encryptedPrivateKey,
    walletType: "CHIPI",
  },
  sessionConfig: {
    sessionPublicKey: session.publicKey,
    validUntil: session.validUntil,
    maxCalls: 100,                // Max transactions allowed
    allowedEntrypoints: [],       // Empty = all functions allowed
  },
}, bearerToken);

console.log("Session registered:", txHash);

Session Configuration Options

ParameterTypeDescription
sessionPublicKeystringPublic key from createSessionKey()
validUntilnumberUnix timestamp when session expires
maxCallsnumberMaximum transactions allowed (decrements with each use)
allowedEntrypointsstring[]Whitelisted function selectors. Empty = all allowed
For security, whitelist specific entrypoints when possible:
allowedEntrypoints: [
  "0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e", // transfer
  "0x219209e083275171774dab1df80982e9df2096516f06319c5c6d71ae0a8480c", // approve
]
5

Execute Transactions with Session

Now you can execute transactions using the session key - no owner private key needed!
// Retrieve session from client storage
const session = JSON.parse(localStorage.getItem('chipiSession')!);

// Execute transaction with session signature (4-element format)
const txHash = await sdk.executeTransactionWithSession({
  params: {
    encryptKey: "user-secure-pin",
    wallet: {
      publicKey: wallet.publicKey,
      encryptedPrivateKey: wallet.encryptedPrivateKey,
      walletType: "CHIPI",
    },
    session,
    calls: [
      {
        contractAddress: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
        entrypoint: "transfer",
        calldata: [recipientAddress, amount, "0x0"],
      },
    ],
  },
  bearerToken,
});

console.log("Transaction executed:", txHash);
Signature Format Difference:
  • Owner signature: [r, s] (2 elements)
  • Session signature: [sessionPubKey, r, s, validUntil] (4 elements)
The SDK handles this automatically.
6

Query Session Status

Check if a session is still active and how many calls remain:
const sessionData = await sdk.sessions.getSessionData({
  walletAddress: wallet.publicKey,
  sessionPublicKey: session.publicKey,
});

console.log(sessionData);
// {
//   isActive: true,
//   validUntil: 1702500000,
//   remainingCalls: 95,
//   allowedEntrypoints: []
// }

if (sessionData.isActive) {
  console.log(`Session has ${sessionData.remainingCalls} calls remaining`);
}
7

Revoke Session

Always revoke sessions when no longer needed or when the user logs out:
const revokeTxHash = await sdk.sessions.revokeSessionKey({
  encryptKey: "user-secure-pin",
  wallet: {
    publicKey: wallet.publicKey,
    encryptedPrivateKey: wallet.encryptedPrivateKey,
    walletType: "CHIPI",
  },
  sessionPublicKey: session.publicKey,
}, bearerToken);

// Clear from client storage
localStorage.removeItem('chipiSession');

console.log("Session revoked:", revokeTxHash);
Always revoke sessions on logout to prevent unauthorized access.

Security and Best Practices

Storage Guidelines

Never store session keys in your backend database. This defeats the purpose of self-custodial security.
Store session keys only in client-side secure storage:
PlatformRecommended Storage
WeblocalStorage or sessionStorage
iOSSecure Enclave / Keychain
AndroidAndroid Keystore
React Nativeexpo-secure-store or react-native-keychain
Use sessionStorage instead of localStorage for sessions that should expire when the browser tab closes.

Lifecycle Best Practices

  1. Always revoke on logout - Never leave active sessions when user signs out
  2. Set short durations - Use 1-6 hours, not days
  3. Limit maxCalls - Set realistic limits based on expected usage
  4. Whitelist entrypoints - Restrict to only the functions your app needs

Example: Secure Logout Handler

const handleLogout = async () => {
  // Retrieve active session
  const sessionStr = localStorage.getItem('chipiSession');
  
  if (sessionStr) {
    const session = JSON.parse(sessionStr);
    
    try {
      // Revoke session on-chain before logout
      await sdk.sessions.revokeSessionKey({
        encryptKey: userPin,
        wallet,
        sessionPublicKey: session.publicKey,
      }, bearerToken);
    } catch (error) {
      console.warn("Failed to revoke session:", error);
      // Continue with logout even if revoke fails
    }
    
    // Clear from local storage
    localStorage.removeItem('chipiSession');
  }
  
  // Proceed with auth logout
  await authProvider.signOut();
};

Example: Session with Restricted Permissions

// Create a session that can ONLY transfer USDC
const restrictedSession = await sdk.sessions.addSessionKeyToContract({
  encryptKey: userPin,
  wallet,
  sessionConfig: {
    sessionPublicKey: session.publicKey,
    validUntil: session.validUntil,
    maxCalls: 10,  // Only 10 transfers allowed
    allowedEntrypoints: [
      "0x83afd3f4caedc6eebf44246fe54e38c95e3179a5ec9ea81740eca5b482d12e", // transfer
    ],
  },
}, bearerToken);

Full Example: Gaming Session

import { ChipiSDK } from "@chipi-stack/backend";

const sdk = new ChipiSDK({
  apiPublicKey: process.env.CHIPI_PUBLIC_KEY!,
  apiSecretKey: process.env.CHIPI_SECRET_KEY!,
});

// Game constants
const GAME_CONTRACT = "0x...";
const MAKE_MOVE_SELECTOR = "0x...";
const CLAIM_REWARD_SELECTOR = "0x...";

async function startGameSession(userId: string, userPin: string, bearerToken: string) {
  // Get player's CHIPI wallet
  const wallet = await sdk.getWallet({ externalUserId: userId });
  
  // Create 24-hour gaming session
  const session = sdk.sessions.createSessionKey({
    encryptKey: userPin,
    durationSeconds: 24 * 60 * 60, // 24 hours
  });
  
  // Register with game-only permissions
  await sdk.sessions.addSessionKeyToContract({
    encryptKey: userPin,
    wallet: { ...wallet, walletType: "CHIPI" },
    sessionConfig: {
      sessionPublicKey: session.publicKey,
      validUntil: session.validUntil,
      maxCalls: 1000,  // Many moves allowed
      allowedEntrypoints: [MAKE_MOVE_SELECTOR, CLAIM_REWARD_SELECTOR],
    },
  }, bearerToken);
  
  // Store session in client
  localStorage.setItem('gameSession', JSON.stringify(session));
  
  return session;
}

async function makeMove(moveData: string, bearerToken: string) {
  const session = JSON.parse(localStorage.getItem('gameSession')!);
  const wallet = await sdk.getWallet({ externalUserId: currentUserId });
  
  // Execute move instantly - no wallet popup!
  return sdk.executeTransactionWithSession({
    params: {
      encryptKey: userPin,
      wallet: { ...wallet, walletType: "CHIPI" },
      session,
      calls: [{
        contractAddress: GAME_CONTRACT,
        entrypoint: "make_move",
        calldata: [moveData],
      }],
    },
    bearerToken,
  });
}

Next Steps

Need help? Join our Telegram Community for support.