Spending Policies: Budget Control for Session Keys

Set per-token spending caps on session keys with on-chain enforcement. Perfect for AI agent budgets, game economies, DeFi automation, and employee wallets. The CHIPI wallet contract enforces limits automatically.

Acknowledgments

Thanks to Omar Espejel for proposing the spending policy feature and contributing to the SNIP development that makes this possible.

What are Spending Policies?

Spending policies let wallet owners set stablecoin caps on what a session key can spend. The CHIPI wallet contract enforces these limits automatically during transaction execution.

Three parameters:

  • - maxPerCall — maximum USDC (or any ERC-20) per single transaction
  • - maxPerWindow — maximum cumulative amount in a rolling time window
  • - windowSeconds — duration of the rolling window (e.g., 86400 for 24 hours)

The contract tracks spending in real time. If a session key tries to exceed the per-call or per-window limit, the transaction reverts on-chain. No backend enforcement needed.

Use cases:

| Scenario | Configuration | |----------|---------------| | AI agent API budget | Max $0.01 per call, $50 per day | | Game daily limits | Max 5 USDC per trade, 100 USDC per 24h | | Employee wallets | Max 200 USDC per transaction, 1000 per week | | DeFi automation | Max position size per swap, rolling window cap |

Requirements

  • - CHIPI v33 wallet — only v33 has spending policy entrypoints. Upgrade from v29 if needed.
  • - Session key registered — spending policies apply to session keys, not owner signatures
  • - @chipi-stack/nextjs or @chipi-stack/backend (latest version)
bashnpm install @chipi-stack/nextjs@latest

Backend SDK (TypeScript)

Use the server SDK to set, query, and remove spending policies:

typescriptimport { ChipiServerSDK } from "@chipi-stack/backend";

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

// USDC on Starknet mainnet (6 decimals)
const USDC = "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb";

// Set policy: max 1 USDC per call, 50 USDC per day
const txHash = await sdk.sessions.setSpendingPolicy({
  encryptKey: userEncryptKey,
  wallet: userWallet,
  spendingPolicyConfig: {
    sessionPublicKey: session.publicKey,
    token: USDC,
    maxPerCall: 1_000_000n,      // 1 USDC (6 decimals)
    maxPerWindow: 50_000_000n,   // 50 USDC
    windowSeconds: 86400,        // 24 hours
  },
}, bearerToken);

Query Current Spend

Check how much a session has spent in the current window:

typescriptconst policy = await sdk.sessions.getSpendingPolicy({
  walletAddress: userWallet.publicKey,
  sessionPublicKey: session.publicKey,
  token: USDC,
});

console.log("Spent:", policy.spentInWindow, "of", policy.maxPerWindow);
console.log("Window resets at:", new Date(policy.windowStart * 1000 + policy.windowSeconds * 1000));

Returns zeroed data if no policy is set (meaning no enforcement).

React / Next.js Hook

The useChipiSession hook includes spending policy actions:

tsximport { useChipiSession } from "@chipi-stack/nextjs";

const {
  setSpendingPolicy,
  getSpendingPolicy,
  removeSpendingPolicy,
  isSettingSpendingPolicy,
} = useChipiSession({
  wallet,
  encryptKey: pin,
  getBearerToken: getToken,
});

// Set limits after registering session
await setSpendingPolicy({
  token: USDC,
  maxPerCall: 1_000_000n,
  maxPerWindow: 50_000_000n,
  windowSeconds: 86400,
});

// Check remaining budget
const policy = await getSpendingPolicy(USDC);
const remaining = policy.maxPerWindow - policy.spentInWindow;

The hook automatically injects the current session's public key.

Python

pythonfrom chipi_sdk import (
    ChipiSDK, ChipiSDKConfig,
    SetSpendingPolicyParams, SpendingPolicyConfig,
    GetSpendingPolicyParams,
)

sdk = ChipiSDK(config=ChipiSDKConfig(
    api_public_key="pk_prod_...",
    api_secret_key="sk_prod_...",
))

USDC = "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb"

# Set policy
tx_hash = sdk.sessions.set_spending_policy(
    SetSpendingPolicyParams(
        encrypt_key=user_encrypt_key,
        wallet=user_wallet,
        spending_policy_config=SpendingPolicyConfig(
            session_public_key=session.public_key,
            token=USDC,
            max_per_call=1_000_000,
            max_per_window=50_000_000,
            window_seconds=86400,
        ),
    ),
    bearer_token=bearer_token,
)

Remove a Policy

Remove the spending cap so the session has no limits for that token:

typescriptawait sdk.sessions.removeSpendingPolicy({
  encryptKey: userEncryptKey,
  wallet: userWallet,
  sessionPublicKey: session.publicKey,
  token: USDC,
}, bearerToken);

On-Chain Enforcement

The contract enforces limits during transaction execution. No middleware or backend checks needed.

Tracked operations: transfer, approve, increase_allowance (ERC-20)

Per-call check: If amount > maxPerCall, transaction reverts

Window check: If spentInWindow + amount > maxPerWindow, transaction reverts

Auto-reset: Window resets automatically when the configured duration passes

Owner-signed transactions bypass spending policy enforcement entirely — only session keys are limited.

Full Documentation

Ready to build?

Connect the MCP server and start building in minutes.

Get Started