walletcap

Coinbase CDP x402 spend controls across replicas and restarts

You cap what an agent pays over x402 with applySpendControls from @coinbase/cdp-sdk/x402, and the agent restarts or runs as several replicas. CDP keeps its running total in a SpendStore, and the default store is in memory, in one process. budget.cdpStore() is walletcap’s SQLite ledger behind CDP’s SpendStore interface, so CDP’s total lives in one budget file that every replica and every restart opens.

Add it: one store line in applySpendControls

$ npm install walletcap @coinbase/cdp-sdk @x402/core @x402/evm viem
$ npx walletcap set --wallet 0xYourAgentWallet --per-payment 50 --per-day 150
import { openBudget } from 'walletcap';
import { applySpendControls } from '@coinbase/cdp-sdk/x402';
import { x402Client } from '@x402/core/client';
import { ExactEvmScheme } from '@x402/evm/exact/client';
import { privateKeyToAccount } from 'viem/accounts';

const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; // USDC on Base

const account = privateKeyToAccount(process.env.AGENT_KEY);
const budget = openBudget({ wallet: account.address }); // the budget `walletcap set` created
const client = new x402Client().register('eip155:8453', new ExactEvmScheme(account));
// As CdpX402Client does: CDP's caps below replace x402's own $1-per-payment default.
client.setSpendControls(false);

applySpendControls(client, {
  maxAmountPerPayment: { atomic: 50_000_000n, asset: USDC },
  maxCumulativeSpend: { atomic: 150_000_000n, asset: USDC },
  maxCumulativeSpendWindow: '24h',
  allowedAssets: [USDC],
  store: budget.cdpStore({ asset: USDC }),
});

Crash test: $1,600–$1,700 past a $150 cap with CDP’s controls, $0 on cdpStore

npx walletcap-crash-test runs CDP’s controls set as Coinbase’s README shows, then runs them again set as above, with store: budget.cdpStore(). Every agent is a separate Node process, held to $50 a payment and $150 in any 24 hours, in USDC on Base. Results from 6 November 2026:

Event CDP’s controls CDP’s controls on walletcap cdpStore
restart, then pay again $300 signed · $150 over $150 signed · $0 over
10 replicas $1,500 signed · $1,350 over $150 signed · $0 over
10 replicas, shared store 1 ms away $350 signed · $200 over $150 signed · $0 over
payee asks for EURC, not USDC 1,000 EURC signed, counted as $0 refused before signing

The shared-store row varies from run to run: in eight runs it was $100 over five times and $200 over three times. The cdpStore column was $0 over in every run. In the shared-store row, walletcap’s store also answers each call after 1 ms.