All tutorials
Bridges & cross-chain

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

A bridge operator, or an insurer underwriting transfers. Every hold-or-release is signed, so there's always a record of which signal drove the call.

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.

THESEUS.md
---
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.

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.

Documentation