DEV Community

Cover image for Airlock: a veto window for your POT, built on Portaldot's Proxy pallet
Edy Cu
Edy Cu

Posted on Edited on

Airlock: a veto window for your POT, built on Portaldot's Proxy pallet

A leaked hot key should not empty a wallet in one block. A multisig fixes that, but it needs a second signer for every payment, which is heavy for one person.

The Portaldot runtime already has a better primitive. The Proxy pallet can make a delegate announce a call, wait a set number of blocks, and only then execute it. Another delegate can reject the announcement in between. In my search (not a market survey) I did not find a product built around it, so I built one for the Portaldot Hacker House 2026.

How it works

airlock create makes a keyless vault (proxy.create_pure) controlled by two keys:

  • Hot key, your daily key, registered with a delay (10 blocks = 60 s in the demo). It cannot move funds directly: the chain answers Proxy.Unannounced. It can only announce a call hash and wait.
  • Cold key, instant. It can veto an announcement (reject_announcement, dispatched as the vault) or retire the hot key (airlock panic).

A watcher reads every Proxy.Announced event for the vault and matches the hash against the owner's intent registry. A spend you registered is YOURS. Anything else is NOT YOURS, printed with the amount at risk, the veto deadline and a paste-ready airlock veto … line.

Airlock architecture

Six Proxy-pallet calls are the whole product: create_pure, add_proxy, announce, proxy_announced, reject_announcement and remove_proxy. There is no contract and no runtime change; the security is Portaldot's consensus, not my server.

What happened on the V3 testnet

On 2026-10-24, the run in the demo video (evidence/final-video-run-v3-2026-10-24.md):

  1. A thief holding the hot key announced a sweep of the whole vault (166811-1).
  2. The watcher printed NOT YOURS: 11.4200 tPOT at risk, "Veto must land by #166820".
  3. The owner pasted the veto line. It landed in block #166816, four blocks early (166816-1).
  4. After the delay the thief executed (166825-1) and then called the proxy directly (166831-1). Both failed Proxy.Unannounced.
  5. airlock status before and after: the same 11.4200 tPOT.

Earlier, on 2026-10-19, a 12.92 tPOT sweep was vetoed the same way, and scripts/verify_onchain.py re-fetched all 44 V3 proof rows by id: 44/44 matched.

On two local interim chains (not V3), a benchmark ran 20 thief drains per chain: 20/20 alerted and vetoed, 0 planck moved. A control vault with no delay lost 100 UNIT in one extrinsic.

Things I learned

  • Inclusion is not finality. V3 has two validators and can fork, so off the interim chains every send waits for finality before Airlock reports it.
  • Pin the chain. Chain profiles are pinned by genesis and runtime code hash; an unknown chain fails loudly instead of signing.
  • An announcement exposes only a hash. That is why the intent registry exists: anything not registered is treated as NOT YOURS.

Honest limits

If the owner ignores the alert for the whole delay, the spend executes: the delay must be longer than your response time. The benchmark has not run on V3. There is no hosted UI; the CLI and watcher are the owner interface. Testnet only.

Links

350 offline tests, plus 4 live-chain integration tests. Built for the Portaldot Hacker House 2026 (Ecosystem DApps). Feedback welcome, especially from validators and anyone holding POT on a hot key.

Top comments (0)