Skip to content

[PROPOSAL] RFP-017 — lightlock #198

Description

@raybaann

RFP ID

RFP-017 — Privacy-Preserving Token Vesting

Your Project Name

lightlock

Team or Organization Name

Daccred (Immutable Labs) · https://daccred.co

Primary Contact

Rain Janea - rain@daccred.co

Team Members

Member Role
Andrew Miracle (koolamusic) · Lightgate
github · x · site
Has spent years on the unglamorous half of crypto - making programs that hold other people's money behave exactly as written, every time, forever. That instinct runs through Attest Protocol, which took the Solana Colosseum global hackathon, and Lightgate, where a ledger-pinned DeFi positions API turned correctness itself into the product. He writes down what a system will never do before he lets it take custody.
Perelyn
github · x
Lives in the layer most engineers never touch - sBPF bytecode, the guts of the SVM, contributor to anza-xyz/pinocchio (zero-dependency Solana programs), anza-xyz/mollusk (SVM test harness), regolith-labs/steel (Solana contract framework), blueshift-gg/sbpf (sBPF toolchain), quinn-rs/quinn (QUIC in Rust); author of sbpf-eye, sbpf-linker, yul_by_example
sun.ray (raybaann) · Daccred
github · x
Turns attestation primitives into things people actually use. The signer/resolver model behind Daccred's Attest Protocol and the Solana attestation service - the same idea this proposal leans on for milestone authority. The SDK, CLI, and Logos mini-app are the surface he's most at home on.

Project Summary

Vesting is the primitive every token launch on LEZ will need before a launchpad (RFP-015/016) can distribute to founders, contributors, or investors. On transparent chains it doubles as a surveillance feed: unlock calendars are indexed and traded against. LEZ's ability to credit a private account directly at claim time removes the post-claim trail.
This proposal builds that primitive as a small, auditable escrow program with three schedule types (cliff+linear, fully linear, milestone), batch creation, cancel and transfer semantics fixed at creation, and claims to public or shielded accounts.

We treat the RFP's soft requirement - a milestone authority separate from the creator - as a first-class feature. Milestone-based vesting is an attestation problem: someone asserts a deliverable happened, and tokens move.

Three questions the RFP leaves open, we can answer:

  • a creator's signature authorises the chained escrow transfer in a single transaction;
  • a public transaction cannot credit a foreign private holding (ClaimedUnauthorizedAccount)
  • private claims are beneficiary-built privacy-preserving transactions; and batch creation is bounded at five schedules per transaction by the chained-call cap, not by cycles.

Our background is on-chain attestation and trust infrastructure (Attest Protocol, multi-chain), so we design milestone authority as a delegable signer from day one, which is what makes milestone vesting usable for buyer-protection cases (creator collateral from an RFP-015/016 launch) where the creator must not be able to approve their own unlocks.

Technical Approach

Interface

The interface is the specification - these signatures state exactly what the program accepts and guarantees. Everything below implements them, and the evidence table further down exercises each one on the public testnet.

pub enum Instruction {
    CreateSchedule { schedule_id: u64, params: ScheduleParams, token_program_id: ProgramId },
    CreateBatch    { items: Vec<BatchItem>, token_program_id: ProgramId },
    Claim          { creator: [u8; 32], schedule_id: u64, amount: Option<u128> },
    Cancel         { creator: [u8; 32], schedule_id: u64 },
    MakeIrrevocable     { schedule_id: u64 },
    SignalMilestone     { creator: [u8; 32], schedule_id: u64, index: u8 },
    TransferBeneficiary { creator: [u8; 32], schedule_id: u64, new_beneficiary: AccountId },
}
pub struct ScheduleParams {
    pub beneficiary: AccountId,
    pub total: u128,
    pub kind: ScheduleKind,          // Linear { start, cliff, end } | Milestone { amounts, signalled }
    pub cancelable: bool,            // may be narrowed to false, never widened
    pub transferable: bool,          // fixed at creation
    pub cancel_authority: Option<AccountId>,     // defaults to the creator
    pub milestone_authority: Option<AccountId>,  // may be a multisig or governance program
}
pub fn vested_at(kind: &ScheduleKind, total: u128, now: u64) -> Result<u128, ErrorCode>;
pub fn claimable_at(schedule: &Schedule, now: u64) -> Result<u128, ErrorCode>;
pub fn next_unlock(schedule: &Schedule, now: u64) -> NextUnlock;

Program (Rust, RISC Zero guest, SPEL)

LEZ programs are Rust guests compiled for the RISC Zero zkVM and declared with the SPEL #[lez_program] macro, which also generates the IDL. We follow the lez-multisig repository layout (core / program guest / idl-gen / ffi / cli / e2e) and build, test, and deploy with the pinned LEZ toolchain (cargo risczero build for the guest, sequencer_service --features standalone locally, wallet deploy-program to testnet). We have already done this once: a vesting kernel built this way is live on LEZ testnet (image id 28b0fecf…, deployed in block 27759) with create, public claim, private claim and cancel transactions on-chain.

Accounts

  • VestingSchedule - PDA of the vesting program, seed derived from (creator, beneficiary, mint, index): creator, beneficiary, mint, escrow holding account, schedule kind, start, cliff, end, total, claimed, cancelable, transferable, cancel_authority, milestone_authority, milestone table (amount per index) and a signalled-bitmap, created_at.
  • Escrow - a token holding account at a PDA of the vesting program. Only the vesting program can authorise debits from it, by including the escrow in a chained call to the token program with is_authorized = true and the PDA seed. This is the same custody idiom the LEZ token program, ATA program, vault example, and lez-multisig use; no new runtime primitive is required.
  • Config - singleton: fee switch (rate, payer side, treasury). Managed through the RFP-001 admin authority library. Initial rate 0.
Image > One signed transaction, one chained call. The escrow lives at an address derived from the vesting program, so only that program, supplying the derivation seed with the authorised flag set can move tokens out of it.

Instructions

Image

Time and accrual (integer only)

Time is read from the sequencer clock program account (ClockAccountData { block_id, timestamp }), passed as a read-only pre-state to every time-dependent instruction, with a validity window so a stale clock cannot be replayed.

Image > The accrual function and the two invariants that follow from it: `vested(t) = total · (t − start) / (end − start)` are floored and capped at the total, zero before the cliff; `claimable = vested(t) − claimed`. The escrow balance always equals total claimed with written assertions to enforce behavior at each state transition

Milestone kind: vested = Σ amount[i] for signalled i. Invariants enforced on every state transition and asserted in tests:

  • claimed ≤ vested(t) ≤ total
  • escrow_balance = total − claimed − returned_on_cancel
  • start / cliff / end / total / kind / transferable never change after creation
  • cancelable moves only true → false

Rounding always favours the escrow (floor on vested), so the program can never over-release.

Atomicity

LEZ has no synchronous CPI; a program returns a ProgramOutput whose chained calls (max 10 per transaction) execute depth-first and the whole transaction succeeds or fails together. A claim therefore emits one chained token Transfer from the escrow to the beneficiary and updates claimed in the same output - atomic by construction (Reliability 1, 2). Internal entrypoints are gated on caller_program_id == self.

Claiming to a private account

The runtime authorises program PDAs regardless of whether the receiving account is public or private, so the escrow transfer can target a shielded holding. We have tested both paths on LEZ v0.2.4: a public transaction that names a foreign private holding as the recipient is rejected by the runtime (ClaimedUnauthorizedAccount), while a beneficiary-built privacy-preserving claim succeeds and credits the private holding (proved on testnet, transaction 67503dc0…). Private claims are therefore always beneficiary-built; the SDK verifies the target is a shielded account before building the transaction and refuses otherwise (Privacy req. 3). Both results are recorded in the privacy-properties document.

flowchart LR
  subgraph public["Public claim"]
    P1["Public transaction<br/>signed by the beneficiary"] --> P2["Vesting program<br/>claim to Public"] -->|credit| P3["Public holding"]
  end
  subgraph private["Private claim"]
    R1["Privacy-preserving tx<br/>built by the beneficiary"] --> R2["Vesting program<br/>claim to Private"] -->|credit| R3["Shielded holding"]
  end
  P2 -.->|"rejected: ClaimedUnauthorizedAccount"| R3
Loading

Cancellation and transfer

Cancel returns total − vested(t_cancel) to the creator via a chained transfer and freezes total at vested(t_cancel); already-vested-unclaimed stays claimable. Transfer of beneficiary is permitted only when transferable was set at creation.

Image > Cancel emits one chained transfer of the unvested remainder back to the creator and freezes the total at the amount vested at cancellation; anything already vested but unclaimed stays claimable by the beneficiary. Transfer of the beneficiary is permitted only when the schedule was created transferable on creation.

Milestones

Fixed amounts per index set at creation. signal_milestone(idx) is rejected with a deterministic error if the bit is already set (Reliability 4). Signer must be milestone_authority, which defaults to the creator but may be any account (multisig such as lez-multisig, or a governance program) set at creation.

Batch creation

Each schedule in a batch needs its own escrow funding transfer, so batch size is bounded by the chained-call limit (10 per transaction), not by cycles or transaction size: measured on LEZ v0.2.4, a five-schedule batch uses 1.5M of the 33.5M cycle budget but all ten chained calls. The bound is five schedules per transaction; the SDK/CLI split larger batches into sequential transactions transparently.

Image

Events

RFP-017 F6 requires a log event per state transition. Upstream LEZ has no event mechanism today; LP-0012 exists as a runtime fork with a partial receipt RPC. Our approach: (1) every state transition is recorded in the schedule account (monotonic last_transition sequence + kind + amount + block), so the full history is reconstructible from account state and transaction receipts without events; (2) the event schema (ScheduleCreated, Claimed, Cancelled, MadeIrrevocable, MilestoneSignalled, BeneficiaryTransferred) is defined now and wired to the runtime event API in the testnet 0.3 milestone if LP-0012 has landed by then. We flag this dependency openly rather than promise emission on a primitive that is not yet upstream.

Program identity and upgrades

On LEZ the program id is the zkVM image id, so a rebuilt program is a new program with new PDAs. Milestone 6 includes a documented migration procedure (new deployment, schedule re-creation tooling for creators, and a frozen-schedule export) so the testnet 0.2 → 0.3 → mainnet path is explicit rather than assumed.

Fees

Governance-activatable switch, initial rate zero, charged to the creator at schedule creation if activated, routed to a treasury account, configured through RFP-001. Zero is the ecosystem norm; the switch means the program need not be redeployed to introduce one.

Breakdown

Each interface above, the guarantee it makes, and the on-chain transaction that proves it - one row each. Rows still in Milestone 4 scope carry no transaction yet and say so.

Interface What it guarantees Verified by
CreateSchedule The full total moves from the creator into a program-owned escrow in one transaction; the creator's signature authorises the chained token transfer. Testnet b0e7a4e6… - block 27787
Claim { amount: None } to a public account Releases exactly claimable_at(now), atomically with the escrow debit. Testnet e0b69dc7… - block 27790, 100 released
Claim to a shielded account The escrow credits the beneficiary's private holding; the destination is not observable. Built by the beneficiary as a privacy-preserving transaction, under real proving. Testnet 67503dc0… - block 27808, 50 released, 481 s to prove
Claim naming a foreign private holding from a public transaction Rejected by the runtime - which is why private claims are beneficiary-built rather than creator-pushed. Harness probe: ClaimedUnauthorizedAccount
Cancel Returns total − vested(t_cancel) to the creator, freezes accrual, and leaves everything already vested claimable. Testnet 219d0ed0… - block 27809, 660 returned
claimable_at / next_unlock Before the cliff: nothing claimable, next unlock reported. After cancellation: accrual frozen, the vested remainder still claimable. CLI against testnet - claimable=0 next_unlock=1787945275 before the cliff; claimable=190 status=Cancelled total=1000 claimed=150 returned=660 vested=340 after
Escrow invariant escrow = total − claimed − returned Holds across the whole lifecycle, and is checked against the chain rather than against our own record. getAccount on escrow 8P55FdBA… returns 190 = 1000 − 150 − 660
CreateBatch Several schedules funded in one transaction, bounded by the ten-chained-call limit. Local sequencer, two schedules in one transaction; the bound itself measured at five
MakeIrrevocable, SignalMilestone, TransferBeneficiary Specified above and covered by the test matrix; not yet exercised on the public testnet. Milestone 4 scope

Read the table as one sentence: every number in the final CLI line reconciles with an independent on-chain account read, and the three rows without a transaction say so plainly.

SDK, CLI, mini-app

  • SDK (TypeScript, SPEL-generated IDL bindings): full lifecycle for creator and recipient, both claim targets, human-readable errors including "nothing claimable until ".
  • CLI: create, batch-create, claim, cancel, signal, transfer, claimable-query.
  • Mini-app (Logos Basecamp loadable via git): recipient view (position, locked, claimable now, next unlock, claim with pre-claim summary + gas check + privacy disclosure on the private path); creator view (create, list, cancel, signal).

Testing and CI

Per-requirement test matrix - every Functionality, Usability, Reliability, and Performance item in the RFP maps to a named test including but not limited to accrual function, rounding rules, and escrow invariants. Property tests on the accrual function (monotonic in t, bounded by total, floor-only rounding). End-to-end suite runs against a standalone LEZ sequencer in CI; default branch stays green. CU costs for each instruction are measured and recorded per testnet version.

Anticipated challenges

  • Events (F6): not buildable on upstream LEZ today. Mitigated by account-recorded transition history now and event wiring in the testnet 0.3 milestone.
  • Version drift: the official scaffold pins LEZ v0.1.2 / spel 0.5, spel 0.6.0 targets LEZ v0.2.0, while the public testnet runs the v0.2.4 builtin programs. We pin the exact LEZ tag the testnet runs (verified by fingerprinting its builtin program ids before every deployment) and re-pin when it moves
  • Proving latency: private-path transactions prove locally (tens of seconds to minutes); the mini-app shows progress and never blocks on a second prove for a single claim.
  • Program-id-is-image-id: handled by the migration procedure in Milestone 6.

Milestones, Payout and Timeline

Milestone 1 - Litepaper and external design review · $10,001 · 2 weeks
A public litepaper for the protocol: custody model and escrow invariants, accrual arithmetic with rounding proofs, schedule semantics (cancel, irrevocability, transferability, milestones), privacy architecture (what is visible, what stays private, why private claims are beneficiary-built), event schema, and the deployment/immutability policy. Independent external review of the custody and accrual design before a line of production code - a custodian writes down what it will never do before it takes custody. The reviewed litepaper is frozen at acceptance and becomes the contract every later milestone is verified against; review notes are published with it.

Milestone 2 - Core program on testnet 0.2 · $20,202 · 3 weeks
The custody core: linear and cliff+linear schedules, claim to public and private accounts, cancel with vested-preservation; escrowed custody via chained token transfers, clock validity windows, transition log. Every escrow invariant from the litepaper has a named test before the instruction that could violate it exists. Harness tests including privacy-preserving claims; CI green against a standalone sequencer. The core's account layouts and instruction wire format are frozen at the end of this milestone (v0.1-core) - everything after builds against them, nothing rewrites them.

Milestone 3 - Mid-point demo · $20,002 · 1 week
CLI and a minimal SDK driving the frozen v0.1-core end-to-end on the public testnet: create, claim before/after cliff, private claim, cancel - live transactions Logos can verify on-chain, against a pinned deployment whose image id is recorded in the repo. A natural review gate at half-budget: what you inspect here is the custody engine that ships, not a prototype.

Milestone 4 - Full instruction set · $20,102 · 3 weeks
Milestone-based vesting, transfer-beneficiary, batch creation with its documented bound, make-irrevocable; event emission wired if the runtime ships it. These extend the frozen core - additive instructions only; the demo-gate deployment's semantics are never altered, so nothing a reviewer approved at M3 changes underneath them. Complete requirement→test matrix green, compute-cost table per instruction. Full program frozen (v1.0-rc) at close.

Milestone 5 - Mini-app, full SDK, IDL · $13,531 · 2 weeks
Logos Basecamp mini-app (recipient and creator views, pre-claim summary, gas check, privacy disclosures), full TypeScript SDK with shielded-target validation, SPEL IDL, README covering end-to-end usage - all built against v1.0-rc; surface code cannot reach into custody code, so a UI bug can never move escrowed funds.

Milestone 6 - Audit, remediation, testnet 0.3 and mainnet · $16,161 · 1 week
Third-party review of v1.0-rc (escrow custody, accrual arithmetic, authority checks) by a reviewer experienced with RISC Zero zkVM and account-model programs, selected with Logos' input; findings remediated, the report published in-repo, and the result tagged v1.0 - the final freeze. Verified on testnet 0.3 with a documented migration procedure, then deployed to mainnet without an upgrade authority: the program that was audited is bit-for-bit the program that holds custody, forever. Privacy-properties document finalised. Stewardship continues past the grant (see Post-Delivery Plan) - immutability is why the maintenance burden stays small: there is nothing to hot-fix, only to index, document, and answer for.

Total Requested Budget (USD)

$99,999

Relevant Experience

We build attestation and on-chain trust infrastructure, and we have been trusted with it before. Attest Protocol won the Solana Colosseum global hackathon and is funded through the Colosseum Public Goods Fund; the same work took the Hack Meridian UK prize from the Stellar Development Foundation. Those are the credibility signals that matter for a program that holds other people's tokens on an immutable deployment: independent reviewers have already looked at how we design custody and authority, and staked something on it.

The stack behind this proposal is the stack the repos below evidence. Rust across the account-model and zkVM work; TypeScript for the attestation framework and SDKs; Go for the on-chain indexers and per-protocol adapters. The specific runtimes LEZ needs - RISC Zero zkVM guests, SVM / sBPF internals, and Soroban/Stellar contracts - are ones we have shipped in, not just read about. Attest Protocol (site attestprotocol.org, docs docs.attestprotocol.org, live sandbox sandbox.attestprotocol.org) is a multi-chain attestation framework whose signer/resolver model is the direct ancestor of the milestone-authority design here; the Solana attestation service is account-model program design (PDAs, escrow custody, authority checks) that carries straight over to LEZ. Lightgate (lightgatehq, site at lightgate.xyz) runs a ledger-pinned DeFi positions API on Stellar/Soroban where correctness guarantees were the product - the org that will co-maintain this program's indexer; its product repos are private, with the per-protocol adapters open as lidapters. Our affiliations run through those same ecosystems: the Attest Protocol ecosystem, the Stellar Development Foundation, Solana/Colosseum, and upstream Anza (contributions to pinocchio and mollusk).

Built and maintained by the team

Upstream contributions

Post-Delivery Plan

Post-Delivery Plan

  1. Handoff at mainnet. What Logos receives when M6 closes: the audited, immutable v1.0 program with no upgrade authority; the requirement->test matrix and accrual property tests; the privacy-properties document; per-instruction compute-cost tables; a runbook for the testnet-0.3-to-mainnet deployment and schedule migration; and a governance operations guide for the fee switch and milestone authority. A CONTRIBUTING guide ships alongside so the community can build on it.
  2. One Quarter of upkeep. Through the quarter after launch we keep the program and its tooling current: triaging issues on GitHub @daccred, tracking LEZ and SPEL version changes, emitting events once LP-0012 is available upstream, cutting SDK and CLI releases, and helping the first launchpad (RFP-015/016) integrate against it. Any critical or high finding from the M6 audit, and mediums we agree to, is fixed before mainnet; anything the auditor raises afterward, or a regression we introduce while remediating, we resolve inside this window. New audit engagements are billed separately. If governance later turns on the fee switch, treasury proceeds can carry maintenance past it.
  3. Where it could go next (not in this scope). Because milestone authority is already a delegable signer, a later version could drive unlocks straight from an on-chain attestation or a governance vote - an oracle or DAO asserting a deliverable landed - turning milestone vesting into programmable buyer protection. That leans on primitives beyond this RFP and would be scoped on its own.

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