Aztec Experiments An anonymous billboard on Aztec v5 mainnet allows users to deposit ETH on L1, post anonymous messages on L2, and withdraw ETH back to L1, with no sender address in public call data. The system includes a censorship mechanism where a deployer-set censor can flag posts as 'immoral', hiding them from the default feed and imposing a time-lock penalty, and an automated local-LLM daemon enforces the on-chain moderation policy. Partial formal verification in Lean 4 / Verity has proven 70 security theorems with zero 'sorry's. An anonymous billboard on Aztec v5 mainnet. Users deposit ETH on L1, post messages anonymously on L2, and withdraw their ETH back to L1. Posts are fully anonymous — no sender address appears in public call data, and there is no link to the L1 deposit. Features: Censorship mechanism : A censor set at deploy time can flag posts as "immoral". Flagged posts are hidden from the default feed and impose a time-lock penalty on the poster's next post. The censor can transfer rights to another address. Automated censorship bot : A local-LLM daemon censor-daemon/ watches the billboard, evaluates posts against the on-chain moderation policy, and automatically flags violations. Moderation policy : The censor can set a text moderation policy on-chain up to 1488 bytes , shown in the UI and CLI. The daemon reads it as the source of truth. Partial formal verification : Security properties rate limits, censorship screening, privacy, deposit safety are formally verified in Lean 4 / Verity — 70 proven theorems, zero sorry s. See fv/ /vbuterin/aztec experiments/blob/master/fv . L1 Ethereum L2 Aztec ┌──────────────────┐ ┌──────────────────────┐ │ BillboardPortal │ │ Billboard │ │ │ │ │ │ deposit ETH │──msg──▶ │ claim deposit │ │ records deposit │ │ consumes L1→L2 msg │ │ sends L1→L2 msg │ │ creates DepositNote │ │ │ │ │ │ withdraw ETH │◀──msg── │ post │ │ consumes Outbox │ │ consumes note │ │ sends ETH │ │ stores message │ │ │ │ creates new note │ │ │ │ │ │ │ │ withdraw │ │ │ │ consumes note │ │ │ │ sends L2→L1 msg │ └──────────────────┘ └──────────────────────┘ Deposits : L1 deposit is public EOA + amount visible on Ethereum Posts : Fully anonymous — no sender address in public call data, no link to L1 deposit Withdrawals : Reveals depositor's L1 address inherent — ETH must go somewhere Anonymity set : All depositors who haven't withdrawn yet Posts are rate-limited by a time-lock stored in a private note. The cooldown is inversely proportional to the deposit amount: cooldown = base cooldown min deposit / amount For example with base cooldown=3600, min deposit=0.001 : - 0.001 ETH → 3600s cooldown 1 post/hour - 0.005 ETH → 720s cooldown 5 posts/hour - 0.01 ETH → 360s cooldown 10 posts/hour Each post advances next allowed time by one cooldown or more if flagged posts are screened . Posting multiple times in quick succession is possible if enough time has elapsed since the last post — the save-up rule max save up allows accumulating up to max save up posts of credit during dormancy, then spending them in quick succession. The deposit→withdraw loop does not help: re-depositing creates a new note with a fresh claim-time lock, identical to simply waiting. A censor set at deployment time can flag posts as "immoral" via declare immoral post index, response . Flagged posts are: Hidden from the billboard feed by default user app shows a "View censored posts" link with confirmation dialog Flag-penalized : when a flagged post is screened during a subsequent post call, the next post's time lock is extended by cooldown K-1 per flag K set at deploy time . This is equivalent to making K-1 dummy posts at the same time: screening 1 flagged post costs K total cooldowns 1 base + K-1 penalty . Screening uses constant-cost Merkle proofs instead of chain walking. Each post call screens 0–2 older posts the "child" and "grandchild" in the post chain via assert note existed by . A post can only be screened after the censor window default 3600s has passed since it was created — guaranteeing the censor time to flag it first. Withdrawal requires all real posts to be screened last screened index = last real post index . Users make dummy posts which advance screening without storing content to screen their last real post before withdrawing. If no real posts exist, only the initial claim-time lock must expire. The post chain uses a cryptographic link mechanism: each post stores a prev link field computed as poseidon2 hash with separator inner note hash, link secret , DOM SEP POST LINK , where link secret is derived from the poster's nullifier hiding key. This preserves anonymity — no public link between posts is revealed. The censor can transfer censorship rights to another address via transfer censor new address . The deployer has no post-deployment control over the censor. The censor can set a text moderation policy on-chain via set moderation policy fields, len up to 1488 bytes, stored as 48 Field values . The policy is readable via get moderation policy and is displayed in the user app, censor app, and CLI output. The automated censor daemon reads the on-chain policy as the source of truth for moderation decisions. See SECURITY PROPERTIES.md /vbuterin/aztec experiments/blob/master/SECURITY PROPERTIES.md for the full security analysis with line-by-line code references. The censor-daemon/ directory contains a self-contained daemon that watches the Billboard contract for new posts, runs them through a local LLM llama.cpp , and automatically flags any that violate the on-chain moderation policy. ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │ daemon.mjs │────▶│ llama-server │────▶│ Local LLM │ │ orchestr │ │ port 5090 │ │ Qwen3.5-2B │ └──────┬──────┘ └──────────────┘ └─────────────────┘ │ │ subprocess black-box ▼ ┌─────────────────────────────┐ │ cli.mjs list --json │ → posts , censorWindow, policy │ cli.mjs declare-immoral │ → flags post on-chain └─────────────────────────────┘ From any state, running node daemon.mjs will: Download + compile llama.cpp if not already present Download the model ~1.4 GB Q4 K M GGUF, Qwen3.5-2B by default Start llama-server — local OpenAI-compatible API on 127.0.0.1:5090 Read contract config — fetches the on-chain moderation policy, censor window, and max save-up Poll the billboard — shells out to cli.mjs list --json to read all posts Moderate each post — sends the post + policy to the LLM, asks "VIOLATION or OK?" Flag violations — shells out to cli.mjs declare-immoral to flag the post on-chain The daemon is a thin orchestrator — it never touches the Aztec SDK directly. All on-chain operations go through the existing user CLI as black-box subprocess calls. It is censor-window-aware : it prioritizes posts closest to expiring oldest first , warns on posts with <5 minutes left, and warns on posts already past the censor window. Policy resolution order: on-chain policy from get moderation policy → local policy.txt → hardcoded default. Dry-run evaluate but don't flag on-chain : node censor-daemon/daemon.mjs --dry-run --once Live actually flag violations : node censor-daemon/daemon.mjs --poll-interval 30 Tests: 23 unit tests test moderation.mjs + 14 integration tests test daemon.mjs with mock infrastructure — no network or llama.cpp required. Run bash censor-daemon/run tests.sh . See censor-daemon/README.md /vbuterin/aztec experiments/blob/master/censor-daemon/README.md for full details. Before interacting with L2, you need Fee Juice to pay transaction fees. This app handles the full flow: Swap ETH→AZTEC optional — If you don't already have AZTEC tokens, the app swaps ETH for AZTEC via Uniswap V3 on L1. Deposit to L2 — Approves the FeeJuicePortal and deposits AZTEC tokens, sending an L1→L2 message. Scan / Recover — Scans L1 DepositToAztecPublic events backwards to find your deposit, derives the claim secret from your ETH wallet signature, and checks nullifier consumption + checkpoint status. Claim on L2 — Consumes the L1→L2 message to mint Fee Juice on L2. Also supports an auto action that runs deposit → wait → claim in one go. Deploys and links the billboard contracts. The deployment has a circular dependency L1 portal needs L2 address, L2 contract needs L1 portal address , broken by making the L2 constructor take no arguments and setting the portal later. Deploy L2 — Deploys the Noir Billboard contract using universal deploy address depends only on salt + artifact, not deployer wallet . Deploy L1 Portal — Deploys the Solidity portal via CREATE2 deterministic from L2 address + rollup + version . Link Portal — Calls update portal on L2 to store the L1 portal address. Cross-Check — Verifies L1→L2 and L2→L1 references match. The main user-facing app. Full flow: deposit → claim → post → withdraw → claim on L1. Deposit ETH — Deposits ETH into the L1 portal sends an L1→L2 message . Claim on L2 — Consumes the L1→L2 message to create a private DepositNote. Post — Posts an anonymous message. Each post extends the time lock by one cooldown. The billboard feed auto-refreshes every 5 seconds. Withdraw — Consumes the DepositNote and sends an L2→L1 message. Claim on L1 — After the epoch proof ~40 min on mainnet , consumes the Outbox message to claim ETH on L1. The billboard feed hides censored posts by default . A "View censored posts" link at the bottom shows how many posts are hidden; clicking it opens a confirmation dialog "These posts have been flagged as immoral by the censor. Are you sure you want to see them?" . Confirming reveals the censored posts with grey styling and a "Hide censored posts" toggle to hide them again. The auto action runs the full flow end-to-end autonomously. A dedicated app for the censor to manage censorship. No ETH wallet needed — the censor only operates on L2. Load censor wallet — Load the censor's Aztec wallet the one whose address was passed to init at deploy time, or the one that received rights via transfer censor . Flag posts — Enter a post index and optional response text, then flag it as immoral. Flagged posts are hidden from the user billboard by default. When a flagged post is later screened during a subsequent post, the poster's next post time lock is extended by K-1 extra cooldowns per flag. Transfer rights — Transfer censorship authority to another Aztec address. View billboard — The censor sees all posts including flagged ones with censor response messages . The censor is set at init time only — the deployer cannot change it after deployment. Only the censor can transfer rights to another address. aztec/ ├── README.md ← this file ├── SECURITY PROPERTIES.md ← security analysis with code references ├── wallet.json ← Aztec wallet secret key + salt ├── eth wallet.json ← ETH wallet private key ├── package.json ← ethers + fake-indexeddb for CLIs │ ├── billboard/ ← Contracts │ ├── Nargo.toml ← Noir workspace │ ├── billboard contract/ ← L2 contract Noir │ │ ├── Nargo.toml │ │ └── src/ │ │ ├── main.nr ← Billboard contract │ │ └── lib.nr ← Helpers: msg hashes, cooldown computation │ ├── portal/ ← L1 contract Solidity / Foundry │ │ ├── foundry.toml │ │ └── src/ │ │ └── BillboardPortal.sol │ └── target/ ← Compiled Noir artifacts gitignored │ ├── shared/ ← Shared modules used by all apps │ ├── styles.css ← Dark theme CSS │ ├── helpers.js ← UI helpers: logging, pagination, extractors │ ├── wallet-buttons.js ← Common wallet loading/generation UI module │ ├── app-env.js ← Common browser env builders buildEnv, buildConfig, makeCallEngine │ ├── aztec-lib.js ← PXE/CRS/wallet infrastructure + Fee Juice constants │ ├── aztec bundle.js ← Aztec PXE bundle WASM, ~58MB │ ├── ethers.min.js ← ethers v6 for L1 interactions │ ├── poseidon2.js ← Pure-JS Poseidon2 verified against bb.js │ └── rpc-config.json ← Aztec node URL + API key │ ├── apps/ ← Web apps + CLIs │ ├── build.mjs ← Build script combines modules → single-file HTML │ ├── serve.py ← Dev server with COOP/COEP headers for multi-threaded WASM │ ├── src/ │ │ ├── fee-juice/ ← Fee Juice app │ │ │ ├── template.html │ │ │ ├── app.js ← Thin wrapper UI → engine │ │ │ ├── engine.js ← Fee Juice flow logic swap, deposit, scan, claim │ │ │ ├── cli.mjs ← CLI tool same engine, Node.js │ │ │ └── pxe-cache.cjs ← IndexedDB dump/restore for CLI PXE caching │ │ └── billboard/ │ │ ├── billboard artifact.json ← Compiled L2 contract artifact │ │ ├── portal bytecode.txt ← Compiled L1 portal bytecode │ │ ├── build artifact.mjs ← Artifact build script VKs, bytecode │ │ ├── build vks.sh ← VK computation helper │ │ ├── deploy/ ← Deploy app │ │ │ ├── template.html │ │ │ ├── app.js ← Thin wrapper │ │ │ ├── engine.js ← Deploy flow L2 + L1 + link + cross-check │ │ │ ├── cli.mjs ← CLI tool │ │ │ ├── gen eth wallet.mjs ← ETH wallet generator │ │ │ ├── gen censor wallet.mjs← Censor Aztec+ETH wallet generator │ │ │ ├── gen user wallet.mjs ← User Aztec+ETH wallet generator │ │ │ ├── billboard artifact.json │ │ │ └── portal bytecode.txt │ │ ├── censor/ ← Censor app │ │ │ ├── template.html │ │ │ ├── app.js ← Flag posts, transfer rights, view all posts │ │ │ ├── engine.js ← Symlink → user/engine.js shared engine │ │ │ └── pxe-cache.cjs ← Symlink → user/pxe-cache.cjs │ │ └── user/ ← User app │ │ ├── template.html │ │ ├── app.js ← Thin wrapper UI → engine, live feed, censored post hiding │ │ ├── engine.js ← User flow deposit, claim, post, withdraw, claim-l1, declare-immoral, transfer-censor │ │ ├── cli.mjs ← CLI tool │ │ └── pxe-cache.cjs ← IndexedDB dump/restore for CLI PXE caching │ ├── dist/ ← Built single-file apps │ │ ├── fee-juice.html │ │ ├── deploy.html │ │ ├── user.html │ │ ├── censor.html │ │ ├── aztec bundle.js ← Copy of the PXE bundle │ │ └── crs/ ← CRS files for proving │ ├── censor-daemon/ ← Automated censorship bot local LLM │ ├── daemon.mjs ← Orchestrator: llama.cpp setup, polling, flagging │ ├── moderation.mjs ← LLM moderation logic prompt, verdict parsing │ ├── policy.txt ← Default moderation policy fallback │ ├── test moderation.mjs ← Unit tests for moderation parsing 23 tests │ ├── test daemon.mjs ← Integration tests with mock infra 14 tests │ ├── run tests.sh ← Test runner script │ └── README.md ← Full daemon documentation │ ├── fv/ ← Formal verification Lean 4 + Verity │ ├── PLAN.md ← Verification plan and status │ ├── billboard aztec lean/ ← Lean model of L2 Noir contract 51 theorems │ │ └── BillboardAztec.lean │ ├── billboard portal verity/ ← Verity port of L1 Solidity portal 12 theorems │ │ ├── BillboardPortal.lean │ │ ├── Spec.lean, Invariants.lean │ │ └── Proofs/Basic.lean │ ├── bridge model/ ← Lean model of L1↔L2 bridge 7 theorems │ │ └── Bridge.lean │ └── notes/ │ └── SECURITY PROPERTIES FORMAL.md ← Theorem-by-theorem mapping P1-P18, I1-I12 │ ├── .pxe-cache/ ← CLI PXE cache IndexedDB dumps, gitignored │ └── node modules/ ← ethers, fake-indexeddb Each app follows the same pattern: — All business logic. Takes an engine.js env object { aztec, ethers, log, initCRS, createStore, getEthSigner, portalBytecode, artifact } and a config object. Returns { ok, state, handles } where handles gives the web UI access to live PXE/contract objects for polling.— Thin UI wrapper. Builds app.js env / config from UI state, calls the engine with the appropriate action, routes log callbacks to page-specific status divs.— CLI tool. Same engine, Node.js environment. Polyfills IndexedDB with cli.mjs fake-indexeddb , loads the bundle, runs the engine. Both the web UI and CLI share the same engine, so any flow that is autonomous in the CLI is also autonomous in the web UI. Fee Juice : status , scan , deposit , claim , auto User : status , deposit , claim , post , list , withdraw , claim-l1 , declare-immoral , transfer-censor , auto Deploy : status , deploy The auto action runs the full flow end-to-end: deposit → wait for L2 ingest → claim → post if message provided → withdraw → claim-l1. Censor daemon : daemon.mjs is not an engine action but a standalone orchestrator that shells out to cli.mjs list --json and cli.mjs declare-immoral as subprocesses. See censor-daemon/README.md /vbuterin/aztec experiments/blob/master/censor-daemon/README.md . 1. Compile L2 contract aztec compile transpiles public ACIR → AVM bytecode + adds VKs cd billboard && aztec compile 2. Build the artifact computes VKs with chonkComputeVk, packages bytecode cd apps/src/billboard && node build artifact.mjs cp billboard artifact.json deploy/billboard artifact.json 3. Compile L1 portal if portal bytecode.txt needs updating cd billboard/portal && forge build --use 0.8.33 4. Build all dapps combines modules into single-file HTML cd apps && node build.mjs cd fv/verity export PATH="$HOME/.elan/bin:$PATH" lake build 2125 jobs, zero sorrys The L2 contract's Nargo.toml must depend on aztec-nr tag v5.0.0 — the same version the aztec bundle.js was built from. Version mismatches cause standard contract addresses HandshakeRegistry, AuthRegistry, etc. baked into the ACIR to differ from those registered in PXE, causing "contract is not registered" errors. Each recompilation changes the contract class ID public bytecode commitment , so always use a salt that was never used before when deploying. Fee Juice swap ETH→AZTEC, deposit, claim — all in one node apps/src/fee-juice/cli.mjs --gen-all Generate new wallets node apps/src/fee-juice/cli.mjs --status --aztec-wallet wallet.json --eth-wallet eth wallet.json node apps/src/fee-juice/cli.mjs --scan --aztec-wallet wallet.json --eth-wallet eth wallet.json node apps/src/fee-juice/cli.mjs --eth-for-swap 0.005 --aztec-wallet wallet.json --eth-wallet eth wallet.json Billboard user deposit → claim → post → withdraw → claim-l1 node apps/src/billboard/user/cli.mjs status --contract-salt 2028 --aztec-wallet wallets/user aztec wallet.json --eth-wallet wallets/user eth wallet.json node apps/src/billboard/user/cli.mjs deposit --contract-salt 2028 --amount 0.002 --aztec-wallet wallets/user aztec wallet.json --eth-wallet wallets/user eth wallet.json node apps/src/billboard/user/cli.mjs post --contract-salt 2028 --msg "hello world" --aztec-wallet wallets/user aztec wallet.json --eth-wallet wallets/user eth wallet.json \ --censor 0x0035abfebdafd10697b8a9a5de2792715602ff077787ca6cfefdab632547189f --k-multiplier 4 node apps/src/billboard/user/cli.mjs list --contract-salt 2028 --aztec-wallet wallets/user aztec wallet.json --eth-wallet wallets/user eth wallet.json \ --censor 0x0035abfebdafd10697b8a9a5de2792715602ff077787ca6cfefdab632547189f --k-multiplier 4 node apps/src/billboard/user/cli.mjs withdraw --contract-salt 2028 --aztec-wallet wallets/user aztec wallet.json --eth-wallet wallets/user eth wallet.json \ --censor 0x0035abfebdafd10697b8a9a5de2792715602ff077787ca6cfefdab632547189f --k-multiplier 4 Censor: flag posts and transfer rights node apps/src/billboard/user/cli.mjs declare-immoral --contract-salt 2028 --post-index 0 --censor-response "spam" \ --censor 0x0035abfebdafd10697b8a9a5de2792715602ff077787ca6cfefdab632547189f --k-multiplier 4 \ --censor-wallet wallets/censor aztec wallet.json node apps/src/billboard/user/cli.mjs transfer-censor --contract-salt 2028 --new-censor 0x... \ --censor 0x0035abfebdafd10697b8a9a5de2792715602ff077787ca6cfefdab632547189f --k-multiplier 4 \ --censor-wallet wallets/censor aztec wallet.json python3 apps/serve.py port default: 5000 project convention Sets COOP/COEP headers for SharedArrayBuffer multi-threaded WASM and serves from apps/dist/ . Local CRS files in apps/dist/crs/ are loaded automatically. The L2 contract is deployed with universalDeploy: true and publicKeys: deriveKeys Fr.ZERO .publicKeys , so the contract address depends only on the salt + artifact — not the deployer's wallet. This means the deploy and user apps always compute the same address. The v5 bundle uses the initializerless Schnorr account contract for self-deployment. The regular SchnorrAccountContract stores its signing key in a private note created by the constructor, but during self-deployment deploy + claim fee juice in one tx , the entrypoint runs before the constructor, so get note fails. The initializerless variant stores the signing key in a PXE capsule and verifies against immutables hash . The web apps run the full PXE stack entirely in the browser: IndexedDB store for PXE state data directory includes wallet address for isolation CRS files loaded from local files first, falling back to CDN Proving via bb.js WASM multi-threaded when COOP/COEP headers are set No backend server — all RPC goes directly to the Aztec node Both CLIs dump/restore the in-memory IndexedDB state to ~/.pxe-cache/ between runs, saving ~2-4s on PXE sync. Cache files are keyed by account address. The shared wallet-buttons.js module provides a common wallet UI across all three apps with: ETH wallet : Load from JSON, Generate random, or connect browser wallet Aztec wallet : Load from JSON, Generate random, or derive from ETH wallet sign a message, use signature as secret key - Color-coded button states and auto-advancement when all wallets are loaded nargo / aztec CLI must match the v5 bundle version forge Foundry, for compiling the Solidity portal Node.js ≥ 24- A browser wallet for L1 transactions in the web UI Lean 4 / elan only for building formal verification proofs in fv/ The fv/ directory contains a partial formal verification of the billboard's security properties using Verity https://veritylang.com Lean 4 framework for verified smart contracts and a standalone Lean model of the L2 contract. Build status: 2125 Lean jobs, zero sorrys, 70 proven theorems/lemmas. | Component | File | Theorems | Axioms | Status | |---|---|---|---|---| L2 model Lean | fv/billboard aztec lean/BillboardAztec.lean | 51 | 5 | Constant-cost screening model: I1–I12, deposit safety, privacy | L1 portal Verity | fv/billboard portal verity/ | 12 | 1 | Solidity port rewritten as verity contract , guard proofs + conservation | Bridge model Lean | fv/bridge model/Bridge.lean | 7 | 2 | Inbox/Outbox no-double-consume, content-hash agreement | L2 model 51 theorems, models the Noir contract's constant-cost screening design from LATEST CHANGE.md /vbuterin/aztec experiments/blob/master/LATEST CHANGE.md : I1 — Constant-cost screening: each post screens at most 2 posts zero axioms I3 — Censor window guarantee: posts only screened after censor window expires zero axioms I4 — Monotonic advance: last screened never goes backwards zero axioms I5 — Flag penalty: each flag adds exactly cooldown × K-1 zero axioms I6 — Save-up rule bounds burst posting zero axioms I7 — Withdrawal requires full screening zero axioms I10 — Chain link unforgeability uses poseidon2 injective axiom Master safety theorem deposit safety master : withdraw ≤ T stop + ⌈N/2⌉ × C × 1+ K-1 ×2 — no permanent lockout Master privacy theorem post unlinkability master : two posts by same user are indistinguishable from two by different users constructive proof, zero axioms Censor governance : only censor can flag/transfer/set-policy proven L1 portal 7 Verity proofs + 5 stubs : P2 no double deposit , P3 withdraw zeroes balance , P4 no withdraw without deposit , P14 u128 overflow , conservation of totalDeposited . Bridge : no-double-consume for Inbox and Outbox, content-hash agreement axioms P1/P16 . 8 axioms total, all at cryptographic/platform trust boundaries: poseidon2 injective — hash injectivity cryptographic assumption nhk app opacity — link secret not derivable from public data cryptographic nullifier unlinkability — nullifiers don't reveal owner Aztec platform property deposit/withdraw hash agreement — L1 and L2 agree on content hashes bridge boundary externalCallStubBool true — Verity test stub for external calls returns true See fv/notes/SECURITY PROPERTIES FORMAL.md /vbuterin/aztec experiments/blob/master/fv/notes/SECURITY PROPERTIES FORMAL.md for the full theorem-by-theorem mapping, and fv/PLAN.md /vbuterin/aztec experiments/blob/master/fv/PLAN.md for the verification plan. - Strengthen end-to-end FV theorems — The bridge soundness theorems P1, P5, P16 and several L2 invariants I2, I8, I9, I11, I12 are currently stated as True := by trivial . Strengthen these to full proofs connecting L1 Verity + L2 Lean + bridge model. - Replace in-app JSON Ethereum wallet with — The current "Load from JSON" ETH wallet option requires users to manually export and load a private key JSON file. Replace this with OpenLV integration for a more secure, user-friendly wallet connection. OpenLV http://openlv.sh/ - End-to-end browser test of censor app — The censor web app censor.html has been built but not yet tested in a browser. The CLI declare-immoral and transfer-censor actions have been fully tested on mainnet salt 2028 . - End-to-end browser test of censored post hiding — The "View censored posts" confirmation dialog and grey styling have been implemented in the user app but not yet tested in a browser against live mainnet data. - Add access control guard to — Currently anyone can call it first with a bogus address, bricking the contract griefing DoS, not theft . Should be deployer-only or called in the same L2 tx batch as deploy. See update portal SECURITY PROPERTIES.md /vbuterin/aztec experiments/blob/master/SECURITY PROPERTIES.md §5. - Fix — Theoretically wraps after ~134M posts, overwriting post 0. Economically unreachable today but should be fixed for correctness. post count / post data key type from u32 to u64/Field - Fix — Over-counts across deposit/withdraw cycles never decremented . Not security-relevant withdrawals read totalDeposited accounting drift deposits msg.sender , not the total but cosmetically wrong. - Surface L1 withdrawal claim in the web UI — The claim-l1 action exists in the engine/CLI but the web UI doesn't expose it as a distinct page with Outbox proof input. - Add "view on explorer" links for confirmed L2 transactions e.g. aztecscan.com . - Show estimated wait time for epoch proofs in the UI during claim-l1 ~40 min on mainnet . - Suppress verbose PXE/bundle console output in the browser — Partially done, but pino logger output still leaks through to the console. - Add transient RPC retry logic to web apps — The CLI has this via a TRANSIENT RE regex; the web apps may not. - Handle multiple deposits to different Aztec addresses from the same ETH wallet — Fee-juice scan filters by to address, but the UX could be clearer about which deposit is being claimed. - Withdraw deposits on old test deployments — Salts 2025, 2026, 2027, 2028 have deposits that can be withdrawn to recover ETH.