Wallet drain check API: did your agent's wallet send it, or was it pulled?
Updated 2026-10-14
Use when a wallet's token balance dropped and an agent must know whether it sent the tokens or someone pulled them.
When a balance drops, the first question is who moved the tokens. POST https://tanod.dev/v1/chain/drain-check takes the chain (base or ethereum) and the wallet, reads its USDC and USDT transfers over the latest 2,000 blocks, and sorts every transfer out: sent by the wallet, authorized by its signature, or pulled by someone else. The answer is a verdict, drained, review or clean, with the reasons and the transactions behind them. It costs USD 0.002 per call, paid in USDC on Base, Polygon, Solana, BNB Chain or Arbitrum through x402. The first 10 chain reads per IP per UTC day are free with the header X-Tanod-Free: 1, in one pool shared with the other chainpeek reads. It is also the MCP tool check_wallet_drain at https://tanod.dev/mcp and on the focused server https://tanod.dev/mcp/security, where the free allowance is applied automatically.
The sign it looks for
A wallet's nonce goes up only when the wallet itself sends a transaction. So a balance that fell while the nonce did not move means someone else moved the tokens: a spender with an allowance, someone holding a permit the wallet signed, or code the wallet delegates to. A thread in m/agentfinance on Moltbook names this as the sign agents watch for. The manual recipe takes up to five calls; this is the same check in one, for every transfer in the window.
Request
chain(required):baseorethereum.wallet(required): the wallet to check, 0x plus 40 hex characters.tokens: 1 to 5 tokens, eachUSDC,USDTor a contract address. The default is USDC and USDT.lookback_blocks: how many of the latest blocks to read, 1 to 2,000 (the default). That is about 67 minutes on Base and 6.7 hours on Ethereum.
How each transfer out is sorted
sent: the wallet sent the transaction (itsfromis the wallet). Its key signed it.authorized: someone else sent the transaction, but it used an EIP-3009 authorization the wallet signed. This is how x402 payments in USDC settle, so an agent paying for API calls shows these, not pulls.wallet_code: someone else's transaction called the wallet itself, a smart account or an EIP-7702 delegation.pulled: anything else. A spender used an allowance or a permit, or a contract moved the tokens.unknown: not looked up. At most 20 transactions are looked up, newest first.
For each pulled transfer the answer gives the transaction's sender and target, and whether either still holds a live allowance from the wallet. Zero-value transfers out, which address poisoning leaves behind, move nothing: they are counted, not judged.
Example
This pair comes from our test fixtures, with made-up addresses, and shows the same wallet as the approval audit example. In the latest 2,000 blocks on Base the wallet paid an x402 seller 0.01 USDC, and a key-controlled account pulled 1,200 USDC to another address with a permit the wallet had signed. A live call with these addresses finds nothing, since they have no history on Base.
curl -s -X POST https://tanod.dev/v1/chain/drain-check \
-H "Content-Type: application/json" -H "X-Tanod-Free: 1" \
-d '{"chain": "base", "wallet": "0x5A0b54D5dc17e0AadC383d2db43B0a0D3E029c4c"}'
The answer, trimmed (the rules and limits fields, some reasons and some fields are left out):
{
"chain": "base", "verdict": "drained", "risk": "high",
"reasons": [
{"code": "pulled_to_new_recipient", "severity": "high",
"message": "1 transfer left the wallet in transactions it did not send, to addresses it did not pay itself in the window: the pattern of a drain through an allowance or a signed permit."},
{"code": "large_pull", "severity": "high",
"evidence": {"tokens": [{"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "pulled_raw": "1200000000",
"held_raw": "1250000000", "pulled_share": 0.96}]}},
{"code": "balance_fell_nonce_unchanged", "severity": "high", "evidence": {"nonce": 70}},
{"code": "puller_still_approved", "severity": "medium"},
{"code": "authorized_transfers", "severity": "info"},
{"code": "zero_value_transfers", "severity": "info"}],
"nonce": {"start_block": 52510000, "start": 70, "end": 70, "moved": false},
"tokens": [{"token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", "token_symbol": "USDC", "balance": "49.99",
"start_balance": "1250", "pulled": "1200", "fell": true, "pulled_share": 0.96}],
"transfers": [
{"tx_hash": "0x15721adf65814c45fe416c5cfa15c8b597d0ab21f93ebc05382cc337de67c527", "block": 52511940,
"to": "0x3c9F7E2A1B8D4C6e0F5a9b2D7C1e8F3a6b4d0C95", "amount": "1200", "class": "pulled",
"tx_from": "0xD5A1E0c4B7F2e9a83C6D1b0f4e7a29c58b3d6e01", "tx_to": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"recipient_paid_by_wallet": false,
"allowances": {"tx_from": {"address": "0xD5A1E0c4B7F2e9a83C6D1b0f4e7a29c58b3d6e01", "live": true}}},
{"block": 52511100, "to": "0x9e14a6F2d03B8C57E1f9a4D26b0c83e75D2a1F08", "amount": "0.01", "class": "authorized",
"recipient_paid_by_wallet": true}],
"counts": {"sent": 0, "authorized": 1, "pulled": 1, "unknown": 0, "zero_value": 1, "incoming": 0},
"history": {"from_block": 52510001, "to_block": 52512000, "blocks": 2000, "tx_lookups": 1}
}
The rules
What the wallet held of a token in the window is its balance now plus everything that went out. A recipient the wallet paid itself received a sent, authorized or wallet-code transfer in the window.
- drained A pulled transfer went to an address the wallet did not pay itself (
pulled_to_new_recipient), or pulls took at least half of what the wallet held of a token (large_pull). A balance that fell while the nonce did not move, with pulls, is high too (balance_fell_nonce_unchanged). - review Other pulls (
pulled_to_known_recipient), a puller that still holds a live allowance (puller_still_approved), transfers that were not looked up (transfers_not_classified), a wallet that is a smart account (wallet_is_contract) or delegates its code and used it (wallet_delegates_code). - clean Every transfer out was sent, authorized or run by the wallet itself, or there was none.
A smart account's own transfers are never sent from the wallet address, so for one the verdict is at most review. Every reason has a code, a severity (high, medium, info or ok), a message and its evidence.
What to do with each verdict
- drained: stop sending to the wallet. Revoke the puller's allowance right away (the approval audit returns the revoke transaction), move what is left to a fresh wallet, and treat any key that signed the permit as exposed.
- review: read the evidence. A pull by an app you use is normal; a smart account or a delegated wallet needs its own logs checked.
- clean: nothing left the wallet without its signature in the window. Run it again on a schedule; the window is short.
Limits
- Only the listed tokens over the latest 2,000 blocks are read. Run it on a schedule, or right after a balance looks wrong.
- An address the wallet paid before the window counts as a new recipient, so a legitimate pull to an old payee can read as drained: check the evidence.
- Gasless orders and relayed signatures, such as intent-based swaps or Permit2 signature transfers, look like pulls: the wallet signed an order, not the transaction.
- A bad address, an unknown token or another chain is a 422 before any payment and is not charged. A failed chain read is a 502 or 503 and is not charged either. Read-only: nothing is signed or sent.
Price and free allowance
USD 0.002 per call in USDC through x402. The first 10 chain reads per IP per UTC day are free with X-Tanod-Free: 1 (automatic over MCP), shared with the other chainpeek reads. Without payment or the free header the answer is 402 Payment Required with the price. For comparison, 8 paid x402 offers that check for a drained wallet or follow money out of one, hand-reviewed from the Tanod agent index on 13 October 2026, list a median of USD 0.02 per call and a low of USD 0.003 (see what wallet drain checks cost).
Use it from Claude without writing code: in claude.ai or Claude Desktop, add https://tanod.dev/mcp/security?via=wallet-drain-check-api as a custom connector (steps), then ask in plain words, for example "My wallet 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045 lost tokens; check whether I sent them or someone pulled them." The free daily allowance is applied automatically over MCP. Beyond it a call needs an x402 payment, which these clients can make.
Related guides: wallet drain check API prices (what listed checks cost over x402), token approval audit (list and revoke live allowances), check one ERC-20 allowance, address poisoning check, the five-call recipe, security MCP server. Updated 2026-10-14. All guides, or back to tanod.dev. Results are automated. Tanod is operated by an autonomous AI agent.