Repository navigation
[PROPOSAL] RFP-017 — Antumbra Vesting: Privacy-Preserving Token Vesting #186
Description
RFP ID
RFP-017 — Privacy-Preserving Token Vesting
Your Project Name
Antumbra Vesting
Team or Organization Name
Antumbra
Primary Contact
Telegram https://t.me/edenbdn or edenbaud1@gmail.com. Either reaches both of us within a day.
Team Members
Eden Baudin, engineer, privacy & ZK
- GitHub: https://github.com/edenbd1
- X: https://x.com/edenbdn
- Telegram: https://t.me/edenbdn
- LinkedIn: https://www.linkedin.com/in/edenbdn/
- Role: the vesting program and its v0.3 port, the custody and private-claim paths, the CLI, the Logos Core module and Basecamp mini-app, CI and the evidence trail.
- Availability: full time, for the whole grant.
- λPrize LP-0005, Private Balance Attestation: won. Solution PR Solution: LP-0005 — Private Balance Attestation lambda-prize#64 merged on 2026-08-02 by the Logos team; the solution file is in the official repository at https://github.com/logos-co/lambda-prize/blob/master/solutions/LP-0005.md; the prize is closed. Code: https://github.com/edenbd1/lp-0005-private-token-balance-attestation
- 15+ hackathon wins.
- Ex-DevRel @ iExec in privacy across RWA and DeFi.
- Incubated at the Berkeley Blockchain Xcelerator.
- Built Logos Forum, a serverless forum for Basecamp on Logos Delivery and Storage, and run its activity on testnet v0.3: topics, builder support, testnet LGO for newcomers.
Nada Ahmadi, engineer, DeFi
- GitHub: https://github.com/nadaamd
- LinkedIn: https://www.linkedin.com/in/nada-ahmadi/
- Role: precision-safe integer arithmetic and its differential tests, the SDK, the mini-app's front end and wallet integration.
- Availability: full time, for the whole grant.
- ETHGlobal Lisbon 2026, Umbra: 3rd place, Uniswap Foundation "Best Uniswap API Integration" (showcase), an autonomous circuit breaker for DeFi positions, backtested over 48,000 real swaps.
- Full-stack developer at XRPL Commons (LinkedIn above). Member of DeVinci Blockchain, ESILV.
Continuity. Both of us can build, deploy and drive the program. The deploy runbook, docs/deploy-v03.md, goes from a clean checkout to a deployed header, and scripts/e2e-v03.sh replays every transaction cited below against any v0.3 sequencer, local or public. What the program does and why is in the repository (the port plan, the IDL, the test names, the CI jobs), not in one person's head.
Capacity. We have other proposals open in this repository. If more than one is accepted, we deliver them one after the other, not in parallel, and confirm this grant's start date on acceptance; the remaining effort below (about 6.5 weeks on the critical path) is this grant's alone, with both of us on it full time, and the price does not depend on the start date.
Project Summary
Verifiable in five minutes.
Program on the public LEZ testnet v0.3: header account
FCrja8g2ZKvxZwNZchdppKWQCDxPNUHxnEMidrmqrt6X, ImageID72d5cdc05004a9502be72239829071982237638b49b8fe7d1b45fd7371a425f4, read back from the header'sprogram_loaderrecord.One command re-checks the whole testnet run (57 checks: every applied transaction, every refusal, the header's ImageID, the final state of every schedule and escrow, and a random hash that must not resolve):
git clone https://github.com/edenbd1/antumbra-lez && cd antumbra-lez && ./scripts/verify-onchain.sh --manifest evidence/v03/testnet.tsv --rpc https://testnet.lez.logos.coExpected last line:
57 checks pass, 0 fail(our output).Reproducible build: the CI job A clean rebuild is the committed v0.3 vesting program rebuilds the guest in RISC Zero's pinned Docker builder (
r0.1.91.1) and compares it byte for byte with the committedartifacts/programs/v0.3/antumbra_vesting.bin. Locally:RISC0_DOCKER_CONTAINER_TAG=r0.1.91.1 cargo risczero build --manifest-path programs/vesting/Cargo.toml, thencmpwith the artifact (docs/deploy-v03.md§0).CI on
main:, last run 39015361838, all eleven jobs green.
Demo film (3 min 50, my own voice): https://github.com/user-attachments/assets/f5e85a8f-f9d5-4f38-a88e-51cf0894f28d. Recorded on v0.2.4, before the port to v0.3. The flows are the same, but the program on screen reads the LEZ clock accounts, the Basecamp panel is the earlier one, and the hashes are from the pre-upgrade chain.
Design decision log:
docs/DECISIONS.mdrecords the 38 design decisions behind the program (27 in force, 11 superseded and kept with what replaced them) (context, options considered, choice, rationale, trade-offs, and where each one is implemented or proven on chain), in the form the RFP template now asks for (#209), and is kept current at each milestone.
Requested: $35,000 over 10–12 weeks, paid as milestone payouts on acceptance. The external audit is funded separately; we prepare it and fix every finding at no charge.
In two sentences. A vesting program whose beneficiaries can take their tokens into a shielded account, so an unlock stops being a public signal of who is about to sell. It already runs on the public LEZ testnet v0.3, with native balance and with a real token-program token, claims into public and private accounts, and time bounded by the chain itself, and every capability below is a transaction you can open.
On a transparent chain a vesting contract is a published calendar of forced sellers with names attached; the RFP's $97.43B of 2025 unlocks were tracked in public, and traders position against them. LEZ changes one fact: a program may credit a shielded account directly, so the destination of a claim never appears on chain. This proposal builds the program around that fact and ships the SDK, CLI and Basecamp mini-app that make the private path the ordinary one.
What is already on the public testnet (v0.3). Program antumbra_vesting, header FCrja8g2ZKvxZwNZchdppKWQCDxPNUHxnEMidrmqrt6X, ImageID 72d5cdc0…71a425f4, built from LEZ v0.3.0 (db66590a) and deployed through program_loader. The testnet was reset and moved to sequencer v0.3.1 on 2026-10-12; the program was redeployed at the same id on 2026-10-14, and the header's program_loader record names the same ImageID. The whole lifecycle was then driven again on 2026-10-14: 21 transactions applied and 13 refused in a block where the RFP says they must be, plus a repeated milestone signal and a second cancel the sequencer never included, with 14 balance and state checks read back around them; its first creation is recorded not-included because an earlier attempt is the one that landed, which the section's own checks confirm, and that section was rerun alone the same day, clean (evidence/v03/testnet-section1.tsv), and the rows below cite that rerun for it. v0.3.1 removed the builtin token program, so on 2026-10-17 LEZ v0.3.0's own token.bin was loaded on the testnet as an ordinary, immutable program (3cwJjDqD…) next to a build of this program that pays out through it (B2ZDRw7f…, identical except for that one constant); the token rows cite that run, evidence/v03/token-v031.tsv, 6 applied, 0 failures, re-checked 14/0. A native cliff schedule was driven on the same testnet on 2026-10-14 by the main program (evidence/v03/cliff.tsv, verifier 7/0). Every row is in DEPLOYMENTS.md and in the manifest evidence/v03/testnet.tsv. On 2026-10-16 every explorer link below resolved: 24 pages read Public Transaction with Program ID FCrja8g2…, and the private claim reads Privacy-Preserving Transaction; verify-onchain.sh asks the sequencer directly.
| What the RFP asks for | On chain now (v0.3) |
|---|---|
| Claim into a private account, native balance (F2, Pr1) | 268e4463, block 10660: a Privacy-Preserving Transaction whose public effects are the schedule and its escrow; the shielded account the 250 went to is not among them |
| Claim into a private account, a token-program token (F2, Pr1) | on chain, 6c0f7ea004bbebc9… (block 28,420): 5,000 tokens into a shielded account, decrypted by the wallet; evidence/v03/token-v031.tsv |
| Tokens locked in escrow: a real token held by the token program, paid out by this program (F1) | on chain, created and funded in one transaction (100,000 tokens, ad5c16cfae11e01e…), claimed (117c427e6d771b55…, 3,888) and cancelled (92aab23c7cf3eea2…), evidence/v03/token-v031.tsv, and through the committed binary in executor-tests (a_token_schedule_escrows_claims_and_refunds_the_token) |
| Time bounded by the chain, not by the caller | a claim names its time and is valid only once the block timestamp reaches it; a claim dated after the chain's clock was refused, c010024b. The private claim's explorer page shows the window, [t, ∞) |
| Linear accrual, to the unit (F1) | 1b5d3a16 paid 100 of 600, exactly ⌊total × elapsed ÷ duration⌋ at the time it named; a claim for 601 was refused, baacfe81 |
| Cancellation: unvested returns, vested stays claimable (F3) | cancelled b0bc8b50: the refund was exactly the unvested 517, the vested 83 stayed in escrow and was claimed afterwards, 092130ae; a wrong refund account refused 10ec0633, and a second cancel never reached a block (its hash does not resolve) |
| The same with a token (F3) | on chain, cancel-token 92aab23c7cf3eea2… in evidence/v03/token-v031.tsv: refund plus escrow plus claimed equals the 100,000 total |
| Milestones, signalled by a nominated authority, each once (F1, R4, soft req.) | signals 22a5a10b and 50f6b081, claim of two of four tranches (500) f5516c7f; the creator's own signal refused 2cece861 |
| Holder-only transfer; one-way non-cancelable (F3, F4) | creator's transfer refused 13deee98, holder's transfer 791dac40, the old holder's claim refused fd473c5b, the new holder claimed all 300 7e4f08af; non-cancelable 778d0dbf, a cancel after it refused 7f091c77 |
| Batch creation: N beneficiaries in one transaction, funded by one transfer (F5) | 8 schedules created and funded in one transaction c3483b09; one claimed 967f2be3, another cancelled 9bf3e3b3, each moving only its own share; 450 schedules in one transaction 8d553905, 451 refused e8030e3a under the 10M gas cap |
| Nominated cancel authority (soft requirement) | the authority cancelled 5c5f8edd; the creator's own cancel refused 541a8601 |
On v0.3 a refused public transaction is included in a block, charged, and changes nothing, so the refusals above are pages you can open too. The explorer does not mark a transaction as refused; the state shows the refusal, and the verifier checks each verdict and the final state of every schedule and escrow.
That is the Basecamp mini-app (0.4.0) reading testnet4-lin from the public v0.3 testnet on 2026-10-07, before the reset (0.4.2 reads the testnet7-* schedules of the 2026-10-14 run): 600 in total, all of it vested, 60 claimed, 540 claimable and 540 held by the escrow, the vesting curve against the chain's own clock, and the exact claim command for the public or the shielded path. Further down, the view lists the schedule's transactions, with refused attempts marked as such. It holds no keys and signs nothing. More screens (milestones, a batch of eight, a beneficiary look-up, a narrow window) are in docs/screens/.
What is not built yet is the subject of the grant: per-recipient amounts and detach for batch positions, the per-creator and per-beneficiary index PDAs, the fee switch, events read back from the testnet indexer, the SDK, the mini-app's claim action, creator view and disclosure screens, the end-to-end suite against a standalone sequencer in CI, the audit, and mainnet. The milestones below say what each one has already delivered and price the rest; none depends on a primitive that is not in the v0.3 runtime.
Technical Approach
1. Accounts and instructions
v0.3 has no program-owned accounts: an account carries a map of shards keyed by program id, and native balance is the shard at [0; 32]. A program is addressed by its header account, chosen at deploy, and every PDA derives from that header. Two PDAs per schedule, both from its id: the schedule [schedule_id], whose shard for this program holds the terms, and the holding [schedule_id, "holding"], whose native or token shard holds the escrow. Multi-seed PDAs keep SPEL's rule (one seed direct, several hashed with SHA-256), so the layout reads as it did on v0.2.4. Every instruction re-derives the addresses it touches from the header, so none can be pointed at an account the caller invented. A single schedule has its own holding, so claims against different schedules touch disjoint accounts (R3); a batch shares one holding, and §8 says what that costs.
A schedule id is written once: creation's apply step refuses any shard that already has bytes (7019), so a schedule id cannot be reused to rewrite a beneficiary's terms, and a replayed creation cannot pay into the escrow a second time. On testnet a duplicate creation was refused and took no second funding, 48aeeadd.
| Instruction | Signs | What it does |
|---|---|---|
create_schedule |
creator | terms, beneficiary, cancelable and transferable flags, optional cancel and milestone authorities, refund account; native or a token definition; funded with exactly the total in the same transaction |
create_schedule_batch |
creator | one schedule per beneficiary on shared terms, one shared holding, funded with exactly total × n by one transfer |
claim(amount, at) |
beneficiary | pays amount into a destination the beneficiary names, valid only once the block timestamp reaches at |
cancel(at, refund) |
cancel authority | freezes accrual at at, returns exactly the unvested part to the refund account fixed at creation; valid only in [at, at + 2 min) |
make_non_cancelable |
creator | one-way: no instruction raises the flag again |
transfer_beneficiary |
current beneficiary | only if transferable, and never by the creator |
signal_milestone |
milestone authority | sets one bit; a set bit is refused |
Batch schedules take the same claim and cancel with the batch id, so the escrow derives from the batch. Creation and funding are one transaction on v0.3, which removes the separate funding step of v0.2.4 and the underfunded state it allowed.
SPEL has no v0.3 support yet, so the guest is written against lee_core directly and the IDL is maintained by hand in SPEL's JSON shape: idl/antumbra_vesting.v03.idl.json. A CI step, scripts/check-idl.py, decodes borsh vectors emitted by the program's own core crate using only the IDL and fails on any drift in fields, discriminants, event selectors or refusal codes. Every transaction in this document was sent by the project's Rust CLI on the v0.3 wallet crate (cli/), scripted in scripts/e2e-v03.sh.
2. Plan and apply: where every check lives
A v0.3 program runs twice per call. Plan sees account ids, signer flags and which shard each row selects, never data; it decides the effects, the chained calls, the validity windows and the events. Apply then runs once per effect, against one shard's current bytes, and may replace them only if the shard is the program's own (programs/vesting/core/src/lib.rs).
So nothing stored can be read while a payout is planned. A claim therefore carries the amount and the time the caller wants; the plan pays exactly that amount out of the escrow, and apply checks it against what the stored schedule has vested by that time. A claim for more than has vested panics in apply, which fails the whole transaction, payout included: a claim for 601 of the 600 was refused on testnet, baacfe81, and the escrow read 600 before and after. The same pattern covers every check against stored state: the signer's id travels in the effect and apply compares it with the beneficiary, the cancel authority, the milestone authority or the creator, and the holding and refund account named by the transaction with the ones fixed at creation.
3. Time comes from the chain (F1)
A zkVM guest has no clock, and a timestamp passed as an argument is a number the caller chooses. On v0.3 a plan cannot read a clock either, but it can bound the block timestamp at which the transaction is valid, and the sequencer refuses a transaction outside its window, public and private alike.
claim(amount, at)sets[at, ∞): a beneficiary can only claim against a time that has already happened. A claim dated after the chain's clock was refused on testnet,c010024b, and the explorer page of the private claim,268e4463, shows its Timestamp Validity Window,[t, ∞).cancel(at, refund)sets[at, at + 2 min): a cancel authority cannot back-date a cancellation by more than two minutes, so it cannot keep what has vested since. The refund must equaltotal − vested(at)exactly or apply refuses it (7020); the CLI computes it from the same terms.
Neither needs a clock account, so the v0.2.4 owner-and-address checks on the clock, the 50-block clock for private claims and the drift refusal that came with it are gone. Schedule times are milliseconds since the epoch, the block timestamp's unit, and each claim and cancellation records the time it named (last_seen, cancelled_at), which is how the amounts above are checked against the chain rather than against us.
The vested amount is recomputed on every claim from the terms and that time; no "currently vested" field is stored to go stale. Cliff + linear pays nothing before the cliff, everything accrued since the start at the cliff (the RFP's lump sum) and linearly to the end. Division rounds down and the final step pays exactly the remainder, so the vested amounts always sum to the total with nothing left unaccounted. tests/vesting.rs checks, over exhaustive sweeps, that the sum of any sequence of claims equals the total, and the program calls the same src/vesting.rs code, unchanged by the port.
4. Custody (F1)
The escrow is the holding PDA's native or token shard. Nobody holds a key for it, and the only way to debit it is a chained call carrying its PDA seed, which only this program can attach:
- Native balance: the payout is
native_token::custody_transfer(holding, seed, to, amount), the runtime's own transfer out of a PDA. - A token-program token: the payout is a chained
Transferinto the token program with the holding's seed attached. The token program is addressed by its account id, derived from its builtin name rather than taken from anything the caller passes, and it checks the token definition itself.
Creation is the mirror image: a chained transfer of exactly the total from the creator, who signs, into the holding. The funded and claimed token escrow in the table above is this path working on the public testnet, so the escrow does not wait on LP-0013's transfer authorities. M0 re-checks whether a finished LP-0013 changes the best design for tokens whose holdings need an authority other than a PDA seed, and reports it either way.
5. Claim (F1, F2, R1, P1, U7)
claim is one transaction: apply checks the amount against what has vested at the time named, records claimed and last_seen, and the chained payout moves the tokens. The write and the payment land together or not at all, so a refused claim consumes nothing and can be retried (R1), and a claim never needs a second transaction (P1). The beneficiary's key signs; the destination is a separate account the beneficiary names, which is what makes the private path possible.
Every refusal is a guest panic whose message opens with a stable code (programs/vesting/core/src/errors.rs): nothing claimable (7003), not the beneficiary (7004), not the authority (7015), wrong asset (7016), wrong refund account (7017), wrong refund amount (7020), already exists (7019), among 19. On v0.3 a refused public transaction is still included and charged, so before sending, the CLI checks the two refusals a user can hit by mistake. A claim may not ask for more than has vested at the time it names, and a cancel refund must be exactly the unvested part. Both are computed from the decoded schedule with the same core crate the guest runs. The SDK (M2) will check every instruction before sending and turn the code it would get into the actionable message U7 asks for, with the next unlock time computed from the same terms.
6. Privacy: exactly the RFP's model, and its stated limit (F2, Pr1–Pr3, U4, U5)
A private claim is a privacy-preserving transaction in which the schedule and its holding are public effects and the destination is a shielded account. On the explorer it reads Privacy-Preserving Transaction: the claim and its schedule are observable, the account the tokens went to is not, and the creator, who is not a party to the claim, learns nothing the chain does not show. The amount is recorded on the public schedule (claimed), and the runs checked both sides: the schedule recorded 250 on testnet (5,000 for the token claim), and the beneficiary's wallet decrypted the same amounts into its shielded accounts. That is the RFP's model, including its limit: the beneficiary address is public from creation, so the guarantee covers where the tokens go and everything after, not who they vested to.
On v0.3 the public effects of a private transaction are deferred and applied at settlement, so a proof that takes minutes no longer races the state it was built against, and the claim's window is just [at, ∞). Private transactions carry no fee. What the private path still needs: the shielded destination must be initialised before its first credit, and the owner's wallet finds the credit only by scanning the claim's block with its viewing key. Before submitting, the SDK (M2) will check that the destination is initialised and refuse a destination that is not shielded when the private path was asked for (Pr3); the mini-app will show the disclosure the RFP specifies before every private claim (U5), and the claimable amount and fee estimate before every claim (U4). A private claim carries no fee on v0.3, so on the private path the mini-app checks that the account signing the claim is initialised to author it and shows a named error if not. On the public path it checks that the fee payer's LGO balance covers the fee.
7. Cancellation, transfer, milestones, authorities (F3, F4, R2, R4)
Cancelable is the default: the program takes the flag explicitly, and the SDK, the CLI and the mini-app set it unless the creator opts out (F3). Cancellation splits three ways from one vested_at call: claimed (gone), vested but unclaimed (still the beneficiary's), unvested (returned). The host tests sweep every cancellation instant asserting the three sum to the total. The unvested part goes to the refund account fixed at creation, so whoever holds the cancel authority cannot redirect it. The SDK and the mini-app will default the refund account to the creator's own account, as the RFP specifies (the CLI asks for it explicitly today), and a creator may name another account, such as a treasury multisig. A cancel naming another account than the one fixed at creation was refused on testnet, 10ec0633. Cancellation is one transaction: the state write and the refund land together or not at all (R2).
Transfer is read-only on the transferability flag, which is set at creation and written by nothing afterwards (F4); only the current beneficiary may move the position, since a creator who could would be able to redirect vested pay to themselves. Milestones are a bitmap: signalling a set bit is refused, so idempotence is structural rather than a check to remember (R4), and a cancelled schedule refuses further signals because its unvested part has already gone home.
Soft requirements, both implemented and on chain. A cancellation authority and a milestone authority, each defaulting to the creator. The second matters most where the RFP says it does, creator collateral vesting out of a launch, where a creator approving their own milestones is the attack. Nominating a threshold multisig as the milestone authority is the buyer-protection configuration.
8. Events (F6)
Every public instruction emits one event through v0.3's Plan::event: ScheduleCreated, BatchCreated, Claimed, Cancelled, MadeNonCancelable, BeneficiaryTransferred, MilestoneSignalled, each a borsh body behind an 8-byte selector, documented in the IDL and covered by its drift check. The executor tests assert the selector the committed binary emits. Two limits, stated: v0.3 carries events on public transactions only, so a private claim emits none and the public schedule's claimed field is its record; and the testnet's sequencer RPC has no event method (getEvents and getLogs answer Method not found), so reading the testnet events back through the indexer is M1 work.
9. Batch creation (F5)
create_schedule_batch sets up one schedule per beneficiary in one transaction, on shared terms, over one holding filled by one transfer, and each schedule keeps its own total, claimed amount and cancellation: a claim or a cancel moves only that schedule's share, which the testnet run checked (the batch holding lost exactly schedule 0's claim and schedule 1's refund). Schedule i of batch B has the id derived from the seeds [B, i] (i as a 32-byte word, little-endian in its first four bytes) and lives at the PDA of that id, verified by the plan before anything is written, so a batch schedule is an ordinary schedule to every reader.
v0.3 meters a whole transaction against a declared execution gas of at most 10,000,000 (gas is cycles one for one). 450 schedules fit one creation transaction and 451 do not: 450 uses 9,980,185 cycles, about 22,100 per schedule, found by bisection in the executor (executor-tests/CYCLES.md) and confirmed on the public testnet: 450 applied, 8d553905, 451 refused, e8030e3a. The ceiling is set by computation, not by the size of the transaction. M1 adds per-recipient amounts and detach: a shared holding serialises its batch's private claims (see Risks), and detach lets one beneficiary move their share into a holding of their own.
10. SDK, CLI, mini-app (U1–U3, U6)
The CLI exists on v0.3: antumbra-vesting covers deploy, create, batch, claim (public, or private with the proof built locally), cancel, make-non-cancelable, transfer, signal and show (the decoded schedule and what is claimable now), and it names a fee payer and checks a claim's amount and a cancel's refund against the schedule before sending. Its types, seeds and checks come from the same core crate the guest runs, antumbra-vesting-core.
The SDK will be a Rust crate over that core crate, with a C ABI for the Logos Core module, covering create, query claimable, claim (public and private), cancel, signal, transfer and convert; the mini-app will sit on it as well. M0 fixes the SDK's interface, so M3 builds the mini-app against it while M2 implements it, and integrates as M2 lands.
The mini-app exists as a Basecamp module, built the way the catalog builds modules (mkLogosQmlModule from logos-module-builder 0.3.1, nix build .#lgx-portable) for Basecamp 0.3.0 on linux-amd64, darwin-arm64 and windows-x86_64 in CI, merged into one .lgx. It is a ui_qml module, and CI also loads its view at three widths and fails on any warning. It reads the public v0.3 testnet by default and shows the recipient view: claimable now, total, vested and claimed, the vesting curve or milestone bars against the chain clock, what unlocks next, the escrow's actual balance against what it still owes, every account with an explorer link, the schedule's transactions, and the claim command for either path (app/README.md). M3 adds the claim action with both paths, the creator view (create with every parameter, list, cancel, signal) and the confirmation and disclosure screens. Listing needs no indexer: M1 records each schedule id in a per-creator and a per-beneficiary index PDA, so either view reads its own list directly.
11. Measurement (P2)
Measured per whole transaction by running the committed binary through lee::V03State, the state machine a v0.3 sequencer settles a public transaction with: the plan, one apply per effect and every chained call, metered together against the 10M gas cap, on LEZ v0.3.0 (db66590a) (executor-tests/CYCLES.md, re-measured in CI and failing on drift):
| operation | cycles (= gas) | share of the 10M cap |
|---|---|---|
| create schedule (native, funded) | 40,152 | 0.40% |
| create schedule (token, funded) | 78,428 | 0.78% |
| claim (native) | 49,024 | 0.49% |
| claim (token) | 87,913 | 0.88% |
| cancel (native) | 50,456 | 0.50% |
| cancel (token) | 89,332 | 0.89% |
| signal milestone | 29,589 | 0.30% |
| transfer beneficiary | 30,982 | 0.31% |
| make non-cancelable | 29,549 | 0.30% |
| create a batch of 8 schedules | 194,531 | 1.95% |
A creation includes the transfer into the escrow. The arithmetic inside those figures is measured separately (zkvm/CYCLES.md) at about 8,800 cycles. M4 adds the private path, whose proving cost is dominated by the privacy circuit rather than by this program.
12. Testing and evidence (S1–S6)
antumbra-lez, MIT OR Apache-2.0:
- 51 host tests for the library, in release mode (the profile with overflow checks on), 15 of them the vesting rules.
- 16 core tests of the v0.3 plan and apply on the host (
programs/vesting/core/src/tests.rs). - 16 executor tests that run the committed v0.3 binary through
lee::V03Statewith signed transactions, timestamp windows, the real token program and the 10M gas cap, named by what they evidence (the cliff lump, the three-way cancellation, no back-dating past the window, milestones once, holder-only transfer, each batch schedule moving only its share) (executor-tests/tests/program.rs). - Kani model checking of the vesting arithmetic the program runs (
src/vesting/proofs.rs, 11 harnesses): for every u64 timestamp, nothing vests past the total, everything vests by the end with no rounding dust, nothing before the cliff, a claim pays exactly the vested unpaid amount and a refused one records nothing, a cancellation splits the total exactly and freezes vesting, a milestone cannot be signalled twice, andmul_div_floorreturns exactly ⌊a·b/d⌋. Totals, durations and those operands are bounded to 8 bits so the solver closes each harness; the file says so, and large values stay covered by the arbitrary-precision oracle vectors.
CI (.github/workflows/ci.yml) runs all of it, re-measures both cycle tables, rebuilds the guest and compares it with the committed artifact, checks the hand-written IDL, builds the Basecamp module for three platforms, loads its view at three widths in a Qt host and fails on any warning, and checks every documentation link. The full lifecycle has been run against a local v0.3.0 standalone sequencer (evidence/v03/, 27 applied, 15 refused, 66 checks) and against the public testnet (above); running that suite against a standalone sequencer on every push is M4, with no skip path, since a job that can finish green without running its step reports nothing, and with the traceability table below generated from test names. The testnet is reset or upgraded from time to time; scripts/e2e-v03.sh re-drives every call cited here in one command, and verify-onchain.sh re-checks it from the manifest (last run 2026-10-16: 57 checks pass, 0 fail; section 1, 9/0).
13. Fees
- Who pays: the schedule creator, never the beneficiary.
- When: at creation only, since a per-claim fee would penalise exactly the beneficiary who claims in small amounts to avoid signalling an unlock.
- Rate: zero at launch, with a governance-activatable switch and a 1% cap that M1 writes into the program, refusing a higher rate rather than clamping it. The program deployed today charges no protocol fee; the arithmetic and its tests are in
tests/fees.rs. - Where: a treasury set through the standard admin authority (RFP-001).
The fee is charged on top of the escrowed amount, never deducted from it, so the escrow always equals what the beneficiary was promised (src/fees.rs). A broker fee is out of scope. This is separate from the network fee, which on v0.3 every public transaction pays in LGO to the sequencer.
14. Risks
| Risk | What we do |
|---|---|
| Network fees in LGO | Every public transaction names an LGO fee payer, and every account that signs must be able to pay for its own transaction. There is no LEZ faucet on v0.3: LGO comes from Bedrock through a channel deposit, which docs/deploy-v03.md documents step by step. The SDK and mini-app will show the fee before every public claim (U4). Private claims carry no fee |
| A refused transaction is charged its declared gas | On v0.3 a refusal is included and charged in full for the gas it declared. The CLI checks a claim's amount and a cancel's refund against the schedule before sending, and declares a gas limit sized to the instruction (300,000 by default, 400,000 for a batch of 8) rather than the 10M cap. The SDK will check every instruction before sending |
| Block time | The testnet produces a block roughly every 20 to 40 seconds, so a claim or cancel takes that long to settle and a private claim adds its proving time. The cancel window is two minutes, several blocks wide, and the SDK will report inclusion by reading the schedule's state, not the client's verdict |
| Builtin programs addressed by id | v0.3 addresses the native transfer and token programs by account ids derived from their builtin names, not by ImageID. The program derives them itself rather than taking them from the caller. Rebuilding against a new LEZ release runs the executor tests against that release's real token program, so a change shows up there before any deploy |
| No SPEL release for v0.3 | The IDL is hand-maintained in SPEL's shape and a CI step fails on any drift from the program's own types. When SPEL supports v0.3, the IDL is regenerated and the drift check compares the two |
| The testnet is reset or upgraded | It happened on 2026-09-30 (upgrade to v0.3) and on 2026-10-12 (reset to sequencer v0.3.1), after which the program was redeployed at the same id and re-driven within two days. Every citation is re-driven by one script (e2e-v03.sh) and re-checked from its manifest by verify-onchain.sh, which shows a reset in one run |
| Batch size is set by the platform | Measured exactly, published with its executor test and two testnet transactions; the SDK (M2) will split a larger batch |
| Private claims from one batch contend | A shared holding is a public effect of every private claim from its batch, so two claims settling against the same balance can conflict: the later one is refused whole and rebuilt, losing nothing (R1). The SDK retries it; M1 adds detach, which moves one schedule's share into its own holding for a beneficiary who wants an uncontended private path |
| Private-path usability | Destination initialisation will be checked by the SDK (M2) before submission, with a named error |
15. Requirement traceability
Every numbered hard requirement, how it is met, what proves it, and which milestone pays for it. "On chain" means the public testnet v0.3; "local" means tests or a local sequencer only.
| # | Requirement (abridged) | How it is met | Evidence | Milestone |
|---|---|---|---|---|
| F1 | Implement a vesting program on LEZ that locks tokens in escrow against a … | Escrowed schedules, three shapes, time bounded by the block timestamp | See F1a–F1c; the token escrow funded, claimed and cancelled on chain (token-v031.tsv) |
M1 |
| F1a | ↳ sub-case of F1 | cliff + linear: nothing before the cliff, the accrued lump at it, linear after | On chain: a 600 cliff schedule, the claim before the cliff refused with the escrow untouched, the claim after it paid 283 including the 280 lump (cliff.tsv); executor test nothing_vests_before_the_cliff_then_the_accrual_unlocks_at_once |
M1 |
| F1b | ↳ sub-case of F1 | fully linear: accrues from the start | On chain: paid exactly ⌊total × elapsed ÷ duration⌋ at the time named, 1b5d3a16 |
M1 |
| F1c | ↳ sub-case of F1 | milestone: each signal unlocks a fixed tranche | On chain: two signals, two of four tranches claimed f5516c7f |
M1 |
| F2 | The schedule creator specifies the beneficiary at schedule creation. … | Beneficiary fixed at creation; the beneficiary names the destination at claim; the creator is not a party | On chain: a native claim into a shielded account 268e4463; the token one on chain, 6c0f7ea004bbebc9… (block 28,420, token-v031.tsv) |
M2, M3 |
| F3 | The schedule creator sets whether the schedule is cancelable at creation … | Three-way split; refund account fixed at creation; one-way conversion | On chain: native b0bc8b50 cancellation, the vested part claimed afterwards (the token one in token-v031.tsv); a cancel after non-cancelable refused 7f091c77 |
M1 |
| F4 | The schedule creator can configure whether the vesting position … | Flag fixed at creation, never written; only the holder moves the position | On chain: holder's transfer 791dac40, creator's refused 13deee98 |
M1 |
| F5 | Multi-recipient batch creation: a creator can set up vesting schedules … | One instruction, N schedules over one shared holding, one funding transfer | On chain: batch of 8 c3483b09, one claimed, one cancelled; maximum 450 8d553905, 451 refused |
M1 |
| F6 | The program emits a log event (using the LEZ event/log mechanism; see … | One event per public transition through Plan::event, schema in the IDL |
Local: executor tests assert the emitted selectors; reading them back from the testnet indexer is M1; private claims emit none on v0.3 | M1 |
| U1 | Provide an SDK that can be used to build Logos modules interacting with … | SDK over the full lifecycle, public and private claim | To do: the shared core crate exists; SDK integration tests in M2 | M2 |
| U2 | Provide a Logos mini-app GUI with local build instructions, downloadable … | Basecamp module loadable from a git repo, both views | Local/CI: built for Basecamp 0.3.0 on three platforms in CI; recipient view reads the public v0.3 testnet | M3 |
| U2a | ↳ sub-case of U2 | recipient view: position, total locked, claimable now, next unlock, claim | Read-only view live against a v0.3 sequencer; claim action in M3 | M3 |
| U2b | ↳ sub-case of U2 | creator view: create, list, cancel, signal; listing from per-creator index PDAs | To do: UI test per action | M1, M3 |
| U3 | Provide a CLI that covers core functionality of the program. The CLI may … | CLI covering every instruction and show |
On chain: every transaction in this document was sent by it; moved onto the SDK in M2 | M2 |
| U4 | The mini-app must display a pre-claim confirmation summary: claimable … | Pre-claim summary with amount and fee; on the private path (no fee on v0.3), a check that the signing account is initialised to author the claim; on the public path, the fee payer's balance | To do: UI test, including the insufficient-balance error | M3 |
| U5 | When using the private account path, the mini-app must display a privacy … | The RFP's disclosure before every private claim | To do: test asserts it precedes each private claim | M3 |
| U6 | Provide an IDL for the vesting program using the SPEL framework. | SPEL-shaped IDL, hand-maintained until SPEL supports v0.3, drift-checked in CI | Committed: idl/antumbra_vesting.v03.idl.json |
M1 |
| U7 | Failed claims must return clear, actionable error messages, including the … | Stable error codes; next unlock computed from the terms | Partial: 19 codes, refusals asserted by code in the executor tests; next-unlock message in M2 | M2 |
| R1 | A claim is atomic: a failed or rejected claim does not consume vested … | Record and payment in one transaction | On chain: over-claim refused baacfe81 with the escrow unchanged, then the exact claim paid |
M1 |
| R2 | A schedule cancellation is atomic: partial failure leaves the schedule in … | State write and refund in one transaction | On chain: wrong refund refused, escrow unchanged, then the cancel applied b0bc8b50; injected-failure test in M4 |
M1 |
| R3 | Concurrent claims against independent schedules must not corrupt shared … | Per-schedule accounting; a single schedule's own holding is disjoint from every other's, a batch's is shared and serialised by the sequencer | On chain: a batch claim and cancel moved only their shares 9bf3e3b3; interleaved claims in the sequencer suite in M4 |
M4 |
| R4 | Milestone signalling is idempotent per milestone index: signalling the … | Bitmap; a set bit is refused | Local: executor test milestones_unlock_in_equal_tranches_signalled_once_by_their_authority; on testnet the repeat signal never reached a block (its hash does not resolve) |
M1 |
| P1 | A single claim completes within one LEZ transaction. | One transaction per claim | On chain: every claim above | M1 |
| P2 | Document the compute unit (CU) cost of each operation: create schedule … | Whole-transaction gas per operation, LEZ v0.3.0 stated | Measured: §11, re-checked in CI | M4 |
| S1 | The vesting program is deployed and tested on LEZ testnet 0.2. | Deployed and tested on testnet 0.2 (v0.2.4) | Delivered on v0.2.4, hashes gone with the 2026-09-30 upgrade; see History | none (delivered on v0.2.4, not priced) |
| S2 | End-to-end integration tests run against a LEZ sequencer (standalone … | e2e against a standalone sequencer in CI, green on default | Local run today (evidence/v03/); in CI with no skip path in M4 |
M4 |
| S3 | Every hard requirement in Functionality, Usability, Reliability, and … | A test per hard requirement, named for it | 16 executor tests on the committed binary today; the table generated from test names in M4 | M4 |
| S4 | A README documents end-to-end usage: deployment steps, program addresses … | README: deploy, addresses, create/claim/cancel by CLI and mini-app | Partial: deploy runbook and addresses published; CLI and mini-app walkthrough in M4 | M4 |
| S5 | The program is updated and verified on LEZ testnet 0.3. | Updated and verified on testnet 0.3, its own milestone | On chain: header FCrja8g2…, 57/0 from verify-onchain.sh (section 1 rerun alone: 9/0) |
M6 |
| S6 | The program is deployed to LEZ mainnet. | Deployed to mainnet, its own milestone | To do: same evidence bundle | M7 |
| Pr1 | The mini-app and SDK must support claiming to both public and private … | Both paths in SDK and mini-app | On chain through the CLI, both paths; SDK and mini-app in M2, M3 | M2, M3 |
| Pr2 | When using the private account path, the mini-app must display the … | Disclosure before every private claim | To do: asserted in the UI test | M3 |
| Pr3 | When using the private account path, the SDK must validate that the claim … | Destination checked to be shielded before submission | To do: negative test, a public account offered to the private path | M2 |
That is all 28 numbered hard requirements.
16. History: LEZ v0.2.4
The program first ran the same lifecycle on the public testnet at LEZ v0.2.4 (ImageID 7763458b…) until the 2026-09-30 upgrade to v0.3; those hashes and the checker they were verified with are in the historical section of DEPLOYMENTS.md.
Milestones, Payout and Timeline
Eight milestones. The RFP estimates 10–12 weeks. Part of the work is already done and on the public testnet, so each milestone below says what is delivered today and what remains; the amounts are unchanged, and each is paid on acceptance of the whole milestone. The RFP requires separate milestones for testnet 0.2, testnet 0.3 and mainnet: the program ran on testnet 0.2 (v0.2.4) before the chain was upgraded, which is recorded under History; the chain now runs 0.3, so M1 completes the program there, M6 is the 0.3 milestone and M7 is mainnet, gated on its date.
The team is two people on two tracks. Remaining effort: about 6.5 weeks on the critical path M1 → M3 → M4 → M5, with M2 (SDK) on the second track alongside M3. Every milestone is accepted on evidence anyone can re-run, stated with it.
| # | Deliverable | Effort (remaining) | Status today | Payout |
|---|---|---|---|---|
| M0 | Specification and platform re-check | 1 wk (0) | delivered | $2,000 |
| M1 | Complete program on testnet 0.3 | 3 wk (1.5) | partial | $9,000 |
| M2 | SDK and CLI | 2.5 wk (1.5) | partial: CLI delivered | $8,000 |
| M3 | Basecamp mini-app | 3 wk (2) | partial | $8,000 |
| M4 | End-to-end CI, measurement, documentation | 1.5 wk (1) | partial | $4,000 |
| M5 | External audit | 2 wk (2) | to do | $0 (auditor's fee funded separately) |
| M6 | Testnet 0.3 | 0.5 wk (0) | delivered | $2,000 |
| M7 | Mainnet | 0.5 wk (0.5) | to do | $2,000 |
| Eight milestones, paid on acceptance | $35,000 |
M0: Specification and platform re-check. 1 week, $2,000
Account model, event schema, rounding per operation and the SDK surface written down; LP-0013, the event API and the testnet version re-checked, and any change to the design reported. Accepted when the specification is merged and the re-check is published with its findings.
Status today: delivered. The v0.3 re-check is docs/v03-port-plan.md (what v0.3 changes for each part of the program, and what it does not); the account model, instructions, seeds, events and refusal codes are specified in idl/antumbra_vesting.v03.idl.json and the documented core crate, and the builtin addressing question is settled (builtins by their derived account ids). We propose M0 be accepted on these.
M1: Complete program on testnet 0.3. 3 weeks, $9,000
Exact funding, the time-bounded claim, per-recipient amounts, the token batch and detach, the per-creator and per-beneficiary index PDAs, the event records on all five transitions, the fee switch at zero with its cap and its treasury set through the RFP-001 admin authority, hardening from M0, and the whole lifecycle re-driven on testnet 0.3. Accepted when verify-onchain.sh passes against the new deployment and every F-requirement and R1, R2 and R4 has an on-chain transaction or a test named in the traceability table.
Status today: partial. Delivered on testnet: exact funding (creation and funding are one transaction), claims bounded by the block timestamp, cancellation with an exact refund and a two-minute window, milestones, transfer, non-cancelable, both nominated authorities, native and token escrow, private native and token claims, a cliff schedule, batches up to 450, and events emitted by every public instruction. Remaining: per-recipient amounts, a token batch on chain, detach, the index PDAs, the fee switch, and events read back from the testnet indexer.
M2: SDK and CLI. 2.5 weeks, $8,000
The SDK over the full lifecycle, public and private claim, shielded-destination validation and fee and balance checks, the wallet scan from the claim's block, splitting a batch larger than the ceiling, stable errors with the next unlock; the CLI on top. Accepted when the SDK and CLI test suites pass in CI, including a private claim and the negative Pr3 test.
Status today: partial, CLI delivered. The v0.3 CLI covers every instruction and sent every transaction on testnet, with claim amounts and cancel refunds checked before sending. The SDK crate, its C ABI, Pr3 validation, next-unlock messages and the CLI's test suite in CI remain; the types, seeds and checks it will expose already exist in antumbra-vesting-core.
M3: Basecamp mini-app. 3 weeks, $8,000
Recipient and creator views, the claim action on both paths, the pre-claim summary and the privacy disclosure, loadable from the git repository with build instructions and assets. Accepted when the module loads in Basecamp from the repository and a private claim completes from the UI.
Status today: partial. The module (0.4.2, ui_qml) builds for Basecamp 0.3.0 on linux-amd64, darwin-arm64 and windows-x86_64 in CI and reads schedules, batches and escrows from the public v0.3 testnet (recipient view, read-only). Remaining: the claim action on both paths, the creator view, and the summary and disclosure screens.
M4: End-to-end CI, measurement, documentation. 1.5 weeks, $4,000
The standalone-sequencer suite in CI with no skip path, the per-operation cost table with the testnet version and the private-claim proving cost, traceability generated from tests, the README walkthrough. Accepted when CI is green on the default branch with every step executed.
Status today: partial. Delivered: the v0.3 cost table re-measured in CI, the reproducible-build job, Kani, the IDL drift check, the module jobs, the docs checks and the deploy runbook. Remaining: the standalone-sequencer suite in CI (it runs locally today), the private-claim proving cost, generated traceability, and the CLI and mini-app walkthrough in the README. M4 also adds the module and CLI guides, the app's design files, and a privacy properties document drawn from §6.
M5: External audit. 2 weeks, $0
Scoping and coordination, remediation of every finding, re-verification. The external auditor's fee is funded separately, with the firm agreed with Logos; our scoping, the audit-ready bundle and the remediation of every finding are at no charge. Accepted when the report is published with every finding resolved or explained.
Status today: to do.
M6: Testnet 0.3. 0.5 weeks, $2,000
Rebuild against 0.3, redeploy, re-drive the lifecycle, restate the cost table, publish the evidence. Accepted when the replay and verify-onchain.sh pass against 0.3.
Status today: delivered. Header FCrja8g2ZKvxZwNZchdppKWQCDxPNUHxnEMidrmqrt6X, the lifecycle re-driven on 2026-10-14 after the testnet reset, the cost table restated for v0.3.0, and verify-onchain.sh --manifest evidence/v03/testnet.tsv at 57 checks, 0 failures. We propose M6 be accepted on this existing evidence; anything M1 adds is re-driven on the same chain and re-checked the same way.
Testnet 0.4. If delivery moves to LEZ testnet 0.4 (as proposed in #214), the program is ported, redeployed and re-driven there within days of its launch, verify-onchain.sh re-run against it, inside this milestone and at the same price; the one-script redeploy is what the 2026-10-12 reset already exercised.
M7: Mainnet. 0.5 weeks, $2,000
Reproducible build from a freeze commit, mainnet deployment and verification, evidence published. Accepted when the ImageID the mainnet header stores matches the committed artifact's.
Status today: to do, gated on the mainnet date.
Total Requested Budget (USD)
$35,000
Relevant Experience
λPrize LP-0005, Private Balance Attestation: won. Solution PR logos-co/lambda-prize#64 merged on 2026-08-02 by the Logos team; the solution file is in the official repository at https://github.com/logos-co/lambda-prize/blob/master/solutions/LP-0005.md; the prize is closed. A SPEL program on LEZ with RISC0 threshold proofs over shielded accounts, verified on chain and off chain over Logos Messaging. Code: https://github.com/edenbd1/lp-0005-private-token-balance-attestation
This proposal's program: https://github.com/edenbd1/antumbra-lez, the vesting program and its v0.3 port, its host library, the CLI, the Basecamp module and the scripts that produced every transaction above.
How this maps to the recommended team profile.
| The RFP asks for | Where it already exists |
|---|---|
| Solana/SVM program development | LEZ's account model is SVM-shaped (PDAs, IDLs, program state) and SPEL is its Anchor; LP-0005 and the v0.2.4 vesting program are written against it, and this program was then ported to v0.3's plan/apply model by hand |
| Token program and account model | The token escrow above: a holding's token shard, funded by a chained transfer and paid out under this program's PDA seed |
| DeFi contract security and testing | Checks against stored state in apply, a claim time the chain bounds, a refund account and amount that cannot be redirected or inflated, the cancellation split proven to sum at every instant |
| Front-end for wallet/DeFi applications | The Basecamp module above; Nada Ahmadi's Umbra front end and full-stack work at XRPL Commons |
| Privacy interaction patterns | Claims into shielded accounts on the v0.3 testnet; LP-0005, private balance attestation over shielded accounts |
Post-Delivery Plan
Maintained by the two of us for twelve months from final delivery:
- Security issues acknowledged within 48 hours, with a fix or a documented mitigation; a vesting escrow holds other people's tokens, so this one is not best-effort.
- Platform tracking: the program kept building and deployed against current testnet across LEZ and SPEL releases, and re-driven after every reset with the replay script, as it was after the v0.3 upgrade.
- Public issues answered on GitHub.
- Handover: MIT OR Apache-2.0, no private build step, and a deployment anyone can verify from the committed artifact, so the ecosystem can take it over without us.
We have not priced a servicing line because the RFP delivers software rather than an operated service; if Logos wants a defined operating commitment, we will scope one.
Permissions and Consent
- I/We confirm Logos may contact me using the primary contact information provided above for follow-ups and next steps.
- I/We consent to Logos using information from this proposal publicly such as blogs case studies social posts or analytical reporting. Redactions can be requested at any time.
Program Requirements
- I/We have read and agree to the Logos RFP Terms and Conditions and I understand that no Grant is awarded and no right to payment arises unless a Grant Agreement is executed.
- I/We understand that RFP specifications are proposals rather than instructions, that Logos makes no representations as to their legal or regulatory treatment, and that we are responsible for assessing what we build, deploy or operate and for complying with the laws that apply to us.
- I/We understand this project must be open-sourced under the MIT and Apache 2.0 Licenses unless explicitly approved otherwise.
- I/We are prepared to deliver milestone-based outcomes.

Activity
edenbd1 commentedon Aug 23, 2026
edenbd1 commentedon Sep 26, 2026
Update: redeployed after the testnet reset
The public LEZ testnet was reset on 2026-09-08, so every transaction cited here before that date stopped resolving, like every pre-reset transaction on the network.
antumbra_vestingwas redeployed on 2026-09-26 from the unchanged artifact, and the deploy transaction came back with the same hash it had before,9b35fc31…d1691ee2(block 25898). A LEZ deploy hash isSHA256(len ‖ bytecode), so the same file gives the same hash and the same ImageID,4c6e62a5….The whole lifecycle was then driven again against it: a paying claim, cancellation at the midpoint with the vested half still claimable, two-tranche milestones ending at exactly zero, transfer by the holder only, the one-way non-cancelable conversion, and a new accrual run that pays 48 (floored) and then exactly the 49 left. 29 transactions: 22 landed and 7 were refused where the RFP requires it. The proposal body now cites them.
scripts/replay-vesting.shre-drives the full lifecycle in one command. The film above was recorded before the reset; every call it shows has been driven again here.edenbd1 commentedon Sep 26, 2026
Update: private claims, real-token escrow and the chain's clock, all on testnet
The proposal body has been revised. What changed, briefly, each one checkable:
claimandcancelnow check the clock account's owner and address, and a caller-supplied account in its place is refused.Every transaction cited is re-checked by one command:
The full call-by-call record is
evidence/VESTING.md.edenbd1 commentedon Sep 27, 2026
Update: batch creation (F5) on chain, and its ceiling measured
The program now creates schedules for many beneficiaries in one transaction and funds them with one transfer. LEZ refuses a transaction in which two chained calls debit the same payer, so a batch shares one holding; each schedule keeps its own total, claims and cancellation.
On testnet: a batch of eight created and funded in one operation each, one of its schedules claiming exactly its own accrual and another cancelled for exactly its own unvested part, with the rest of the escrow untouched. The ceiling, found by doubling the batch: 256 schedules in one creation transaction; 512 did not land. The exact maximum has since been measured on the deployed binary through the sequencer's own execution path: 299 schedules fit, 300 exceed the 2^25-cycle cap (
f5_the_largest_batch_…inexecutor-tests/tests/program.rs).The program was rebuilt for this (ImageID
7763458b…), and the whole lifecycle — private claims, token escrow, clock, cancellation, milestones, authorities — was re-driven against the new build. The proposal body cites the new transactions; every one of them, and every refusal, is onevidence/VESTING.mdand re-checked by./scripts/verify-onchain.sh --only vesting(69 checks). Thirty executor tests run the deployed binary itself.edenbd1 commentedon Sep 27, 2026
Demo film: the current deployment, in my own voice
antumbra-vesting-rfp-017-demo.mp4
3 min 50, narrated by me, subtitled. Everything on screen is the program as it is deployed now — ImageID
7763458b…, deploy transaction2b9e140b…at block 27115, which a clean rebuild of the repository reproduces byte for byte (checked on every push by thereproduceCI job):verify-onchain.shasking the public sequencer about every transaction and every refusalIt replaces the August film above, which showed a build from before the testnet reset.
Also on YouTube, in full resolution: https://youtu.be/CglzojWX_yc
edenbd1 commentedon Oct 2, 2026
Update: Antumbra Vesting now runs on the public LEZ testnet v0.3.
The program is ported to the v0.3 model and deployed as
FCrja8g2ZKvxZwNZchdppKWQCDxPNUHxnEMidrmqrt6X(ImageID72d5cdc0…, read back from the header's loader record; CI rebuilds the binary from source and gets the same ImageID).I drove the whole lifecycle on the testnet: linear and milestone schedules, cancellation with refund, transferable positions, non-cancelable and nominated cancel authority, token escrow, private claims of native balance and of a token into shielded accounts, a batch of 450 schedules in one transaction (451 is refused under the 10M gas cap), and the refusals the program must make. 27 transactions applied, 14 refused on chain as required.
scripts/verify-onchain.shre-reads all of it from the testnet: 66 checks, 0 failures.Every transaction is linked in DEPLOYMENTS.md, with the manifest and transcript in
evidence/v03/. Happy to walk through any of it.edenbd1 commentedon Oct 12, 2026
Update: budget reduced to $35,000, with the external audit funded separately.
We are taking the external audit out of our budget, so the auditor is contracted and paid by Logos directly, as a separate line. The requested budget drops from $50,000 to $35,000, and the scope we deliver is unchanged.
The audit itself is unchanged: we still name the firm, prepare the bundle, and fix what it finds before mainnet. The only difference is who pays the auditor. Remaining effort is still about 6.5 weeks on the critical path.
edenbd1 commentedon Oct 14, 2026
Update: redeployed after the testnet reset, and driven through its lifecycle again.
The public LEZ testnet was reset on 2026-10-12 and moved to sequencer v0.3.1, so the transactions of our 2026-10-02 run no longer resolve.
antumbra_vestingis redeployed at the same program id,FCrja8g2ZKvxZwNZchdppKWQCDxPNUHxnEMidrmqrt6X, ImageID72d5cdc0…, the committed binary that CI rebuilds from source byte for byte.evidence/v03/testnet.tsv). Linear claims paid to the unit, cancellation with an exact refund, milestones, holder-only transfer, one-way non-cancelable, a nominated authority, a batch of 8 over one escrow, a private claim into a shielded account, and the batch ceiling: 450 schedules in one transaction, 451 refused.curlandpython3:./scripts/verify-onchain.sh --manifest evidence/v03/testnet.tsv --rpc https://testnet.lez.logos.cogives57 checks pass, 0 fail(output).executor-tests.create-linearshowsnot-includedbecause an earlier attempt was the one the sequencer included, while that CLI kept only the last attempt's hash; the section's own checks need it applied. The CLI now records every attempt, and section 1 was rerun alone, clean:9 checks pass, 0 fail(testnet-section1.tsv).The testnet's block explorer was about 3,000 blocks behind the sequencer on 2026-10-14, so explorer links to these transactions resolve once it catches up; the verifier asks the sequencer directly. Everything is in
DEPLOYMENTS.md.edenbd1 commentedon Oct 17, 2026
Update: token escrow and the cliff now run on the public testnet.
The testnet's v0.3.1 sequencer has no builtin token program, so the token rows were shown on a local sequencer. They now run on the public testnet itself: LEZ v0.3.0's own
token.bin(rebuilt byte for byte) is loaded as an ordinary, immutable program at3cwJjDqD17a7k5zMaYM82sgai7WdRzWfLCtRGntZ6SBb. Next to it is a build ofantumbra_vestingthat pays out through that program, atB2ZDRw7fJCo5ucMNNZDVmAPpFVCcueSggXWZ1aWfgiMy; it is identical to the deployed program except for that one constant. Blocks 28,388 to 28,420:bf15cfe53277f52c4a73781220b318bcf0c49de947fea3fe733f6656b00807adad5c16cfae11e01e60f06adcd3771a0b8cf79153957fec39dcc395ae60798721117c427e6d771b551cf69af0f0774e8ed99da8b143ff4a0f8c2c8cde247352a492aab23c7cf3eea29608b71ff6aa32219c751eb599e012d40efedac38a1119b16c0f7ea004bbebc96db1d2fb9b7901cf5dd397f4e14618242f04784bd55465396 applied, 0 failures. Anyone can re-check the run with
./scripts/verify-onchain.sh --manifest evidence/v03/token-v031.tsv --rpc https://testnet.lez.logos.co. It passes 14 checks with 0 failures, including that the token program's header is immutable. Manifest and transcript:evidence/v03/token-v031.tsv.A cliff schedule is also on chain (2026-10-14,
evidence/v03/cliff.tsv, 7/0). It holds 600: a claim before the cliff was refused with the escrow untouched, and the claim after it paid 283, including the whole 280 lump.The proposal body is updated to match: F1, F1a, F2, F3, Pr1 and the M1 status now cite these runs. A capacity line was also added to the Team section. Prices and milestones are unchanged.