Skip to content

[PROPOSAL] RFP-017 — Hourglass: Privacy-Preserving Token Vesting #201

Description

@nodersteam

RFP ID

RFP-017 — Privacy-Preserving Token Vesting

Your Project Name

Hourglass

Team or Organization Name

NODERS

Primary Contact

office@noders.team

Team Members

NODERS is a Web3 engineering team. We build SDKs, wallets, explorers and on-chain programs across privacy ledgers and L1s: the Canton Go SDK (go-daml) and wallet library, the open-source Logos testnet explorer we run today, smart-account and monitoring tooling on Monad, and the RISC Zero prototypes behind our Logos proposals. We also operate validators, RPC and indexing for 60+ networks with a 24/7 on-call rotation (noders.team) — which is what lets us commit to running what we build.

  • Alexey Moskalev — lead: LEZ program design, delivery, operations. GitHub alexeymoskalev-devops. Status: full-time.
  • Ruslan Glaznyov — Rust / RISC Zero engineer: SDK, CLI, sequencer CI. GitHub RuslanGlaznyov. Status: full-time.
  • cryptobtcbuyer — Basecamp mini-app (QML / C++), Figma. GitHub cryptobtcbuyer. Status: full-time.

This proposal is filed from the NODERS organisation account (nodersteam).

Project Summary

Hourglass is production-ready, privacy-preserving token vesting for LEZ: cliff+linear, fully linear and milestone schedules; token-denominated escrow held in a program-controlled vault; claims paid — at the beneficiary's choice, at claim time — to a public holding or to a fresh private account the creator never learns; the universal cancellation invariant with one-way non-cancelable conversion; transferable positions; separate cancellation and milestone authorities; batch creation; a typed event schema; and the SDK, CLI and Basecamp mini-app with the pre-claim summary and privacy disclosure the RFP asks for.

The on-chain core is built and running on public testnet 0.2. Every on-chain hard requirement of RFP-017 is implemented and exercised end to end — on the public testnet, including a privacy-preserving claim; batch creation through the zkVM state machine; events through the documented seam (see Known gaps).

Evidence
Repository noders-team/lez-programsprograms/vesting, tag testnet-0.2-evidence-v4, on upstream logos-blockchain/lez-programs 6b53f3a (LEZ v0.2.4)
Testnet 0.2 deployment ImageID 952b031fecf9e357c76daca7c307e0debb4949d30d419b3c3a1db402a1fa46be, deploy tx 5feaa17a… (block 27324)
Lifecycle on chain 21 steps, 8 of the 9 instructions, every rule the RFP states including the rejections — docs/vesting/testnet-evidence.md
Private claim on chain tx 3a2828ab… (block 27362): 1,000 tokens from the escrow to a fresh private account in one privacy-preserving transaction
Tests 75 — 17 core · 41 handler · 17 zkVM state-machine; CI green on main
Cycle cost Claim 376,495 user cycles (524,288 padded, 1.6 % of the public-execution budget); batch of 10: 7 %
Re-verify scripts/vesting/verify-onchain.sh against the live sequencer — confirms all 21 lifecycle steps (inclusion and non-inclusion), the 8 final balances and the 6 private-claim properties: ALL GOOD
Technical Appendix docs/vesting/TECHNICAL-APPENDIX.md — mechanics, platform findings, test matrix, cycle table, reproduction commands

Two results matter most for this RFP.

Token-denominated escrow works today. The vault is a program-derived account, funded and paid out by chained Token Program transfers. Although RFP-017 lists LP-0013 as an open custody blocker, the deployed program needs no transfer-authority primitive: LP-0013 in fact landed upstream as a mint-authority model, and escrow of any SPEL token runs on testnet now (Appendix C, E.1).

The private claim runs on the public testnet. The registered public beneficiary signs the claim; the destination is a fresh private account that is not revealed to the creator or to public observers. The recorded transaction claimed 1,000 tokens in a single privacy-preserving transaction. Reproduction steps, the zkVM state-machine tests, the clock-binding design and the proof evidence are in Appendix A, B and D.

What the grant funds is the client surface (SDK, CLI, Basecamp mini-app), program hardening, runtime events, an external audit before mainnet, the testnet 0.3 migration and the mainnet release. Twelve months of maintenance and an operated indexer are included at no extra charge. The repository is public from the day of filing; a fresh schedule with a Logos-held beneficiary key can be provisioned on request so a reviewer can run the private claim end to end.

Technical Approach

Architecture. One schedule account and one escrow vault PDA per schedule. Nine instructions, in discriminant order: CreateSchedule, CreateScheduleBatch, Claim, ClaimTo, Cancel, MakeNonCancelable, SignalMilestone, TransferBeneficiary, ClaimToPrivate. The vault is funded at creation and paid out at claim and cancel through chained Token Program transfers authorised with pda_seeds — the pattern the upstream AMM uses for its own vaults; the runtime applies a call chain only if every call succeeds (LP-0015, delivered in the core runtime), which makes each claim and cancel one atomic transaction. Schedule state carries the parties, amounts, kind, flags, nominated authorities and an event seam. Account layout, PDA derivation and custody mechanics: Appendix C.

How the hard requirements are met.

  • Schedule types (F1). Cliff+linear with floor pro-rata (vested(end) == total exactly), fully linear, and milestone tranches unlocked in order by the milestone authority with idempotent signalling (R4). M1 adds an optional cliff_amount. Math and overflow argument: Appendix C.6.
  • Public and private claims (F2, Privacy 1). Every claim is signed by the registered beneficiary holding, so nobody can force a public claim and defeat a later private one. Claim pays that holding; ClaimTo pays any public destination; ClaimToPrivate pays a fresh private account created by the claim itself. Observers see the schedule, the amount and the signing holding; the destination appears on chain only as a commitment, and the creator never learns it. Because a privacy-preserving transaction cannot read the per-block clock without expiring in one block, ClaimToPrivate carries an as_of timestamp and pins it as the lower bound of the runtime's validity window, so it can never pay more than was vested at inclusion. Execution model and clock design: Appendix B and D.
  • Cancellation (F3). Cancel freezes accrual, returns the unvested remainder to the creator in the same transaction, and leaves vested tokens claimable; MakeNonCancelable is one-way. Both are gated by the nominated cancellation authority (default: the creator holding; a multisig or DAO holding works). SDK, CLI and mini-app default new schedules to cancelable, as the RFP specifies.
  • Transferability (F4). Fixed at creation; the current beneficiary reassigns the position, after which only the new holding can claim (the old holding's claim is rejected — on chain in the evidence run).
  • Batch (F5). 10 schedules per transaction. The binding limit is the runtime's 10 chained calls per transaction (one escrow-funding transfer per schedule), not transaction size; pinned by a test; the SDK splits larger lists. Why one vault per schedule rather than a shared vault: Appendix C.4.
  • Events (F6). Six typed events (Created, Claimed, Cancelled, MadeNonCancelable, MilestoneSignalled, BeneficiaryTransferred), schema documented. LEZ v0.2.4 has no runtime event API, so today the program ships an event seam — event_seq + last_event in the schedule account — that maps 1:1 onto the runtime events the LEZ team merged into dev on 2026-08-27 and is wired to them in the testnet 0.3 milestone.
  • Reliability and performance (R1–R3, P1–P2). One call chain per claim and per cancel; independent accounts and vaults per schedule; every claim, public or private, is a single transaction; a cycle table for every instruction on the real guest, re-measured on each LEZ release (Appendix G).
  • Fee structure — the RFP's four questions. The schedule creator pays, at creation, on top of the escrowed amount (claims stay free, so small private claims are not penalised). Rate 0 % at launch behind a governance-activatable switch with a hard cap of 1 % compiled into the guest; the switch is held by an admin holding set at deployment (a Logos-designated multisig if Logos names one, otherwise a NODERS multisig until handover). Routing to a configurable treasury holding; an optional per-schedule broker fee for launchpad integrators, off by default.

Client surface (U1–U7, Privacy 2–3). SDK as a Rust core module with a C ABI (the modules/token pattern) exposing create, query-claimable, claim, claim-to (public and private), cancel, signal and transfer; the private claim becomes an SDK call with an async prove / submit / re-prove state machine and proving-progress reporting. CLI over the IDL with a claimable query and actionable errors computed locally before submission ("nothing claimable — next unlock at …", insufficient gas, not a private account). Basecamp mini-app in QML/C++ (the apps/token pattern): recipient view (position, locked, claimable now, next unlock, claim with pre-claim summary, gas check on the private path, privacy disclosure) and creator view (create with all parameters, list, cancel, signal). The pre-claim summary reads the network's fee schedule; testnet 0.2 charges no fee, so there it shows 0, and the gas check is live the moment a fee exists. The SPEL IDL exists today (artifacts/vesting-idl.json, pinned to the instruction enum by a test) and is frozen at M1.

Known gaps and platform constraints. Each is reproduced by a test or a command in the repository; the full list with the milestone that addresses each is Appendix I.

  • Runtime events. No event API in LEZ v0.2.4; the seam above is the interim. The runtime does not commit events for privacy-preserving transactions, so F6 for private claims is bounded by the platform, not by the program.
  • Client surface not yet built. SDK, CLI and mini-app are M2–M3; today the stock spel-cli drives 7 of 9 instructions and the private claim runs through the evidence tool tools/vesting-private-claim.
  • Known defect in the deployed program. Beneficiary holdings are not validated against the schedule's token definition; a wrong-token holding would leave a non-cancelable schedule unclaimable. Closed in M1.
  • Private-claim latency. Proving on the claimant's machine plus inclusion took about six minutes in the recorded run (~13 GB RAM).
  • Pending-proof invalidation. A pending private claim is invalidated by any transaction that touches the schedule, the vault or the beneficiary holding — including a one-unit transfer into that holding by anyone. Nothing is lost; the SDK re-proves automatically and names the interfering transaction; the mini-app recommends a dedicated beneficiary holding.
  • Authorities today must be program-owned accounts (token holdings), because a plain wallet can act as a signer only once under the current SPEL output filter; plain-wallet authorities come with new_claimed_if_default in M1.
  • Amount linkability. The opening balance of the private note equals the public claim amount; stated in the privacy disclosure.

Requirement traceability. Status today and the milestone that closes each item (test names per requirement: Appendix F).

Req. Requirement Today Closed in
F1 Cliff+linear, fully linear, milestone schedules On chain + tests M1 (cliff_amount)
F2 Beneficiary fixed at creation; claim to public or private account; creator does not learn the private account On chain, public and private (3a2828ab…) + zkVM tests M2 (SDK)
F3 Cancelable by default; one-way non-cancelable; unvested → creator, vested stays claimable On chain + tests; default in clients M2/M3
F4 Transferable flag fixed at creation On chain + tests
F5 Batch creation, documented maximum zkVM test (max 10) M2 (SDK)
F6 Event per state transition, documented schema Event seam in state; schema in README M6 (runtime events; none for private txs by runtime design)
U1 SDK, full lifecycle, public and private claims M2
U2 Basecamp mini-app, recipient and creator views M3
U3 CLI: create, claim, cancel, signal, claimable spel-cli over the IDL, 7/9 instructions M2
U4 Pre-claim summary, gas check, actionable error M3
U5 Privacy disclosure before each private claim M3
U6 SPEL IDL artifacts/vesting-idl.json + order test M1 (frozen)
U7 Actionable errors incl. next unlock time M2
R1 / R2 Claim and cancel atomic Runtime call-chain atomicity; rejected steps leave balances untouched on chain M1 (explicit tests)
R3 Independent schedules share no state By construction + batch test
R4 Milestone signal idempotent per index On chain (duplicate rejected) + tests
P1 Claim in one transaction On chain
P2 Cycle cost per operation with testnet version Cycle table, all instructions M1 (token-guest cycles), re-measured M4/M6/M7
S1 Deployed and tested on testnet 0.2 Done M4 (release packaging)
S2 Sequencer e2e in CI, green default branch CI: unit, handler, zkVM, clippy, fmt, IDL M1 (sequencer e2e)
S3 A test per hard requirement F/R/P today; private program path M1–M3 (U; client privacy)
S4 README: deployment, addresses, CLI + mini-app walkthrough Deploy runbook + evidence M4
S5 Testnet 0.3 M6
S6 Mainnet M7
Pr1–3 Public/private claim in SDK and mini-app; disclosure; private-destination validation Program path on chain + zkVM tests M2/M3
Fees Four questions answered Above M1 (switch)
Soft Separate cancellation and milestone authorities On chain with nominated holdings + tests M1 (plain-wallet authorities)

Milestones, Payout and Timeline

# Milestone Deliverables Done gate Duration Payout
M1 Program hardening Design note (fee switch, event mapping, cliff_amount) reviewed with Logos in week 1; program tightenings: cliff_amount, holding-vs-token-definition validation, ClaimToPrivate default-destination check and optional valid_until, plain-wallet authorities, fee switch + broker option; edge-case, property and explicit R1/R2 tests; sequencer e2e in CI; IDL frozen; program re-verified on the current testnet Suite green vs standalone sequencer; CI green on main 3 wks $17,000
M2 SDK + CLI Rust core module + C ABI, full lifecycle; public and private claim paths with private-destination validation and the async prove / submit / re-prove state machine; CLI incl. claimable and local actionable errors SDK/CLI exercise every flow vs sequencer; SDK private claim tx linked 2.5 wks $14,000
M3 Basecamp mini-app Recipient and creator views, pre-claim summary, gas check, privacy disclosure, actionable errors; Figma; loads via git repo Loads in Basecamp; screenshots + e2e script 2.5 wks $14,000
M4 Testnet 0.2 release Deployment + verify script; cycle table pinned to the testnet version (vesting + token guest); README and SDK/CLI doc packets; privacy-properties document (threat model, amount-linkability, re-prove races); audit-ready bundle Evidence bundle reviewed 1.5 wks $10,000
M5 External audit Two-week review of the program by a firm agreed with Logos (Zellic / Trail of Bits class) on the M4 bundle, before mainnet; findings fixed in scope and re-verified Report published; findings closed 2 wks $10,000 (passed through at cost)
M6 Testnet 0.3 Program re-verified on 0.3; events wired to the runtime mechanism; CLI serializer re-based on borsh; cycle table re-measured; clients updated Suite + verify script green on 0.3 0.5 wk $5,000
M7 Mainnet Program deployed to mainnet and verified with the same script; clients pointed at mainnet; 12-month maintenance window opens Mainnet deployment verified 0.5 wk $5,000

Core delivery M1–M5: 11.5 weeks; 12.5 with M6/M7. M6 and M7 are paid only on the corresponding release; if a release has not shipped within six months of M5, the milestone scope is re-agreed with Logos, not paid early. Of the $75,000, $65,000 is the build (M1–M4, M6, M7) and $10,000 the external audit, paid as the work is delivered; the 12-month maintenance window and the operated indexer (Post-Delivery Plan) add nothing to the price.

On team size and price. Three full-time engineers for ten and a half engineering weeks, with the program already on testnet, is what lets the schedule hold: the grant buys client surface, hardening and two platform migrations, not discovery.

On the audit. The template asks for an audit milestone where user funds are handled; the escrow handles them, so the audit is a milestone. The custody logic is compact with a single custody invariant, and M4 delivers an audit-ready bundle (traceability, invariants, threat notes) so a third-party review is a two-week job. It is inside the price: $10,000 passed through at cost to a firm agreed with Logos, the report published, findings fixed before mainnet.

Total Requested Budget (USD)

$75,000 (M1–M7, external audit included)

Relevant Experience

  • This RFP: the vesting program above — every on-chain hard requirement implemented, 75 tests, cycle table, full lifecycle on public testnet 0.2 including a privacy-preserving claim, with linked transactions and a re-verification script; platform findings reproduced by tests and a spel-cli fix open upstream (logos-co/spel#266).
  • Logos: the open-source Logos testnet explorer (noders-team/logos_explorer), running today; the RISC Zero verification prototype behind our RFP-020 proposal [PROPOSAL] RFP-020 - LEZ RedStone Oracle Adaptor by NODERS #110 (not selected; the review called it "technically sound and was highly competitive with a few other options") — noders-team/lez-signature-bench, real proofs and reproducible cycle numbers.
  • Privacy-ledger tooling on Canton (a privacy-preserving ledger with public and private state, like LEZ): the Canton Go SDK (go-daml) and wallet library (go-wallet-daml) — signing, submission and ledger-API clients built from scratch.
  • On-chain and tooling work elsewhere: session-key smart accounts with spend caps and freeze on Monad (monad-agent-kit), a Rust monitoring agent for validator operators (monad-sentinel), Cosmos and Solana exporters and indexers.
  • Operations: validators, RPC and indexing for 60+ networks with 24/7 on-call (noders.team) — the base for the maintenance and hosted-indexer commitments below.

Post-Delivery Plan

Continuity, at no additional charge, for 12 months after mainnet: defect fixes across program, SDK, CLI and mini-app; tracking of LEZ, SPEL and RISC Zero releases with the cycle table re-measured on each; the event mapping kept current with the runtime event API; security reports acknowledged within 48 hours; a public verification bundle per release; a maintenance report at month 6 and month 12. After month 12, best-effort maintenance continues as long as we operate LEZ infrastructure, and we hand over cleanly if Logos assigns a successor.

Operated service: a NODERS-hosted vesting indexer with an unlock-notification endpoint (schedule created, cliff reached, claimable now) for LEZ wallets and dashboards, served next to the Logos explorer we already run — REST + WebSocket, open-sourced with the SDK so anyone can run their own; it indexes public schedule state only and learns nothing about private claim destinations.

Adoption: the nominated milestone authority is the hook RFP-015/016 name for creator-collateral vesting; we will offer the integration to whichever teams are awarded those RFPs and publish the three reference flows (team vesting, investor lockup, grant milestones) as SDK examples.

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

RFP-017Proposals for RFP-017proposalProposal submitted for an RFP

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions