Nchedo
Docs

How the vault, the bait, and the canary fit together

This page is the reference version of the pitch on the landing page. Full source, the Foundry test suite, and the deploy scripts are in the repo.

The mechanism

A factory transaction deploys two contracts and wires them together: a vault that holds the real reserve, and a bait that holds a small instant reward. A separate canary key — generated in your browser, never stored anywhere — is the only key that can claim the bait.

  Owner key (hot, in .env)        Canary key (bait, same .env)
           │                                  │
           │ requestWithdrawal()              │ claim() — pays instantly
           ▼                                  │
     NchedoVault ◄──────── lock() ──────  NchedoBait
     pays after a public delay,      the lock fires inside the
     always, for everyone            thief's own transaction

NchedoBait.claim() calls NchedoVault.lock() before it pays out. If either half fails, the whole transaction reverts — a successful claim cannot pay without also locking. Any withdrawal in flight is deleted in the same transaction.

Three keys, one allowed to be careless

owner key

Hot and scriptable. Requests withdrawals, which mature after the delay you pick. It cannot skip the delay, and it cannot act at all once the vault is locked.

.env
canary key

Spends exactly once, ever. Whoever signs with it gets the bait instantly and locks the vault in the same transaction — the entire detector is that one signature.

.env · same file, on purpose
recovery key

Cold, kept off-repo. The only key that can sweep a locked vault (recoverTo), freeze one pre-emptively (lockByRecovery), or reverse a false alarm (unlock).

never in the repo

Threat model

ScenarioResult
Owner key requests an immediate withdrawalDelayed; announced on-chain, matures after the delay
Canary key claims the baitVault locks automatically, in the same transaction
A withdrawal is pending when the bait is claimedDeleted in that same transaction
Owner key acts on a locked vaultReverts — no request, no execute, no unlock
Attacker reads the code, ignores the bait, waits out the delayNot automatically stopped — a public window to respond
Sweeper takes the canary's gas dust and never claimsVisible as a balance alert; not containment
Recovery on a locked (or live) vaultrecoverTo sweeps everything to a safe address
Recovery key compromisedOutside the protection boundary
Funds in an EOA, not the vaultNot protected

Every row is a passing test in contracts/test/Nchedo.t.sol.

Try it yourself

You do not need your own deployment, and you do not need to touch a local .env to test the app — the canary key is generated in your browser at /create and shown once on screen. Paste it straight into the attacker console on the panel to spring your own trap in the same session.

  1. Connect a wallet on Monad Testnet and get testnet MON from the faucet.
  2. Build a vault — pick a recovery address you also control, then send the one-shot transaction.
  3. Copy the canary key it hands you and paste it into “Take it” on the panel.
  4. Open the recovery console with the recovery wallet and sweep the reserve.

To run the contracts and tests locally instead, see README.md in the repo — forge test -vv covers both the claim path and the attacks Nchedo does not stop.

Deployed

NetworkMonad Testnet · chain 10143

Full write-up, brand notes, and the video script are in the repo — github.com/TheWeirdDee/nchedo ↗