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@latestBackend 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
- - Spending Policies Guide — full API reference
- - Spending Policy Methods — backend SDK reference
- - x402 + Session Keys — combine with automated payments