Skip to main content

What Are Spending Policies?

Spending policies let wallet owners set dollar-denominated caps on what a session key can spend. The CHIPI wallet contract enforces these limits automatically during transaction execution — no backend or middleware needed. Limits are enforced on ERC-20 operations: transfer, approve, increase_allowance.
Spending policies require CHIPI v33 wallets. v29 wallets do not have these entrypoints. See Wallet Upgrades to upgrade.

When to Use Spending Policies

How It Works

The policy is per (session key, token) pair. You can set different limits for different tokens on the same session.

Configuration

A spending policy has three parameters: The contract also tracks:
If no policy is set (maxPerWindow is 0), the session has no spending limits. All ERC-20 operations are allowed without caps.

TypeScript Backend

React / Next.js

The useChipiSession hook includes spending policy actions:
The React hook automatically injects the current session’s sessionPublicKey. You only need to pass token, maxPerCall, maxPerWindow, and windowSeconds.

Python

Async variants are available: aset_spending_policy, aget_spending_policy, aremove_spending_policy.

Validation Rules

The SDK validates before submitting to the contract:

On-Chain Enforcement

The contract enforces spending limits during __execute__() for session-signed transactions:
  1. Tracked selectors: transfer, approve, increase_allowance, increaseAllowance
  2. Per-call check: If amount > maxPerCall, transaction reverts with “Spending: exceeds per-call”
  3. Window check: If spentInWindow + amount > maxPerWindow, transaction reverts with “Spending: exceeds window limit”
  4. Auto-reset: When now >= windowStart + windowSeconds, the window resets to zero
Owner-signed transactions (2-element signature) bypass spending policy enforcement entirely.

Security notes

For anyone giving a session key to an agent:
  • The whitelist is not scoped to a contract. allowedEntrypoints is matched by selector, and a selector is allowed on every contract. "transfer" lets the session call transfer on any token the wallet holds. Set a spending policy for every token the wallet can hold, not only the one the agent is meant to use.
  • Only ERC-20 transfers and approvals are metered. Policies cap transfer, approve and allowance increases (see On-Chain Enforcement). Any other whitelisted entrypoint that moves value is uncapped. In particular, don’t whitelist a vault’s withdraw / redeem for a session: ERC-4626 versions take a receiver, so the session could send the assets to any address. Route vault exits through a contract whose functions return funds to the caller.
  • maxPerWindow: 0 means no cap. It disables the policy for that token; it does not block spending. Always set a positive window cap.
  • maxPerCall: 0 with a positive window blocks every metered spend of that token.
  • A refused call can still report success. Sent through Chipi’s paymaster, a session call the wallet refuses (over a cap, not whitelisted, revoked session) comes back success: true with nothing moved. Check the receipt’s events: a Transfer (or Approval) from your wallet is the proof, not the status.
  • Register the caps with the session. registerSession({ ..., spendingPolicies }) in useChipiSession, or buildSessionSetupCalls, puts the session and its policies in one owner-signed transaction, so the session never exists on-chain without its limits. See Agent with a session key and a passkey.