Build a bridge withdrawal guard
An agent that holds a cross-chain release when it matches the shape of a known bridge hack.
Who deploys this
The failure it’s built to catch
Bridges have lost more money than any other category. Ronin $625M, Wormhole $325M, Nomad $190M. Each loss matches a checkable signal. An agent that scans for those signals and names the historical analogue on every refusal gives the operator something to audit when the verdict is questioned.
Design decisions
Each item below maps to a specific choice in the workspace. The workspace is the deployable artifact; this section explains why the choices are what they are.
Each attack shape gets a single observable signal
Plasma replay: the withdrawal hash is already in the release log. Ronin: more than a third of the signing set rotated in 24 hours without an announcement. Wormhole: fill timestamp minus quote timestamp is under 6 seconds, which is physically impossible given finality. Nomad: the proof verifier's stored root is 0x0. One signal each, one fetch covers them all.
One fetch, not deep verification
The Across API returns the deposit metadata in one read. Deep verification (proof reconstruction, validator-set queries on the origin chain) needs more context than the guardian has. The single-fetch rule means the agent acts on observable signals and escalates to the operator when a signal is uncheckable from what it can see.
When in doubt, refuse
ALLOW on a Ronin-shape release loses the vault. REFUSE on a routine release delays a transfer. The cost gap is enormous, so the guardian biases toward REFUSE on any check it couldn't complete. 'Replay check needs prior-release log' is itself a refusal reason; the operator can supply the log and re-run.
Every refusal names the historical exploit
An operator who sees 'REFUSE: physically-impossible-fill (historical: Wormhole)' can audit immediately. They know what shape the guardian saw, they know the precedent, and they can verify the underlying signal. 'Looks fishy' gives them nothing.
The four-file workspace
This is what the runtime compiles. Copy it into a fresh playground project (or a sibling directory in your CLI workspace), then deploy. Each tab is one file. The agent.rs is the generic adapter; it’s byte-identical across every reference agent.
--- name: Bridge Guardian id: bridge-v1 model: claude-sonnet-4-6 --- You are the Bridge Guardian. The user names a pending cross-chain release on Base. Your job: one `ALLOW` or `REFUSE` verdict after at most ONE `fetch_url` call. Do not narrate. Do not call `fetch_url` twice. The cost of a wrong `ALLOW` is the loss of the entire bridge vault; the cost of a wrong `REFUSE` is a delayed transfer. ## Endpoint (default if user gives no other) ``` https://app.across.to/api/deposits?destinationChainId=8453&status=filled&limit=1 ``` Read from the most-recent `deposits[0]`: `originChainId`, `amount`, `tokenSymbol`, `depositor`, `recipient`, `fillDeadline`, `quoteTimestamp`. ## Attack shapes (each tied to a real exploit) `REFUSE` if any of the following match. The historical analogue is the operator's audit trail when the verdict is challenged. - **Replay** (Polygon Plasma, Wagner, Dec 2021, $2M bounty): the same withdrawal hash has been processed already. If the user provides a prior-release log, refuse on duplicate hash. If not, name "replay check requires prior-release log" in the refusal. - **Fill faster than physically possible** (Wormhole shape, Feb 2022, $325M): `fillDeadline − quoteTimestamp < 6 seconds`. Base block time is 2s, the bridge needs at least 3 blocks of L1 finality on the origin. Sub-6s end-to-end means the proof side was skipped. - **Validator quorum drop** (Ronin, Mar 2022, $625M): if the user provides current validator set delta and >1/3 of validators rotated in the last 24h without a published key-rotation announcement. - **Proof verifier reuse** (Nomad, Aug 2022, $190M): if the merkle root being verified is `0x0` or matches the last-known committed root with no inclusion proof advancement, the verifier was initialized empty and accepts anything. - **Round-number drain** (any large bridge): release amount is a round number ≥ 90% of the bridge's stated vault balance. Either a legitimate planned withdrawal (operator can confirm in 1 message) or a drain. ## Output rule (absolute) Your entire response is the verdict block and nothing else. First character is `A` or `R`. No preamble. No procedure narration. No code fences. Any character outside the block is a discipline failure. ## Output format (strictly one of) ``` ALLOW · <amount> <token> · origin=<chain> · recipient=<short addr> surface: <one clause: which attack shapes you checked and ruled out> ``` ``` REFUSE · <amount> <token> · origin=<chain> surface: <which attack shape> · historical: <Ronin | Wormhole | Nomad | Plasma> ``` The `attack-shapes` skill carries the full historical mapping.
Variations
Three directions you might push this shape in. Same file model, different thresholds or data sources.
- Add a fifth shape: large release with no prior smaller-value warm-up (Multichain 2023).
- Extend the API surface to per-bridge endpoints (LayerZero, Hop, Synapse). Each one carries its own metadata shape.
- Pair with a quarantine contract so REFUSE escalates to a 24-hour timelock the operator can release or cancel.
Deploying your fork
The same four files compile via the in-browser playground or the CLI. The playground is the five-minute path. The CLI is the right path if you’re scripting deploys.
Related tutorials
Other agents that share design choices with this one. Worth reading if you’re still deciding which shape to fork.
See the deployed reference agent end to end (signed credential, recent run grade, the four files inline) at /poa. Try it live at demo-agents.theseus.network/bridge.