> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chipipay.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Spending Policies

> Set per-token spending caps on session keys — enforced on-chain by the CHIPI wallet contract

## 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`.

<Info>
  Spending policies require **CHIPI v33** wallets. v29 wallets do not have these entrypoints. See [Wallet Upgrades](/sdk/guides/wallet-upgrades) to upgrade.
</Info>

## When to Use Spending Policies

| Use Case              | Configuration                                  |
| --------------------- | ---------------------------------------------- |
| **AI agent budgets**  | Max $0.01 per API 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 |

## How It Works

```
1. Owner creates a session key and registers it on-chain
2. Owner sets a spending policy: per-call limit + rolling window limit
3. Session key executes transactions freely within limits
4. Contract enforces automatically — reverts if limits exceeded
5. Window resets automatically after the configured duration
```

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:

| Parameter       | Type          | Description                                         |
| --------------- | ------------- | --------------------------------------------------- |
| `maxPerCall`    | bigint (u256) | Maximum token amount per single transaction         |
| `maxPerWindow`  | bigint (u256) | Maximum cumulative amount within the rolling window |
| `windowSeconds` | number (u64)  | Rolling window duration in seconds                  |

The contract also tracks:

| Field           | Description                                    |
| --------------- | ---------------------------------------------- |
| `spentInWindow` | Amount spent in the current active window      |
| `windowStart`   | Unix timestamp when the current window started |

<Note>If no policy is set (maxPerWindow is 0), the session has no spending limits. All ERC-20 operations are allowed without caps.</Note>

## TypeScript Backend

```typescript theme={null}
import { 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 policy and spend
const policy = await sdk.sessions.getSpendingPolicy({
  walletAddress: userWallet.publicKey,
  sessionPublicKey: session.publicKey,
  token: USDC,
});
console.log(`Spent ${policy.spentInWindow} of ${policy.maxPerWindow} this window`);

// Remove policy (session has no limits for this token)
const removeTxHash = await sdk.sessions.removeSpendingPolicy({
  encryptKey: userEncryptKey,
  wallet: userWallet,
  sessionPublicKey: session.publicKey,
  token: USDC,
}, bearerToken);
```

## React / Next.js

The `useChipiSession` hook includes spending policy actions:

```tsx theme={null}
import { useChipiSession } from "@chipi-stack/nextjs";

const USDC = "0x033068f6539f8e6e6b131e6b2b814e6c34a5224bc66947c47dab9dfee93b35fb";

function SessionWithLimits() {
  const {
    session,
    hasActiveSession,
    createSession,
    registerSession,
    setSpendingPolicy,
    getSpendingPolicy,
    removeSpendingPolicy,
    isSettingSpendingPolicy,
    isRemovingSpendingPolicy,
  } = useChipiSession({
    wallet,
    encryptKey: pin,
    getBearerToken: getToken,
  });

  const setupWithLimits = async () => {
    // 1. Create and register session
    await createSession();
    await registerSession({ allowedEntrypoints: ["transfer"] });

    // 2. Set spending limits
    await setSpendingPolicy({
      token: USDC,
      maxPerCall: 1_000_000n,
      maxPerWindow: 50_000_000n,
      windowSeconds: 86400,
    });
  };

  const checkBudget = async () => {
    const policy = await getSpendingPolicy(USDC);
    const remaining = policy.maxPerWindow - policy.spentInWindow;
    console.log(`${remaining} USDC units remaining in this window`);
  };

  return (
    <div>
      {!hasActiveSession ? (
        <button onClick={setupWithLimits} disabled={isSettingSpendingPolicy}>
          Setup Session with $50/day Limit
        </button>
      ) : (
        <button onClick={checkBudget}>Check Remaining Budget</button>
      )}
    </div>
  );
}
```

<Note>
  The React hook automatically injects the current session's `sessionPublicKey`. You only need to pass `token`, `maxPerCall`, `maxPerWindow`, and `windowSeconds`.
</Note>

## Python

```python theme={null}
from chipi_sdk import (
    ChipiSDK, ChipiSDKConfig,
    SetSpendingPolicyParams, SpendingPolicyConfig,
    GetSpendingPolicyParams, RemoveSpendingPolicyParams,
)

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,       # 1 USDC
            max_per_window=50_000_000,    # 50 USDC
            window_seconds=86400,         # 24 hours
        ),
    ),
    bearer_token=bearer_token,
)

# Query policy
policy = sdk.sessions.get_spending_policy(
    GetSpendingPolicyParams(
        wallet_address=user_wallet.public_key,
        session_public_key=session.public_key,
        token=USDC,
    )
)
print(f"Spent {policy.spent_in_window} of {policy.max_per_window}")

# Remove policy
sdk.sessions.remove_spending_policy(
    RemoveSpendingPolicyParams(
        encrypt_key=user_encrypt_key,
        wallet=user_wallet,
        session_public_key=session.public_key,
        token=USDC,
    ),
    bearer_token=bearer_token,
)
```

Async variants are available: `aset_spending_policy`, `aget_spending_policy`, `aremove_spending_policy`.

## Validation Rules

The SDK validates before submitting to the contract:

| Rule                                                                 | Error                             |
| -------------------------------------------------------------------- | --------------------------------- |
| Token address must not be empty                                      | `INVALID_SPENDING_POLICY`         |
| `windowSeconds` must be a positive integer within u64 range          | `INVALID_SPENDING_POLICY`         |
| `maxPerCall` must be non-negative and fit in u256                    | `INVALID_SPENDING_POLICY`         |
| `maxPerWindow` must be non-negative and fit in u256                  | `INVALID_SPENDING_POLICY`         |
| `maxPerCall` cannot exceed `maxPerWindow` (when maxPerWindow is set) | `INVALID_SPENDING_POLICY`         |
| Wallet must be CHIPI type                                            | `INVALID_WALLET_TYPE_FOR_SESSION` |

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