Release Lockvote (ERC-20 symbol LVOT) on Sepolia as an evm_project: the fixed-supply launch token plus one application contract. Token: Lockvote (LVOT), total supply 1,000,000,000 LVOT with 18 decimals, minted once to the deployer. Application contract: LockVoteTreasury. Currency: LVOT is the app's working currency. LockVoteTreasury's constructor takes one argument, the LVOT address (constructorArgs ["$token"]); it stores the token immutable, exposes it as token(), and holds no LVOT at deploy and never needs any: users get LVOT by swapping Sepolia ETH in the ETH/LVOT launch pool the factory seeds. LVOT comes in only through lock (approve + SafeERC20.safeTransferFrom) and goes back only through unlock (safeTransfer to the caller); nothing is burned. LVOT is a plain fixed-supply ERC-20 with no transfer fee, so the amount pulled is the amount credited. The treasury itself holds ETH: anyone may send ETH through receive() or donate() (event Donated). No owner, admin, pause or upgrade path; only passed proposals move ETH. A Sepolia test toy: the treasury holds only Sepolia test ETH that testers donate, LVOT votes carry no off-chain rights, and the README and the page say so. Locking: lock(amount) pulls LVOT; unlock(amount) returns it and reverts while block.timestamp < lockedUntil[holder], where lockedUntil is the latest voteEnd of any proposal the holder voted on, so locked tokens cannot vote, move and vote again while a vote is open. Proposals: propose(recipient, amountWei, bytes32 descriptionHash) needs >= 100,000 LVOT locked (0.01% of supply), recipient != 0 and amountWei > 0; voteEnd = creation time + 3 days. vote(id, support), only while block.timestamp < voteEnd, once per address per proposal with weight = the voter's locked balance at that moment (locking more later adds nothing; no vote changes); zero weight reverts. A proposal passes when forVotes > againstVotes and forVotes + againstVotes >= 1,000,000 LVOT (0.1% of supply, low enough to reach with tokens swapped on Sepolia); ties fail. execute(id): anyone, only when voteEnd + 1 day <= block.timestamp < voteEnd + 15 days (from voteEnd + 15 days it is Expired), only if passed, not executed and address(this).balance >= amountWei; mark executed, then send ETH to the recipient with call (the one push payment, by design; every state-changing function is nonReentrant); if the call fails the whole execute reverts and can be retried inside the window. Competing passed proposals are paid first come, first served; others revert until the balance allows. Only ETH transfers, never arbitrary calldata. Accepted and stated in the README: whoever locks a quorum can pass an unopposed proposal; the 1-day delay only gives visibility. Views: proposalCount(), proposal(id) (recipient, amount, descriptionHash, proposer, voteEnd, for, against, state), locked(holder), lockedUntil(holder). Events: Locked, Unlocked, Proposed, Voted, Executed. Tests (Foundry) must cover: boundaries at voteEnd, voteEnd + 1 day and voteEnd + 15 days, quorum at exactly 1,000,000 LVOT, a tie, a reverting recipient, unlock before lockedUntil, and the invariants that the LVOT balance equals the sum of locks, ETH leaves only through executed proposals, each at most once, and a holder's tokens never count twice on one proposal. The independent adversarial review must attack: double voting via unlock-transfer-relock, vote weight after additional locks, quorum/tie off-by-one, execute before the delay, twice, or after expiry, re-entrancy from the recipient, and ETH accounting when several proposals pass. Deploy through the project factory, then publish a one-page website to lock and unlock (with unlock time), propose, list proposals with state and tallies, vote, execute, and show the treasury's ETH balance. The page reads the LVOT address from LockVoteTreasury.token(), shows the connected wallet's LVOT balance and allowance, has an Approve step before locking, and says LVOT comes from swapping Sepolia ETH in the launch pool (no in-page swap). Lists come from contract views and events only (no backend, no indexer; log queries are chunked from the deployment block). Keep it to one small page; the static export has index.html in dist/.