Methodology

Karatum Sentinel (method sentinel-1.0)

Testnet pilotSource: docs/methodology/sentinel.md

Sentinel is a real-time, read-only watch over the wallets AI agents run on. It turns Karatum from periodic audits into live protection: it follows new blocks on Base (and, when enabled, Ethereum) mainnet, picks out the transactions that touch a watched wallet, runs deterministic detectors, and raises alerts with evidence transactions and a measured block-to-alert delay.

It sends no transaction, holds no key, and uses only free public RPC / websocket endpoints (no Alchemy, no API key). It cannot revoke a certificate, a Guard registration or an attestation: there is no recall call in the service.

Public surface: Explorer /sentinel, GET /v1/sentinel/alerts (feed), /alerts/{id}, /stats, /wallets/{address}, /detectors, and POST /v1/sentinel/webhooks (API key). Code: services/sentinel, storage packages/store/src/sentinel.ts (migration 038_sentinel.sql).

1. Watch set

Per chain, three tiers (a wallet is listed once, at its highest tier):

tierwhosize
certifiedwallets bound to a Karatum build (builds.wallet) or the Guard (guard_bindings)all
claimedwallets of agents registered through the API (agent_identities, kind wallet) and Census agents at lifecycle VERIFIEDall
censusthe most active externally owned wallets of Census agents: ECONOMICALLY_ACTIVE agents first, then by sent transactions in the 90-day Backtest window (Base) or lifetime nonce (Ethereum, where no free archive node is used to read the window start)SENTINEL_TOPN_8453 (default 3000), SENTINEL_TOPN_1 (default 1000)

Certified and claimed wallets are watched on every enabled chain: an EOA is the same key everywhere. The set is recomputed every 30 minutes (sentinel_watch). Watching more wallets does not cost more RPC: matching is a set lookup in memory; only matched transactions need receipts.

2. Ingestion (free public infrastructure)

  1. Heads: eth_subscribe newHeads on several free public websockets at once (Base: publicnode, drpc, tenderly; Ethereum: publicnode, drpc). The first socket to announce a block number wins, the others are redundancy. An HTTP poll takes over when every socket has been silent for three block times. Sockets reconnect with backoff.
  2. Block: eth_getBlockByNumber(n, true) (about 150-300 KB on Base) is hedged across the free RPCs: if the best endpoint has not answered after 600 ms (Base), the same call goes to a second one and the first answer wins. A node that has not seen the block yet (null) is skipped. Per-endpoint call rates are respected; 429 / 5xx / garbage cools an endpoint down.
  3. Owner logs: one eth_getLogs by block hash per 900 watched wallets, topics [Transfer | Approval | ApprovalForAll | Permit2 Approval] x [owner in watch set]. This finds token outflows and approvals of a watched wallet even when the wallet did not sign the transaction (an approval being drained by a third party, a relayed permit).
  4. Receipts only for matches: transactions sent by a watched wallet, transactions whose owner-logs touch one, and EIP-7702 authorisations whose recovered signer (recoverAuthorizationAddress) is watched. eth_getTransactionReceipt in one batch, or one eth_getBlockReceipts when more than 12 match.
  5. Up to 3 blocks are fetched ahead while the previous one is evaluated; evaluation is in block order. After a restart Sentinel resumes from the stored block; a gap larger than 150 blocks is skipped and counted (gaps in /v1/sentinel/stats).

Blocks are read at the chain head. A reorganisation after that is not re-checked (Base has a single sequencer; on Ethereum this is a known limit).

3. Detectors

All are pure functions of (transaction, receipt, wallet baseline, address facts, scam lists); every number they used is stored in the alert's details. Thresholds below are the defaults of DEFAULT_CONFIG (services/sentinel/src/detectors.ts).

detectorfires whenseverity
drain.native_sweepnative transfer to an address on an open scam list (>= 0.0005 ETH); or >= 80% of the pre-block balance and >= 0.033 ETH to a never-used address (no code, nonce 0, never paid by this wallet)listed: high, critical when >= 50% of the balance; fresh: high, critical with 2+ independent behaviour signals
drain.token_sweepany ERC-20 transfer to a listed address; or >= 90% of a token balance to a never-used address, for tokens Sentinel can value (USDC/USDT/DAI >= $200, WETH >= 0.05)high / critical as above
drain.third_party_pulltokens leave the wallet in a transaction it did not sign, to a listed or never-used address (>= 50% of the balance)high; critical for >= 90% of a valued token or a listed recipient with >= 50%
drain.multi_assetthree or more different assets to the same never-used or listed address within 5 blockshigh; critical for listed or 5+ assets
approval.flaggedany allowance to a listed addresshigh; critical when unlimited
approval.unverifiedunlimited allowance, NFT setApprovalForAll, or Permit2 grant to an account (high) or to a contract not verified on Sourcify (medium; a relayed permit is one band worse); not for spenders the wallet approved beforemedium / high
scam.interactiona call to a listed addresshigh when value or tokens moved, else medium
behaviour.shift(new destination AND new call type) AND at least one of: gas bid >= 8x the block median and >= 4x the wallet's p90; active in an hour with < 1% of its history (>= 40 txs); native value > 10x its usual large transfer and > 3x its maximum; waking after > max(14 d, 20x median gap). Needs a baseline of >= 15 txsmedium; high with 3+ extra signals; one alert per wallet per 30 min
swap.sandwichthe wallet's single-hop Uniswap V3 swap is bracketed in the same pool by a same-direction swap before and the opposite swap after, from one account that is not the wallet (the Backtest's findSandwich); the shortfall is re-simulated against the best QuoterV2 route at block start (Base only)low (pattern); medium at <= -100 bps; high at <= -500 bps
tx.fail_storm>= 8 reverts and >= 60% of the wallet's txs in 10 minutes, or 5 identical reverts in a rowlow; medium when > 0.02 ETH of gas burned
key.delegationthe wallet signed an EIP-7702 authorisation to a contract it never delegated to (clearing a delegation is ignored)critical for a listed delegate; high for an unverified contract or an account; medium otherwise
key.sprayvalue or tokens to 3+ never-used EOAs within 5 blocks, only for wallets with a baseline of >= 15 txs, and not when the wallet habitually pays new addresses (> 30% distinct recipients in its baseline)high
key.velocity>= 8 txs in 30 s, >= 50% to destinations outside the baseline, when the baseline median gap is >= 120 smedium; high when value moved

A transaction "to a never-used address" can be a legitimate move to a new cold wallet; the alert text and the spot-check section say so. Listings are evidence that someone reported an address, not a verdict.

Baselines

Per-wallet profile (sentinel_baselines): top destinations, call types, hour-of-day histogram, tip ratio to the block median, gaps, largest values, known EIP-7702 delegates and approved spenders. It starts from the Backtest transaction cache when the wallet was graded, is updated by every transaction Sentinel sees, and is saved every minute. A wallet with fewer than 15 transactions is in learning mode: the behaviour and velocity detectors stay silent for it. Most Census wallets have no Backtest cache, so for them the baseline is built live.

Address facts

eth_getCode / eth_getTransactionCount (cached 6 h; a zero nonce 20 s), balances at block - 1 (only for candidates), Sourcify verification (only for approval spenders and delegates). Contracts verified only on Basescan read as unverified, which is why that case is capped at medium.

4. Response

  1. Webhooks: POST /v1/sentinel/webhooks (API key; https, public DNS name, port 443; max 5 per key) with a severity floor and optional detector / wallet filters. Each delivery is POST JSON with X-Karatum-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<rawBody>")>, X-Karatum-Event: sentinel.alert, X-Karatum-Delivery (stable across retries), retried at 1 min, 5 min, 30 min, 2 h, 6 h, 24 h. Same verifier as the Incident Registry (verifyWebhook).
  2. Public feed: the alert API above and /sentinel; follow it with ?order=cursor&since=<next>.
  3. Incident Registry hook: severity >= high opens an incident (detector sentinel.<id>, dedupe key sentinel:<chain>:<wallet>:<tx>), status reported (private queue for a maintainer). Recall stays human: karatum-incidents confirm <id> --recall.
  4. Fund-loss fast path (flag only): a drain-type alert (native/token sweep, third-party pull, multi-asset, spray, listed payment) at high/critical on a certified or claimed wallet opens the incident as triaged and public at once (the evidence is public chain data, so it is not embargoed) and states in its summary that no certificate, Guard registration or attestation has been revoked and that any recall needs a human to confirm it. Census-tier wallets never take the fast path. Approvals are never fast-path.
  5. Telegram (optional): when KARATUM_TELEGRAM_BOT_TOKEN and KARATUM_SENTINEL_TELEGRAM_CHAT_ID are set, high/critical alerts are posted. Neither is set on the current server, so this is inactive.

5. Metrics and how latency is measured

sentinel_blocks has one row per processed block (block_ts, head_at, fetched_at, done_at); /v1/sentinel/stats reports blocks per second, watch-set size, alert counts by severity and detector, and median / p95 latency:

  • latencyMs = alert written minus block timestamp. The timestamp has one-second resolution and is the sequencer's seal time, so this figure is exact to about +-1 s and is the honest "how stale was the chain when we told you".
  • processingMs = alert written minus the moment the head reached Sentinel. This is Sentinel's own delay with no timestamp error.
  • Block-to-evaluated medians use the same two clocks for every block, not only alert-bearing ones.

6. Limits (read before relying on it)

  • Smart-contract wallets send no transactions of their own, so only their token flows are visible through the owner logs; native sweeps and delegations by contract wallets are not.
  • Transfers are watched from watched wallets; incoming transfers (dust, address poisoning) are not.
  • Tokens other than the stablecoins / WETH Sentinel can value count only through the sweep-fraction rule (>= 90% of the balance) at sizes it cannot check, so they are valued as unknown and alert only for listed recipients or multi-asset sweeps.
  • Only Uniswap V3 pools are checked for sandwiches (Aerodrome, Uniswap V2-style and aggregator routes are not).
  • Free public nodes rate-limit and sometimes lag; hedging and cooldowns reduce, not remove, that. Gaps are counted and published.
  • The method version is part of every alert's meaning: a change to a threshold bumps SENTINEL_VERSION.

7. What has been measured (2026-10-16, Base mainnet, first run)

Live from 17:25 UTC, 3,009 Base wallets (9 certified, 3,000 top-active Census) and 1,009 Ethereum wallets:

  • Base: 0.50 blocks/s sustained (the chain's rate), about 138,000 transactions scanned per hour, about 170 per hour touching a watched wallet. Median block timestamp to block evaluated 385 ms (p95 1.4 s); Sentinel's own part (head announced to evaluated) median 293 ms. Heads arrived over publicnode's websocket first on every block. No gaps.
  • Ethereum: 0.08 blocks/s; median 2.6 s from block timestamp to evaluated (the free websocket announces heads about 1.7 s after the timestamp; our part is 0.67 s).
  • Alerts raised by the live agent watch set in the first minutes: none. Matched transactions of the top-active Census wallets were routine. A quiet first run says the detectors are not noisy on this population; it does not show they would catch a drain, and the run was too short to say anything about the alert rate per day.

False-positive study (not the agent set): the same detectors were replayed over about 300 sampled Base blocks treating every transaction sender as watched. Without baselines, the behaviour and velocity detectors are silent in this mode. From the first 45 hits, 20 were examined by hand (chain state of the spender, the repeat pattern of the sender, what the contract is):

hits examineddetectorfinding
6key.sprayall six were payout / distribution jobs repeating the same shape many times (one wallet 19 times, others 8 destinations at once). This was a defect: the detector must not fire for a wallet without a baseline. Fixed in the same release (it now needs >= 15 baseline txs and a low share of new recipients).
8approval.unverified (contract, medium)one spender was approved by 5 unrelated wallets (a widely used contract of about 20 KB), another by 3, an Aerodrome Slipstream position-manager approval, a small cbETH proxy. Likely legitimate protocols verified on Basescan but not on Sourcify. Medium is the right ceiling; this is the main source of false positives.
5approval.unverified (account, high)unlimited allowances over low-value tokens to spenders that are accounts with no code and no transactions, by two wallets repeating it with many different fresh spenders (memecoin tokens). Inconclusive: either a bot using throw-away addresses or token-honeypot victims. Not resolved.
1key.delegation (medium)delegation to 0x63c0…e32b, a verified-looking contract of about 11 KB used for smart-account upgrades. Expected behaviour.

So of 20 examined hits: 0 confirmed malicious, 6 false positives (fixed), 9 likely benign, 5 inconclusive. Detectors that rest on listed addresses, sweeps to never-used addresses and third-party pulls produced no hit in this population sample, and were not validated against a real drain either: their evidence is the unit tests and the plumbing check below, not field results.

Plumbing check on live data: six real wallets with unlimited approvals were temporarily added to the watch set and the blocks replayed; the approval detector raised its alerts from receipts and owner-logs as designed. The rows were removed again.