DEV Community

Michael Tuszynski
Michael Tuszynski

Posted on Originally published at mpt.solutions

An AI Agent With a Crypto Wallet Is a Hot Wallet Anyone Can Talk To

An attacker sent Grok a free NFT, posted a message in Morse code, and walked away with roughly 3 billion DRB tokens, about $174,000. Nobody stole a private key and no smart contract had a bug. One AI agent translated some dots and dashes in public. A second AI agent read that translation as an order and paid it.

That incident from May 2026 is the best case against giving agents custody of crypto. An agent wallet is a hot wallet with a chat interface, and anyone who can get text in front of the agent can talk to it.

The attack turned a translation into a transfer

The sequence is short, and every step matters.

Grok had a wallet through Bankr, a bot that executes on-chain actions for users who tag it on X. That wallet was read-only. The attacker airdropped a free Bankr Club NFT to it. Holding the NFT upgraded the wallet's permissions from read-only to transfer rights. So an inbound token, which anyone can send to any address, changed what the wallet was allowed to do.

Next the attacker posted Morse code and asked Grok to translate it and tag Bankrbot. Grok did what it was asked. The decoded text told Bankrbot to send about 3B DRB to the attacker's address. Bankrbot saw a tagged instruction coming from an account with transfer rights, and it executed.

Two design decisions failed here. Permissions could be granted by something the wallet received, not just by something its owner configured. And one agent's output (a translation, which is about as harmless as text gets) went straight into another agent's command parser. Neither is a model problem, and a smarter Grok would not have fixed either one. Grok translated correctly. That was the attack.

The money came back because the attacker chose to return it

The funds were returned, and coverage has treated that as a happy ending. It's the most alarming part of the story. The recovery came from the attacker's goodwill. No system clawed it back.

On-chain transfers are final. You get no chargeback, no issuing bank to call, and no dispute window. Once Bankrbot signed the transaction, the only party who could undo it was the person holding the $174K. Allium's explainer on agent wallets describes agents that hold and spend onchain as an emerging pattern. The pattern inherits that finality whether or not anyone designed for it.

A human who gets phished on a bank wire has a few hours and a fraud department. An agent that gets prompt-injected on a public chain has however long a block takes to confirm.

Monitoring fires after the money is gone

Compliance and transaction-monitoring vendors were quick to cite the incident as a reason to buy detection: watch agent wallets, score anomalous transfers, alert on suspicious patterns. That's the strongest counterargument to my position. It says agent custody is fine once you have good enough eyes on it.

Look at what monitoring would actually have done here. Bankrbot's transfer was one transaction, signed by an authorized wallet and sent to a valid address. A good anomaly model flags it as unusual, maybe within seconds of confirmation. The flag lands on a transfer that has already settled, on rails with no reversal path. You get an excellent incident report and a very short list of remediation options, most of which come down to asking the attacker nicely.

Detection pays off when something downstream can act on it, like a card network holding settlement or a bank freezing an account. On a public chain nothing downstream can act. Every control that matters has to run before the signature.

Capability limits have to be enforced in code, before signing

Here's the control set I'd require before an agent touches anything that moves value:

  • No standing transfer rights. The default is read-only. Spending authority is granted per task and expires.
  • Permissions come from the operator's configuration and nowhere else. No inbound token, NFT, message, or airdrop can change what the wallet may do.
  • Destinations come from an allowlist. An address the operator hasn't approved can't receive funds, however the request is worded.
  • Per-transaction and daily caps, enforced outside the model.
  • Human approval above a threshold, set low enough that it actually triggers.
  • Text an agent emits never gets parsed as a command by another agent. Translations, summaries, and replies are data.

The last rule is the one that would have stopped the Grok/Bankr chain on its own. Here's roughly what the gate looks like as code that sits between any agent and the signer:

type Origin = "operator" | "agent_output" | "inbound_message";

type TransferRequest = {
  to: string;
  amountUsd: number;
  origin: Origin; // set by the transport layer, never by the model
};

// Loaded from deploy-time config. No code path reads wallet holdings
// to decide permissions, so an airdropped NFT changes nothing.
const POLICY = {
  allowlist: new Set(["0xTreasury...", "0xVendorA..."]),
  perTxCapUsd: 250,
  dailyCapUsd: 1_000,
  humanApprovalAboveUsd: 100,
} as const;

export function authorize(
  req: TransferRequest,
  spentTodayUsd: number,
): "allow" | "needs_human" | "deny" {
  if (req.origin !== "operator") return "deny";
  if (!POLICY.allowlist.has(req.to)) return "deny";
  if (req.amountUsd > POLICY.perTxCapUsd) return "deny";
  if (spentTodayUsd + req.amountUsd > POLICY.dailyCapUsd) return "deny";
  if (req.amountUsd > POLICY.humanApprovalAboveUsd) return "needs_human";
  return "allow";
}
Enter fullscreen mode Exit fullscreen mode

Run the attack through it. The instruction to Bankrbot came from Grok's public reply, so origin is agent_output, and the first line returns deny. Even if someone mislabeled the origin, the attacker's address isn't on the allowlist. Even if it were, $174K is roughly 700 times the per-transaction cap. The attack has to get past four independent checks, and none of them depend on the model reading the request correctly.

The origin field carries the most weight, and it's also the easiest to get wrong. It has to be stamped by whatever received the message. If the model fills it in, the attacker can fill it in too. In the agent pipelines I run, no tool that moves money or deletes data accepts arguments parsed from another model's free-text output.

This gate has real costs. Allowlists make agents worse at the open-ended "pay whoever" tasks that make crypto agents appealing in demos. Human approval adds latency. Caps cut into throughput. That's the point. Those costs are the price of putting a limit on what a stranger's message can do.

Card rails can say no and take it back

In June 2026 Mastercard announced Agent Pay for Machines. Going by Mastercard's own description, it authenticates the agent, enforces spending limits, and guarantees settlement on a network that already has dispute rails. That's Mastercard describing its own product, and nobody has shown publicly how those disputes play out when the buyer is a bot. Treat it as a design statement until someone reproduces it in production.

Still, the design statement points the right way. Card networks were built on the assumption that some transactions will be wrong. Authorization can decline a purchase before it goes through, and chargebacks can reverse it afterward. The Grok/Bankr chain had neither.

Smart accounts get you limits but still no reversals

The strongest technical pushback is that crypto can enforce limits too. Smart-contract wallets support session keys with spending caps, destination restrictions, and expiry, all enforced on-chain. That's true. A well-built smart account can run most of the gate above at the contract level, which is better than a policy check sitting in an off-chain service.

But it only covers the "say no" half. A session key capped at $250 limits the damage, and the $250 is still gone. For agents that have to transact on-chain, such as protocol treasuries, on-chain market makers, or anything with no card-network equivalent, smart-account limits are the minimum, and the caps should be set on the assumption that every dollar under them is already lost. For everyone else, which means agents buying API credits, SaaS seats, or cloud capacity, there's no reason to choose a rail without reversals.

The cap is the most an attacker can take

If an agent has to move money, put it on rails that can refuse a payment and reverse one. If it has to move crypto, assume the cap is what you'll lose, because a stranger with a free NFT and a Morse code chart can try to take it.

That Morse code message was worth about $174,000, and the only control that worked was the attacker deciding to send the money back.

Top comments (0)