AgentCap

← Blog

The risk is silent wallet spend, not the payment protocol

GetAgentCap team · published 9/24/2026

Most agent wallet incidents aren't protocol failures. They're money leaving a wallet while nobody watches. This post walks through a minimal spend-cap and alert setup that an engineer can check line by line: block ranges, cursors, reconciliation, and alerts when the watcher itself fails.

The protocol usually isn't the problem

If you run an autonomous agent or bot with a funded wallet, you've probably spent time thinking about payment rails: x402, direct USDC transfers, some vendor's metered API, a swap router. Rails matter. But when an agent wallet goes wrong, the protocol is usually doing exactly what it was told to do. The actual problem is that money left the wallet and nobody noticed until much later.

This post is about that gap. It covers what silent spend looks like, and what a minimal, boring, reliable spend-cap and alert setup needs. We build getagentcap.com, which does this for USDC on Base and Solana, but everything below applies whether you use us, build it yourself, or use something else.

What silent spend looks like

A few patterns show up again and again. Retry loops: an agent calls a paid endpoint, gets an ambiguous response, and retries; each retry settles, the payment layer is fine, and the agent just paid 400 times for one answer. Price drift: a tool you pay per call quietly changes its price, or the agent starts picking a pricier tier because its prompt nudged it there; nothing breaks, but daily spend doubles. Forgotten wallets: a test bot from three weeks ago still holds a balance and still runs on a cron somewhere. Leaked or over-permissioned keys: someone else is spending from your wallet, and on chain this looks like ordinary transfers.

None of these throw an error. Your logs look normal. The protocol worked. The only signal is the wallet balance going down faster than it should, and that signal only helps if something is watching.

Why protocol-level controls aren't enough

Some rails give you per-request limits or signed payment requirements. Those are useful, but they are scoped to one rail. Real agents rarely use just one. A single bot might pay for inference over x402, top up a service with a direct USDC transfer, and pay gas-adjacent fees somewhere else. If your spend control lives inside one protocol, anything that leaves the wallet another way goes unseen.

The wallet is the one place every rail passes through. So that's where to watch. Watch the address, not the integration.

A ledger built from chain data, not app logs

Your agent's own logs record what it meant to do. The chain records what actually happened. Build the ledger from the chain.

On Base (EVM), that means reading USDC Transfer events where the from topic is your wallet, using eth_getLogs against the USDC contract with a block range. On Solana, it means reading signatures for the wallet or its USDC token account with getSignaturesForAddress, then fetching and parsing each transaction for SPL token balance changes.

Store each outbound transfer with at least: tx hash or signature, block or slot, timestamp, amount, counterparty. That table is your source of truth.

A per-wallet cursor

Don't rescan from genesis on every run, and don't use one global "last block" for all wallets. Keep a cursor per wallet: the last block (Base) or last signature (Solana) you fully processed.

Each run: read the cursor, fetch from cursor + 1 up to the current head minus a small confirmation margin, then write the new transfers and advance the cursor in the same transaction, so a crash can't leave the cursor ahead of the data.

Two details that bite people. Chunk your ranges: RPC providers cap eth_getLogs block spans and result sizes, so if a wallet was offline for a day, a single call from cursor to head will fail or get truncated — walk forward in fixed chunks, a few thousand blocks tuned to your provider, until you catch up. Paginate on Solana: getSignaturesForAddress returns pages newest-first, so to catch up you page backward with "before" until you reach your stored cursor, then process oldest-first.

We shipped a bug in exactly this area early on: a shared cursor meant one wallet's catch-up could skip another wallet's range. Per-wallet cursors with chunked catch-up fixed it. It's an easy mistake to make, and it fails silently, which is the theme of this whole post.

Reconciliation

Event ingestion can miss things: an RPC node returns a partial result, a reorg shifts a block, a provider has a bad hour. Periodically compare the balance you expect from your ledger with the actual on-chain balance (balanceOf for USDC on Base, getTokenAccountBalance on Solana). If they disagree beyond dust, re-scan the recent window and flag it.

Reconciliation also calls RPC, so rate-limit it and back off on 429s. A reconciler that hammers the provider into throttling makes ingestion worse.

Caps that mean something

A cap is just a threshold on a rolling window: this wallet should spend no more than 50 USDC per 24 hours, or no more than 5 USDC per hour. After each ingestion run, sum outbound transfers in the window and compare.

Be honest about what a monitoring cap is. If you don't hold the keys, and a non-custodial monitor shouldn't, a cap can't stop a transaction from being signed. It tells you fast. That is still the difference between losing 60 USDC and losing 6,000. If you need hard enforcement, pair monitoring with limits in the wallet itself (smart account spending policies, session keys with allowances) and use the ledger to check that those limits actually hold.

Alerts, including when the watcher breaks

Alert on: a cap crossed (warn at maybe 80%, alert at 100%), an unusual counterparty or a single transfer above a set size, and ingestion or reconciliation failing.

That last one is the one most people skip. If your ingestion job has been failing for six hours because an RPC key expired, your dashboard shows zero spend, which looks great. A spend monitor that fails quietly is worse than none, because it gives you false confidence. Alert when a run errors, when a cursor hasn't moved in longer than expected, and when reconciliation keeps disagreeing.

Send alerts somewhere a human actually looks: email, Slack, a webhook into your on-call. A red badge on a dashboard nobody opens doesn't count.

What this costs to run

Not much. A cron job every few minutes, a Postgres table or two, an RPC provider on a free or cheap tier, and an email or webhook sender. The work is in getting the edge cases right (chunking, pagination, cursor atomicity, reorg margins, failure alerts), not in infrastructure.

Where getagentcap.com fits

getagentcap.com does the above for USDC on Base and Solana: paste a wallet address, get a live spend ledger, set caps, get alerts. x402 payments are supported, but so is any other outbound USDC transfer, because we watch the wallet, not the protocol. There is no SDK to install, no API keys to hand over, and no custody. We only read public chain data.

To be clear about where we are: this is an early product. We don't have customers to quote or usage numbers to show. We're publishing how it works so you can judge whether the approach is sound, and so you can build it yourself if you'd rather. If you run agent or bot wallets and silent spend is a problem you've actually hit, we'd like to hear how it happened. That tells us more than any launch post could.

Whatever you use, the takeaway is the same: pick the wallets that can spend money on their own, watch them from the chain, put a number on "too much," and make sure you hear about it when the watcher itself goes quiet.