RFP ID
RFP-015 — Token Launchpad: Bonding Curve
Your Project Name
Synarton
Team or Organization Name
VacuumLabs
Primary Contact
peter.hucik@vacuumlabs.com
Team Members
| # |
Name / pseudonym |
Social links |
Role |
Status |
| 1 |
Uroš Kočišević |
GitHub · CV |
Chief Product Owner |
Full-time |
| 2 |
Boris Hristov |
GitHub · CV |
Tech lead / Senior Full-stack Engineer |
Full-time |
| 3 |
Goktug Gurbuzturk |
GitHub · CV |
Senior Full-stack Engineer |
Full-time |
| 4 |
Ladislav Dubravsky |
GitHub · CV |
Senior Full-stack Engineer |
Full-time |
| 5 |
Marek Roštár |
GitHub · CV |
Project Manager / Utility Dev |
Full-time |
| 6 |
0xcr1st0f |
CV |
Advisor |
Part-time |
Project Summary
We propose a privacy-preserving bonding-curve token launchpad on the Logos Execution Zone (LEZ): a constant-product AMM over virtual reserves, in the pump.fun lineage the RFP names as its model, with a price trajectory fully computable before a sale opens. Creators configure a sale from a token pair, a sale quantity D, an optional DEX seed R & the virtual reserves that shape the curve; participants buy & sell from a public account directly, or privately through the deshield→trade→re-shield pattern, with a mini-app, an SDK, a CLI & sale analytics on top.
Two things distinguish this proposal & both are verifiable before you award anything.
First, the pricing core already exists in Logos's own repository. RFP-015's Reference Implementation & lez-programs/programs/amm/core/src/lib.rs are algebraically identical - checked term for term, not by inspection — including the rounding directions F1 mandates, the fee lattice & the exact-output inverse F1 requires the SDK to expose. The fee arithmetic in particular is exercised rather than dormant: fees is a parameter of the pool-creation instruction, assert_supported_fee_tier gates it on four instructions, the supported set is [1, 5, 30, 100] bps so a pool cannot be created fee-free & the stored rate feeds the kernel on every trade. price_impact_bps & spot_price_q64_64, which U4 needs, are already written & tested. The curve is multiplication & division on u128 with U256 intermediates throughout: no fixed-point exponentiation, no approximation, no convergence argument to audit. We are porting a proven kernel, not deriving a new one.
Second, the specification contains a defect that makes the sale unclosable, and we can prove it arithmetically. The supply target is unreachable on integer arithmetic: no integer collateral input yields exactly D tokens, because the output lattice near exhaustion is ~2,434 tokens wide & D falls strictly inside the gap. This is measured, not argued from one example — across 400 randomly generated parameter sets in the shape the RFP names as its model, none is closable. F4's auto-close therefore never fires, hard requirement R3 is unsatisfiable, F5 leaves the entire raised collateral permanently locked, and two of the five test cases S3 explicitly mandates are tests of behaviour the specification cannot produce. The remedy is a partial-fill clamp built on Logos's own swap_exact_out_amounts — whose "the pool never comes up short" rounding is exactly what F1 asks for, and whose three failure modes are all excluded, one by F2's own Vt > D constraint and two by our creation-time envelope. F1's "must revert" forbids the clamp, so we request that deviation explicitly rather than shipping a silent divergence.
We found eight defects in total. Four are High or Critical: the unreachable supply target above; a fee the specification credits to the reserve and pays to the treasury, leaving the books 1.0101% ahead of the vault at a 1% rate; the same fee ambiguity unresolved on the sell side, where the losing reading drains the collateral vault before a sold-out position can be sold back; and an F5 withdrawal that hands the creator the entire remaining collateral reserve at close while auto-graduation to a DEX is only a soft requirement — leaving token holders with no backing, no sell path & no DEX. All eight are set out below with resolutions. Where the specification is ambiguous we state the reading we take and what follows from it — twelve such readings, each a decision Logos can review rather than a question Logos has to answer.
We are equally explicit about what argues against this mechanism. A bonding curve structurally penalises the privacy path — a private buy is three transactions & the buyer pays the impact of whatever lands in the gap, a 6.19% worse fill with one buy ahead at sale open. No implementation removes that. We quantify it, mitigate it & disclose it rather than leaving it for an audit to find (§10).
RFP-016 is a separate submission and delivery; awarded together, the two run as one Synarton product.
Technical Approach
This is the condensed version. The full proposal carries the complete requirement-by-requirement walk (§3.5.1–3.5.37), every simulation & the full design-decision register. Section links below point into it. Every claim here stands on its own.
1. Architecture & stack
Basecamp mini-app (QML/C++) CLI external integrators
│ │ │
└──────────────┬───────────┴────────────────────────┘
│
client core module (single source of truth)
· quote / price-impact / pre-trade summary
· saga engine: public path + deshield→trade→re-shield
· analytics observer (indexer + per-block account reads)
│
SPEL-generated IDL
│
┌──────────────────┴──────────────────┐
│ bonding-curve program (immutable) │
│ create · buy · sell · close │
│ withdraw · admin config │
└──────────────────┬──────────────────┘
│ chained calls
┌──────────────────┴──────────────────┐
│ token program · ATA program · PDA vaults │
└────────────────────────────────────────────┘
One immutable program; one client core module the mini-app, CLI and third parties all consume, so no quote or saga has a second implementation to drift from. Stack: Rust on RISC0 via SPEL, u128 with U256 intermediates reusing amm_core's mul_div_floor/mul_div_ceil verbatim so F1's rounding is inherited rather than re-implemented; QML/C++ for the Basecamp module; proptest suites plus the two CI e2e lanes. Dual MIT + Apache-2.0 from M0.
2. Platform dependencies — three RFP entries that do not match the code
Verified against local clones: logos-execution-zone @ 9a7a71a and tags v0.2.1–v0.2.4, lez-programs @ 72a3e74, roadmap @ 77a9418. Three entries in the RFP's own Platform Dependencies section do not match the code - two stale in opposite directions, and one naming a primitive the prize it cites never delivered. We flag all three, because anyone scoping this RFP will read them as current.
| Dependency |
RFP says |
Ground truth |
Effect |
| LP-0012 structured events |
"Resolved" |
NOT delivered upstream. emit_event → 0 hits; TxReceipt → 0 hits, at every release tag and dev HEAD. The prize is [CLOSED] and was genuinely won, but the code lives in a contributor fork |
We do not depend on it. U8 analytics run on indexer calldata + getAccountAtBlock + intra-block replay (C9), with a documented migration when events land |
| LP-0013 token authorities |
Hard blocker, "currently open", required for the "transfer-authority primitives" that custody the reserves |
Mis-specified, and closed. LP-0013 delivered a mint authority model; no transfer authority exists anywhere - transfer_authority, delegate, approve return 0 hits across programs/token/, and "transfer" does not occur in the prize text. The RFP's own Resources list titles it "mint authorities", contradicting its dependency block |
Not a blocker, and never was. Rule 5 forbids only decreasing a balance a program does not own, so program-owned PDA vaults suffice - the pattern programs/amm's pool PDA already uses |
| LP-0015 cross-program calls |
"Resolved" |
Partially. ChainedCall ships, but the prize's own success criteria are unchecked and its distinctive deliverables are absent upstream |
Enough to build on - our buy is 3 of the 11 executions available |
| LP-0014 ATAs |
Listed only under Resources |
Delivered. Full programs/ata/, and create is idempotent |
Satisfies F7 directly |
| RFP-001 admin authority |
Hard blocker, "in development" |
Not delivered. The enabling PR spel#233 (opened 2026-06-24) was closed unmerged on 2026-08-19 |
We build the admin authority inline, as the AMM did in update_config.rs, and migrate if RFP-001 lands |
Current release is v0.2.4 (2026-08-07); v0.2.1 shipped 2026-08-02. Testnet 0.3 stands at 0 of 12 roadmap milestones with no published date - which is why S6 & S7 are gated rather than dated.
3. Scope & boundaries
In scope: the immutable program (create, buy, sell, auto-close, constrained manual close, withdraw) with virtual reserves, two accounting buckets, the partial-fill clamp and per-swap fee accrual; the client core module, SDK, CLI and Basecamp mini-app; public and private paths including private sells; one-directional mode; the optional end timestamp with its minimum-duration check; analytics independent of LP-0012; the validation envelope; tests, benchmarks, README and the privacy document; deployment to testnet 0.2, with 0.3, mainnet & an external audit as gated milestones.
|
Out of scope |
Why |
| OOS-1 |
Auto-graduation execution to a DEX |
Soft requirement; execution depends on RFP-004. We ship the hook and emit the graduation-relevant state |
| OOS-2 |
A second pricing mechanism |
The RFP's reference formula is what we implement |
| OOS-3 |
Token creation / metadata service |
The creator supplies an existing token pair |
| OOS-4 |
Admin custody & key-rotation policy |
Admin is a single configurable testnet handle |
| OOS-5 |
Protocol-level bot mitigation |
None implemented - the RFP states no ecosystem platform does & directs projects needing it to RFP-016 |
| OOS-6 |
Audit fees inside the fixed price |
The RFP body states no audit requirement, but the programme's proposal template directs bids to include an audit milestone where a project "handles user funds" or "implements cryptographic primitives" - this does both. M9 runs an external tier-1 audit outside the build envelope, billed at cost |
| OOS-7 |
Off-chain indexer as a hosted service |
The observer is a client-side library, not a subgraph |
| OOS-8 |
Production fee-rate & treasury policy |
Testnet values only; the rate is admin-configured at deployment |
The three soft requirements. One-directional mode - implemented (M2); C22 relies on it as a D-7 mitigation. Optional end timestamp - implemented (M2, C26); it carries the RFP's own minimum-duration floor, the only mechanism the specification offers against the D-7 penalty, and it bounds the >98% of curves that never reach the supply target. Auto-graduation - not implemented (OOS-1); execution depends on RFP-004 and we will not price a milestone against another team's unshipped deliverable. We ship the seam: close emits real collateral, R and final spot as a documented payload, so whoever wires the DEX later need not reopen the program.
4. The pricing core & the defect that makes the sale unclosable (D-1, DEV-1)
The constant product is k = Vt · Vc; a buy pays eff = C_in − fee & receives tokens_out = Vt − k/(Vc + eff), rounded so the residue stays with the pool. F1 mandates that rounding & amm_core already implements it - mul_div_floor on the output, mul_div_ceil on the input, documented "so the pool never comes up short". That much is a port.
F4 requires the sale to close when the sale reserve is exhausted - when tokens_out takes the last of D. F1 requires a buy that would exceed the remaining reserve to revert. On integer arithmetic those two cannot both hold.
Take the RFP's own shape of parameters - Vt = 1,073,000,191e6, Vc = 30e9, D = 793,100,000e6, fee 100 bps. Exhaustion needs eff* = Vc·D/(Vt − D):
eff* = 85,005,301,050.330473 <- not an integer
and collateral is an integer. The two integers either side of it give:
eff = 85,005,301,050 -> tokens_out = D − 805 undershoots: 805 units of dust remain unsold
eff = 85,005,301,051 -> tokens_out = D + 1,629 exceeds the reserve -> F1 mandates a revert
One unit of effective collateral moves tokens_out by roughly 2,434 units at the boundary, so the output lattice is ~2,434× coarser than the input lattice and D falls strictly inside the gap.
This is not a property of these parameters, and we establish that by measurement. 1,000,001 integer values of eff around the boundary produce zero hits, and 1,000,000 integer C_in values through the fee lattice produce none either. Across 400 randomly generated pump.fun-shaped parameter sets, none is closable - 0.00%, median boundary step 1,590. And over 300 sales driven by randomised buy sequences, none reached exact exhaustion. The mechanism is structural - dQ/d(eff) = k/(Vc + eff)², so near exhaustion the output lattice is three orders of magnitude coarser than the input lattice and D has to be struck by coincidence.
The consequence chain terminates in locked funds:
tokens_out == remaining is unreachable, so F4's auto-close never fires.
- F4 is the only close condition among the hard requirements - the end timestamp is soft.
- R3 ("auto-close on supply target… atomic in the same transaction") is therefore unsatisfiable as specified.
- F5 gates withdrawal on the sale being closed, so the entire raise is permanently locked.
- Two of S3's five mandated test cases test behaviour the specification cannot produce - "auto-close on supply target" & "manual close" (undefined, D-3).
DEV-1 - requested deviation on F1. The single terminal buy that would exhaust the sale reserve is partially filled at exactly the remainder, with the unused collateral refunded in the same transaction, instead of reverting. Every other overshoot condition still reverts. The clamp is computed with Logos's own swap_exact_out_amounts, whose rounding is already what F1 asks for, and whose totality we checked rather than assumed: it returns None in exactly three cases - amount_out >= reserve_out, reserve_in == 0, and fee_bps >= FEE_DENOM. The first is excluded by F2's own Vt > D; the other two by our creation-time envelope. So the clamp cannot fail for any reason the function is allowed to fail, on every sale, at the one call site that matters.
Why the boundary cannot be closed from the client. The reasonable first instinct is an SDK-side cap: use F1's own inverse C_in = k/(Vt − Q) − Vc with Q set to the tokens remaining, so the buy is sized never to overshoot and never to revert. Setting Q = D gives the same non-integer eff*, so the client must round, and both directions fail: ceil, which F1 mandates, lands on +1,629 and forces the revert the cap existed to avoid; floor lands on −805 and F4 still never fires. The reason is structural rather than a rounding choice - a client can only choose an input, and every representable input maps onto a ~2,434-unit output lattice that D does not sit on. No client-side sizing closes the sale, because the target is not in the image of the function.
We implement the clamp. F1's must-revert and F4's supply-target close are jointly unsatisfiable on integer arithmetic, so the clamp is the only resolution that leaves F4, R3 and F5 all deliverable. The alternative is not a variant of the same build - it abandons the supply-target close and re-gates withdrawal on a soft requirement - so it is M2's first scope item rather than a late discovery. No competing proposal requests this deviation; those that reach the boundary at all either schedule "revert-on-overshoot", shipping the defect, or propose the client-side cap refuted above.
Derivation, sweep code & the totality argument: §3.5.5.
5. Where the collateral actually goes (D-5 on the buy, D-8 on the sell, D-2)
The contradiction (D-5, High). Three statements in the RFP disagree about what enters the pool on a buy. F1 & the Reference Implementation say the real collateral reserve and Vc each increase by C_in. The Fee Structure section says the fee is deducted before the pricing formula, so the effective input is C_in − fee, and that the fee "is transferred atomically to a protocol treasury account." The fee cannot both leave to the treasury and be credited to the reserve.
Simulated over 200 buys of 5 SOL at 100 bps:
| Reading |
Booked reserve |
Vault holds |
Divergence |
k drift |
F1 / Reference Impl (Vc += C_in) |
1,000,000,000,000 |
990,000,000,000 |
10,000,000,000 - 1.0101% of the raise |
3.5188e+00 % |
Fee structure (Vc += eff) |
990,000,000,000 |
990,000,000,000 |
0 |
1.6533e-10 % |
F5's "full real collateral reserve" withdrawal is then unbacked by exactly that margin - it either underflows or fails, at the worst possible moment: on a successful sale, at close.
DEV-2 - requested deviation on F1 & the Reference Implementation. Read C_in as eff in both, so pricing & the reserve move by eff = C_in − fee. Reversible only into insolvency, so we treat it as settled rather than optional.
Where the fee sits between the buy and the treasury, and why it is not moved in the buy. The runtime settles this. A LEZ transaction carries one post-state per account: the account list must be duplicate-free (validated_state_diff rejects a repeated account_id), and where two writes to one account do reach the state diff the later silently overwrites the earlier - state_diff.insert(...) is a map insert, not a merge, so the loser leaves no error behind. A buy debiting the buyer for collateral and again for the fee would name the payer twice; one chaining a credit into the vault and also writing the vault would lose a write with nothing reported. So the fee accrues: the buy books eff to the reserve and the remainder to an accrued-fee counter, and an authority-gated sweep_fees moves it to the treasury ATA in its own transaction.
This is better accounting than an atomic transfer, not a concession to the platform. Revenue is recognised in the buy that earns it and moved when it is taken, and the reserve is never credited with money about to leave. The M2 done gate asserts collateral vault = booked reserve + accrued fees, exactly, after every operation - a three-term equality that fails under the literal F1 reading and additionally catches a sweep taking more than was accrued.
D-8, High - the same ambiguity, unresolved on the sell side. The Fee Structure defines two quantities on a sell, C_out and C_out_raw; F6's floor names only C_out, and nothing disambiguates which the reserve moves by. Simulated over 17 buys of 5 SOL (99.74% of the reserve) then 200 sells clearing the position:
| Sell-side reading |
Booked |
Vault |
Divergence |
k drift |
Outcome |
C_out is the gross C_out_raw |
62 |
62 |
0 |
2.07e-07 % |
solvent |
C_out = C_out_raw − fee (net) |
405,146,053 |
−440,761,672 |
845,907,725 - 1.0000% of sell-side gross |
1.35e+00 % |
vault exhausted on the 197th of 200 sells |
The sign is the same as D-5's, so D-8 compounds it rather than mirroring it. We take the gross: the real collateral reserve and Vc each decrease by the pricing formula's output, the seller receives C_out_raw − fee, and the difference accrues to the same counter and leaves on the same sweep. This is interpretation, not a fourth deviation: on the buy side F1 names C_in, which the Fee Structure defines unambiguously, so reading it as eff is a genuine departure; on the sell side C_out is never defined against either candidate, so choosing the solvent one is a reading.
DEV-3 - requested deviation on F6. The sell-side slippage floor is checked on the seller's net proceeds (C_out_raw − fee), not the gross the pricing formula produces. Checking a seller's minimum against a number they never receive under-protects by exactly the fee rate - 1.0000% at 100 bps. The sell quote shows gross, fee and net.
D-2, Medium - k is non-decreasing, not constant. The RFP says k "must never change"; on integers it cannot. mul_div rounding leaves a residue, and because F1 rounds against the trader it always accrues to the pool. Over the 200-buy run above the correct reading drifts k by 1.6533e-10 % and the literal one by 3.5188e+00 % - the gap is the defect, not the drift. C4: we state the invariant as non-decreasing and enforce it on chain, as a post-state assertion every operation on the U256 product, so it holds even where k is not representable in u128. Strictly stronger than a property test, and it costs a comparison.
Both simulations & the fee-lattice reuse traps: §3.5.6.
6. Close & what happens to the money (F4, F5, D-6, D-3, D-4)
D-6, High - F5 is a rug vector as written. F5 lets the creator withdraw, after close, "the full real collateral reserve plus any unused DEX seed reserve." Auto-graduation to a DEX is only a soft requirement. Take both literally and a completed sale ends with the creator holding every unit of collateral, and holders holding tokens with no backing, no sell path and no DEX - the sell path prices against a reserve that is now empty. The specification contains its own alternative and does not connect them.
C5 - the collateral destination is a creation-time parameter, defaulting to literal F5. The default is the hard requirement, since graduation execution is out of scope (OOS-1) and defaulting to a behaviour we do not ship would be worse; the alternative escrows the collateral for graduation. D-6 is handled by disclosure rather than by silently overriding a hard requirement: the creator's confirmation screen and the participant's pre-buy summary both state which destination the sale uses, so a buyer knows before paying whether anything backs the token after close. This is the most consequential non-arithmetic defect in the RFP, and the one most likely to produce a real loss.
D-3, Medium - "manual close" is required by P2 and S3 and created by no Functionality requirement. P2 asks that "a close transaction (manual or auto-triggered)" complete within one transaction and S3 mandates a "manual close" test, but no Functionality requirement grants the instruction, says who may call it, or says where the collateral goes. Unconstrained, and combined with literal F5, it is a cheaper rug than the one D-6 describes - the creator closes early and withdraws, and buyers never reach the supply target they were pricing against. C24: creator-authorised, only after a documented minimum open period, destination governed by the same F5 parameter. Authority and period are configuration.
D-4, Low-med - the sell curve is not the buy curve's inverse, and bucket re-entry is unspecified. Sells are bounded by the real collateral reserve, not the virtual one, so a sell of the same size does not return the buyer to the price they bought at, and nothing states where sold-back tokens go. We return them to the sale reserve, keeping D the supply target it is defined to be - which makes the target unreachable for a second, independent reason if sells outpace buys, and the DEV-1 clamp is what keeps it closable then too.
F5 analysis, the three-bucket drain assertion & C24: §3.5.9.
7. Sale creation, the validation envelope, and k (F2)
Creation fixes the pair, D, optional R and the virtual reserves, transfers D + R real tokens into program-owned PDA vaults, and initialises the two buckets: a sale reserve starting at D and decreasing with each purchase, and a DEX seed reserve starting at R, untouched until close. Virtual reserves are the net-new core - the curve prices on (Vt, Vc) while custody and the close condition track the real balances. grep -i virtual programs/amm returns zero hits, so this is where test effort concentrates.
F2 states exactly one constraint, Vt > D. Nothing bounds Vc, D, R or the fee rate, and several unvalidated sets are degenerate or hostile: a Vc small enough that p₀ rounds to zero, D = 0, or a fee rate at or above the denominator - which swap_exact_out_amounts rejects with None, rendering sales unfillable. We validate all of them at creation, in the immutable program, and publish the envelope. Notably we do not impose Vt ≥ D + R, which the RFP does not require: Vt is a pricing parameter, not a deposit.
And k is recorded, not computed with, which is what keeps the parameter space open. The RFP asks for k = Vt · Vc "computed and stored at creation" and separately requires curve state to expose it. Read as an instruction about representation, that is not implementable at the top of the plausible range: an eighteen-decimal pair gives k = 1e51 against a u128::MAX of about 3.4e38, so a program whose arithmetic depended on a stored k would have to refuse the sale. It does not have to - each formula folds into one mul_div with a U256 product and a quotient proven to fit u128, an identity leaving the invariant and every rounding direction unchanged. Nor is this our invention: there is no k in amm_core either. The invariant is fixed at creation and reported as state; nothing prices from a stored k, and no sale is refused on magnitude.
8. The private path (F3, U1, U5–U7, Priv1–Priv4, R1)
The path is three transactions - deshield (PPE) → buy (public) → re-shield (PPE) - and the middle leg staying public is a design decision, not a shortcut.
C14 - the buy stays public on both paths, and R1 is why. The curve PDA is the hottest account in the system, written by every buy, on a sale whose premise is concurrent demand at open. Any design putting the buy inside a PPE transaction fails R1 under exactly the load R1 is about: a client-proved transaction is proved against a pool snapshot, so at most one such buy survives per block and the rest are dropped and must be re-proved. The deshield touches only the buyer's private account and a fresh ephemeral, so it has no contender; the buy is an ordinary public transaction the sequencer orders against live state.
Freshness buys the absence of a contender; it does not by itself buy admission. In privacy_preserving_circuit/src/execution_state.rs, a post-state claiming an account asserts it is uninitialised, then splits on claim kind: Claim::Authorized on a public account asserts pre_is_authorized and fails otherwise; Claim::Pda(seed) checks only that the id matches the derived PDA, with no authorization requirement; private accounts are exempt. So a fresh public destination that is neither signed for nor claimed by derivation is refused - inside the circuit, where no receipt carries a reason. Which is why the ephemeral is derived, not merely new: Priv4 already requires non-reuse, derivation is the cheapest guarantee of it, and the same derivation supplies the claim. Where a leg needs the ephemeral to sign - the public buy does - the client holds that keypair and authorises it there.
U1's atomic collateral-and-gas deshield is the one genuinely hard clause, and it is not a platform primitive. There is no one-shot "deshield token + gas" instruction: collateral is a token-program asset, native gas moves through authenticated_transfer. Atomicity must be constructed, and it is constructible because a PPE transaction is not single-program - the circuit walks the chained-call chain and verifies each callee. The construction: one PPE transaction whose entry program emits both legs, atomic under runtime rollback, 2–3 executions against a cap of 10. The question is dormant today: the runtime does not charge fees (TODO: Make this variable when fees are implemented, no fee field on a zone transaction), so the deshield is single-asset and atomic by construction. The gas leg is built parameterised and conditional on fees landing (C25).
The execution budget is not uniform, and we state which one we spend. Public execution checks chain_calls_counter <= MAX before incrementing (11); the host PPE prover uses a strict >= (10), so a client-proved transaction has one fewer execution than a public one. Our public buy is 3 of 11, leaving 8 unused; the deshield is 2–3 and the re-shield 1–2 against the cap of 10. Neither the invocation limit nor cycles (MAX_NUM_CYCLES_PUBLIC_EXECUTION = 1024 * 1024 * 32) binds this program.
Proving is minutes, and we treat that as a design constant rather than a risk to discover. Logos's own cycle_bench documents real proving as "slow, ~minutes" and PPE composition as "very slow, ~hour", and the reason is structural: each inner program receipt is added as a circuit assumption, so the outer privacy circuit recursively verifies every program in the chain. That cost is not ours to optimise, so the private path is built as an explicitly asynchronous flow with per-leg progress and elapsed time - never a spinner.
The saga engine, and why durability is not optional. A private operation that stops after the buy leaves the user's tokens on a public ephemeral account - the exposure Priv4 exists to prevent, reached by a laptop lid rather than an attack. So the saga is durably journalled before the deshield is submitted, and on restart completes the re-shield. C20: resumption is not Priv4 reuse - Priv4 forbids reuse across operations, and finishing an operation already begun is that same operation; the alternative forbids crash recovery and strands funds.
Private sells are in scope (C6). F3 is the only place the RFP generalises to "trade", and it makes both paths mandatory for program and SDK; every other instance - U1, U4–U7, all four Privacy requirements - is written as deshield→buy→re-shield. A launchpad whose privacy covers only the entry is not privacy-preserving: a private buy followed by a public sell links the position that was being protected. The saga engine is direction-generic (C23), so the mirror is incremental. If Logos intends buys only: 1.5–2 dev-weeks off M4, nothing else changes.
The trust split, stated plainly: the program enforces the money (pricing, rounding, solvency, close semantics, atomicity under rollback); the client enforces the privacy (single-use ephemerals, non-reuse, non-skippable re-shield), because the program cannot tell a fresh account from a reused one.
9. Sale analytics without the dependency the RFP calls resolved (U8)
U8 asks for collateral raised, spot price and supply sold over time per sale, without exposing identity or linking buys to accounts. The RFP points at LP-0012 structured events. Those are not delivered at any ref (§2 above), so an event-based observer would be built on a primitive that does not exist.
C9 - the observer does not depend on LP-0012. Every public transaction carries PublicMessage { program_id, account_ids, nonces, instruction_data } - full instruction data. So the observer filters the indexer by program_id, decodes instruction_data against the U9 IDL, and reads the touched sale PDA via getAccountAtBlock; where several buys land in one block it replays the pricing function over the decoded calldata to order them. That reconstructs raised, spot and supply-sold from data that exists today, and migrates to events when they land rather than being rewritten. Cost: 1–1.5 dev-weeks, and M0 proves the path against the live chain before M5 depends on it.
The privacy property is the part that can fail invisibly. Aggregates are keyed by sale, never by account, and the private path's only on-chain handle is a single-use ephemeral, so there is nothing to key on; the done gate asserts the surface exposes no account-linked field.
10. D-7 - the private path costs money on a bonding curve, and we say so
On a rising curve price moves with every buy, and a private buy is three sequenced transactions, so whatever lands in the gap between a private buyer's deshield and their buy is priced in ahead of them. The RFP acknowledges this only inside a soft requirement. We quantify it - fill degradation on a 1 SOL buy, by buys landing ahead:
| Curve state |
1 buy ahead |
3 buys ahead |
10 buys ahead |
| At sale open |
6.19% worse |
16.97% |
43.02% |
| After 30 SOL raised |
3.21% |
9.18% |
26.25% |
| After 200 SOL raised |
0.86% |
2.55% |
8.13% |
The penalty is worst exactly where privacy matters most - at open, when the curve is steepest and the sale most contended. Three reductions, and one disclosure of what is left. The largest is submitting the deshield and the buy in the same block: the ephemeral's id is derivable client-side before the deshield lands, so the buy needs no confirmation to be built, collapsing the exposure window rather than narrowing it - at the cost of a resubmit if the deshield is delayed, so it is a default with a clean fallback. One-directional mode (C22) removes the reflexive sell pressure that would otherwise land in the gap. The minimum-duration floor carried by the end-timestamp soft requirement is the specification's own mitigation, validated at creation so it exists in the build rather than only in the text. Then U5's disclosure states the number, so a private buyer sees the cost of the privacy they are choosing rather than discovering it in the fill.
What none of them does is remove it, and that is structural. Closing the gap entirely would take either a different pricing mechanism - which the RFP fixes - or the buy inside the PPE transaction, which fails R1 under exactly the contention R1 exists for (§8).
Full table, derivation & mitigations: §3.5.35.
11. Declared deviations, and the readings we take
We request three deviations from hard requirements. All three are forced by defects, all three are narrow, and all three are stated here rather than discovered during delivery.
| # |
Requirement |
Deviation |
Why it is necessary |
| DEV-1 |
F1 - a buy whose tokens_out would exceed the remaining sale reserve "must revert" |
The single terminal buy that exhausts the reserve is partially filled at exactly the remainder, with refund, instead of reverting. All other overshoot conditions still revert |
Without it tokens_out == remaining is unreachable (D-1), so F4 never fires, R3 is unsatisfiable, and F5 leaves the entire raise permanently locked |
| DEV-2 |
F1 and the Reference Implementation - "the real collateral reserve increases by C_in" |
Both the reserve and Vc increase by eff = C_in − fee, matching the RFP's own Fee Structure section |
The literal reading books collateral the vault does not hold - 1.0101% of the raise at a 1% fee (D-5) - and F5's withdrawal on a successful sale is unbacked by that margin |
| DEV-3 |
F6 - on sell, revert if the computed C_out is below the stated minimum |
The floor is checked against the seller's net proceeds (C_out_raw − fee), not the gross the formula produces |
Under the only solvent reading of C_out (D-8), checking a seller's minimum against a number they never receive under-protects by exactly the fee rate - 1.0000% at 100 bps |
Twelve ambiguities, and the readings we take. Each is a decision here, with its reasoning, rather than a question returned to Logos - so anything we read wrongly is visible now rather than at the milestone it lands in. All are reversible at kickoff as configuration or scope unless the row says otherwise.
Four of the twelve are the deviations above, plus the sell-side fee base - never disambiguated, so reserve and Vc move by the gross, the seller gets net and the treasury the difference: interpretation, not a fourth deviation. The remaining eight:
| Ambiguity |
Our reading and decision |
What "computed and stored" binds for k |
Fixed at creation and reported as state; nothing prices from a stored k - one mul_div in U256, so no parameter set is refused (§7) |
| Does F3's "trade" require private sells? |
In scope (C6, M4). If Logos intends buys only: 1.5–2 dev-weeks off M4, nothing else changes |
| Where the collateral goes at close |
A creation-time parameter defaulting to literal F5, graduation-escrow the alternative (C5). D-6 handled by disclosure |
| Who may call the manual close |
Creator-authorised, only after a documented minimum open period, destination governed by the F5 parameter (C24) |
| Is the gas fee model live? |
Build the gas leg, conditional on fees landing (C25) - parameterised rather than written against an assumed rate |
| How S6 and S7 are scheduled |
Fixed-scope, triggered by platform availability, outside the 12-week calendar (C19). A dated commitment would assert what no Logos source supports |
| Does saga resumption count as Priv4 reuse? |
No - crash recovery on that basis (C20); the alternative strands funds |
| What does U1's "atomic deshield… single indivisible user action" bind? |
The construction satisfying both readings - one PPE tx emitting both legs. Dormant while there is no user-paid gas; a forced split would be declared, not absorbed |
Full register, with per-row reasoning: §3.7.
12. Testing
Two CI lanes green on the default branch, and a skipped lane is not a passing lane: an in-process harness carrying the invariant and property suites, and a standalone sequencer job for e2e (S2). The five cases S3 names by hand each exist as a named test. Four more are the ones that would catch the defects above. Two-way solvency: over generated buy/sell sequences interleaved with sweeps, the vault equals booked reserve + accrued fees exactly and the reserve never exceeds the vault - this fails under either the D-5 or the D-8 losing reading, and twice under both. The D-1 boundary: on the canonical parameter set the terminal buy fills the remainder, refunds and closes in one transaction, where a literal-F1 implementation reverts forever. R2: failure injected at each of the buy's three executions, the sale account asserted byte-identical across it, not merely in the fields we thought to check. Priv4: two buys from one shielded account never share an ephemeral address, and killing the process mid-saga generates no second one.
13. Requirements traceability matrix
All 35 hard requirements - 34 numbered plus the unnumbered Supportability milestone mandate - owned by at least one milestone with an objective acceptance test. Zero gaps. R = reuse of existing Logos code, N = net-new.
| Requirement |
How it is met |
|
Milestone |
| F1 Constant product, virtual reserves, rounding against the trader |
amm_core kernel ported verbatim, rounding inherited; DEV-1 clamp at exhaustion; DEV-2 prices on eff |
R+N |
M1 · M2 |
F2 Sale creation, Vt/Vc/k, two buckets |
Creation instruction, PDA vaults, published validation envelope; k recorded rather than used as an operand, so no parameter set is refused |
N+R |
M1 |
| F3 Both paths, buy and sell |
Public path an ordinary transaction; private path deshield→trade→re-shield; private sells in scope (C6) |
N |
M2 · M4 |
| F4 Auto-close when the sale reserve is exhausted |
Close written in the same execution as the terminal clamped buy - deliverable only because of DEV-1 |
N |
M2 |
F5 Creator withdraws collateral + unused R |
Gated on closed state; destination a creation-time parameter defaulting to literal F5 (C5); D-6 disclosed to both roles |
N |
M2 |
| F6 Slippage protection both directions |
Minimum-out on buy; DEV-3 floor on net proceeds on sell; refusal proven not to move state |
N |
M1 · M2 |
| F7 ATAs for all token interactions |
Delivered programs/ata/, create idempotent so a not-yet-existing vault is not a special case; LP-0013's dependency mis-specified and unneeded - PDA-owned vaults |
R |
M1 |
| U1 SDK, both roles, both paths, atomic deshield |
Core module + saga engine; both legs in one PPE tx; fresh derived ephemeral, uncontended and claimable by seed |
N |
M3 · M4 |
| U2 Mini-app, participant and creator views |
Basecamp QML/C++ module from a git repo with build instructions and assets; gate is loads and runs in the host |
N |
M4 |
| U3 CLI covering core functionality |
Same core module as the mini-app, so coverage is structural rather than parallel-maintained |
N |
M3 |
| U4 Pre-buy confirmation summary |
Exact tokens from the program's own mul_div; two of five values reuse existing code, price impact computed as (Vc+eff)²/Vc² − 1 since price_impact_bps measures execution slippage, not U4's metric |
R+N |
M3 |
| U5 Privacy disclosure before each private buy |
States what an observer sees, what the pattern does not protect, and the D-7 price exposure |
N |
M4 |
| U6 Enforce the atomic deshield |
In the core module so the CLI inherits it; ephemeral saga-created, single-use, not user-nominable |
N |
M4 |
| U7 Balance pre-check before deshield |
Covers collateral and fee and gas together, so a deshield cannot strand a user mid-flow |
N |
M4 |
| U8 Sale analytics, no identity linkage |
Indexer calldata + getAccountAtBlock + intra-block replay via the U9 IDL - no LP-0012 dependency (C9); keyed by sale, never by account |
N |
M5 |
| U9 SPEL IDL |
Generated and frozen first in M1, the contract three streams work against (C8) |
R |
M1 |
| U10 Actionable errors |
Typed error per failure, each carrying its recovery datum - remaining quotable amount, or the clearing price |
N |
M2 · M3 |
| R1 Consistency under concurrent buys |
Sequencer's sequential validate-then-apply, failed txs skipped; the buy stays public on both paths (C14) so PPE contention cannot drop it |
R+N |
M2 |
| R2 Failed buy reverts atomically |
All-or-nothing across the buy's three executions, asserted as byte-equality of the sale account |
R+N |
M2 · M5 |
| R3 Auto-close atomic with the final buy |
Same-transaction close on the clamped buy. Unsatisfiable as specified (D-1); deliverable under DEV-1 |
N |
M2 |
| P1 Buy within one LEZ transaction |
Public buy = 3 of 11 executions; private path three transactions, PPE legs against a cap of 10 |
R |
M2 · M5 |
| P2 Close within one LEZ transaction |
Auto-close is a state write inside an execution already paid for; the constrained manual close (C24) is one transaction |
N |
M2 · M5 |
| P3 Document CU cost per operation |
Executor cycles, wire bytes & chained-call depth via Logos's own cycle_bench, CI-gated (C17), testnet version stated - LEZ has no CU denomination, so we name what it meters |
R+N |
M5 |
| S1 Deployed & tested on testnet 0.2 |
Its own milestone, as the mandate requires; 0.2 live since 2026-08-02, v0.2.4 current |
- |
M6 |
| S2 e2e against a standalone sequencer, green in CI |
Second CI lane; a skipped step counts as not run |
N |
M5 · M6 |
| S3 A test per hard requirement |
This matrix, complete, plus the five cases named by hand - two of which test behaviour the spec cannot produce (D-1, D-3), stated rather than quietly passed |
N |
M5 |
| S4 README documenting end-to-end usage |
Deployment, addresses, both walkthroughs; gate is a reader who did not write it following it from a clean checkout |
N |
M6 |
| S5 Privacy & anonymisation document |
What is visible, what is not & what the pattern does not protect - timing correlation, analytics as an aggregate public record |
N |
M6 |
| S6 Verified on testnet 0.3 |
Fixed-scope redeploy-and-verify, gated on the platform date (C19) |
- |
M7 |
| S7 Deployed to LEZ mainnet |
Reproducible build from a freeze commit, deployment record published; platform-gated |
- |
M8 |
| Priv1 Both paths, complete pattern |
Saga engine with a non-skippable re-shield, durably journalled before the deshield is submitted |
N |
M4 |
| Priv2 Pre-confirmation privacy summary |
Generated from the same account and amount data the saga uses, so it cannot drift from the operation |
N |
M4 |
| Priv3 SDK validates re-shield target is shielded |
Type-level check before submission, so failure precedes any movement of funds |
R+N |
M4 |
| Priv4 Ephemeral never reused |
Enforced by derivation, not convention; resuming an operation already begun is not reuse (C20) |
N |
M4 |
| (unnumbered) Separate milestones for 0.2, 0.3, mainnet |
Three distinct milestones, the latter two gated (C19). Counted as the 35th hard requirement |
- |
M6 · M7 · M8 |
Per-requirement test detail: §3.9. Design decisions C1–C26: §3.6.
Milestones, Payout & Timeline
M0–M6 sum to 12 weeks, the upper bound of the RFP's 10–12 week estimate. We plan to the ceiling rather than under it, because two of the three deviations sit in M2's scope and we would rather absorb a re-scope inside the schedule than renegotiate it. M7 and M8 sit outside that envelope on Logos's calendar, not ours - testnet 0.3 stands at 0 of 12 roadmap milestones with no published date, so both are fixed-scope milestones triggered by platform availability (C19). M9 is the audit programme and does not fit inside a 12-week build, so we do not claim it does. Durations are calendar, not dev-weeks; three engineering streams run throughout - A on-chain, B client core/SDK/CLI/analytics, C mini-app and documentation - with the tech lead on stream A, a project manager who also takes utility development, and a part-time advisor.
| # |
Deliverable headline |
Duration |
Payout |
| M0 |
Deviation sign-off, scaffolding, CI, marginal-proving + observer spikes |
≈1.5 wk |
$12,000 |
| M1 |
Curve core: pricing, virtual reserves, buckets, sale creation — IDL frozen |
≈2 wk |
$16,000 |
| M2 |
Lifecycle: partial-fill clamp, auto-close, withdrawal, fee accrual, slippage |
≈2 wk |
$16,500 |
| M3 |
SDK + CLI, full public-path lifecycle, pre-trade summary, error taxonomy |
≈2 wk |
$16,000 |
| M4 |
Private path: sagas, durability, Priv1–Priv4, disclosures, D-7 mitigations |
≈2 wk |
$16,500 |
| M5 |
Analytics without LP-0012, reliability suite, CU benchmarks + CI gate |
≈1.5 wk |
$12,000 |
| M6 |
Testnet 0.2 deployment, coverage matrix, README, privacy document |
≈1 wk |
$8,000 |
| Build total (M0–M6) |
|
12 wk |
$97,000 |
| M7 (gated) |
Testnet 0.3 verification + delta report + C9 analytics migration |
≈0.5 wk, on platform availability |
$1,500 |
| M8 (gated) |
Mainnet deployment + deployment record |
≈0.5 wk, on platform availability |
$1,500 |
| M9 (audit) |
External tier-1 audit + remediation — outside the build envelope |
≈4–6 wk calendar, ≈2 wk ours |
at cost + quoted separately |
M0 - Deviation sign-off, scaffolding, CI & the two measurements that are actually open. ≈1.5 wk · $12,000
Decision record confirming the three deviations & the twelve readings, DEV-1 & DEV-2 taken first because both sit in M2's scope; SPEL monorepo scaffold under dual MIT + Apache-2.0; CI with both e2e lanes; the marginal-proving spike; the analytics observer spike against the live indexer.
Every M0 gate closes on a published artifact, not on our word for it and every figure carries the commit, the pinned toolchain versions and the command that produced it - including figures that come back worse than we would like, since a measurement published only when it flatters is not a measurement. Done gate: spel generate-idl succeeds, an empty program builds to a guest binary and a loadable Basecamp module, versions pinned in the lockfile; CI green on both lanes; the observer spike, as a re-runnable script, pulls a real transaction from the live indexer, decodes it against the draft IDL and reads the touched account via getAccountAtBlock - validating C9 with LP-0012 absent, against the live chain rather than against our reading of it; the chain-caller depth sweep from cycle_bench --prove --ppe committed with raw output, giving a marginal cost per chained call and a go/no-go on the two-leg deshield, proving time reported separately from waiting time; the private path's asynchronous shape fixed in the mini-app shell; the decision record signed off or formally directed otherwise, minuted either way — the one gate we cannot close alone, which is why it is first.
M1 - Curve core. ≈2 wk · $16,000
Pricing, virtual reserves, both buckets, sale creation with the validation envelope, F6 on buy, the SPEL IDL generated and frozen (C8). Done gate: the ported kernel matches amm_core on shared vectors term for term; the non-decreasing-k assertion passes on chain; slippage refusal proven not to move state; the k-magnitude case prices rather than refuses; IDL committed and consumed by the core module.
M2 - Lifecycle. ≈2 wk · $16,500
The DEV-1 clamp with refund, F4 auto-close atomic with the terminal buy, F5 withdrawal with the destination parameter, fee accrual plus the authority-gated sweep, DEV-3's sell-side floor, sell bounding, the constrained manual close (C24), one-directional mode, the end timestamp with its minimum-duration floor (C26). Done gate: the D-1 boundary regression - on the canonical parameter set the terminal buy fills the remainder, refunds and closes in one transaction, where a literal-F1 implementation reverts forever; the vault equals booked reserve + accrued fees, exactly, after 200 buys and across a sweep, and again after a full buy-then-sell cycle that clears the position; withdrawal rejected before close and paying the configured destination after it, with all three vaults draining to exactly zero.
M3 - SDK and CLI. ≈2 wk · $16,000
Full public-path lifecycle both roles, the exact-cost inverse exposed, U4's pre-trade summary, the U10 error taxonomy, README first pass. Done gate: CLI and SDK suites green against a standalone sequencer; the pre-buy summary asserted equal to the on-chain result; one named test per error class.
M4 - Private path. ≈2 wk · $16,500
deshield→trade→re-shield for buy and sell, the derived single-use ephemeral, durable saga journalling, non-skippable re-shield, Priv2 summary, Priv3 validation, U5-U7 guardrails, the D-7 reductions. Done gate: e2e on both private directions; the process killed mid-saga, the re-shield completing on restart with no second ephemeral generated; a public account offered to the private path rejected explicitly; two buys from one shielded account never sharing an ephemeral address; U7 refusing a deshield that covers the purchase but not the gas.
M5 - Analytics, reliability, measurement. ≈1.5 wk · $12,000
The U8 observer (IDL-driven calldata decoding, per-block PDA reads, intra-block replay), the reliability and concurrency suite, the per-operation cost table under a CI gate (C17). Done gate: the observer reconstructs a known sale's full history from indexer data alone, with no account-linked field on the surface; cycles, wire bytes and chained-call depth published with the testnet version stated; sequencer e2e green with no skipped steps.
M6 - Testnet 0.2 deployment. ≈1 wk · $8,000
Deployment, completed coverage matrix, README walkthroughs, the S5 privacy document. Done gate: deployed with the freeze commit, guest image id and deployment record published; full suite green against the deployed program; someone who did not write the README following it from a clean checkout to deploy, create a sale, buy and close.
M7 - Testnet 0.3. ≈0.5 wk · $1,500 · Trigger: Logos publishes testnet 0.3
Rebuild, redeploy, verify, delta report on anything the runtime moved, plus the C9 migration if LP-0012 has landed. Done gate: suite green against 0.3, deployment record published.
M8 - Mainnet. ≈0.5 wk · $1,500 · Trigger: Logos opens mainnet deployment
Reproducible build from a freeze commit, deployment, verification, record. Done gate: deployment verified and recorded, gated on M9 — we would not deploy an immutable program that had not been audited.
M9 - Audit programme and remediation (runs outside the 12-week build envelope)
Payout: external tier-1 audit fees billed at cost (pass-through to Logos); our remediation and re-verification (≈2 wk) quoted separately once the firm is engaged · Duration: ≈4–6 weeks calendar after the M6 freeze, ≈2 weeks of it ours; the slot is reserved at kickoff so firm lead time is absorbed before it binds.
Primary tier-1 audit by one of our two partners, Zellic or Sherlock, selected at kickoff by availability, of the immutable program & the client core, with four named focus areas - each a place this document argues the specification is unsafe: DEV-1's clamp at the exhaustion boundary, the fee accrual & sweep on both sides of the trade, the F5 withdrawal path & collateral destination & PDA vault custody across the buy's three chained executions. A second independent review on the same frozen commit is available if Logos wants it & is the one addition we would recommend. Done gate: report published with the codebase; all critical & high findings resolved or formally accepted with written rationale; CI green post-remediation including the D-1 boundary regression & the three-term solvency identity. M9 is numbered last because it is charged last, not because it runs last: it opens at the M6 freeze, runs in parallel with M7 & its done gate is a precondition for M8.
Schedule risks we are naming up front
| Risk |
Exposure |
Handling |
| M0's decision record is not answered inside M0 - the RFP states Logos "typically respond within 14 days" |
M2 opens without DEV-1/DEV-2 settled & M2 is where both land |
M1 does not depend on either - it is the kernel, reserves, buckets, creation & the frozen IDL. M1 & M2 swap order if the answer is late and the 12-week total is unchanged. The one M0 gate we cannot compel |
| DEV-2 or DEV-3 refused - the literal fee bases stand |
The program books reserves the vault does not hold on both sides (D-5, D-8) & F5's withdrawal fails on a successful sale |
We would not ship it silently: the M2 done gate asserts vault-equals-reserve-plus-accrued both directions, so a refusal is a specification decision on record rather than a defect we absorb |
Total Requested Budget (USD)
$100,000
Relevant Experience
What we have built on this exact problem. Vacuumlabs has delivered a production bonding-curve token launchpad end to end. It began as a Solidity system on an EVM L2; we rewrote the launch programs for Solana in Rust with Anchor & extended the product well past the feature set it started with. Three parts of it are the parts RFP-015 asks for: a bonding curve a launch trades on until it completes, with per-token curve state, buys priced against it and a defined completion condition; graduation of a completed launch into a constant-product DEX pool, including a Meteora constant-product pool on the Solana side - we place auto-graduation out of scope here (OOS-1) because its execution depends on RFP-004, but the reason C5 can specify a graduation-escrow destination precisely is that we have built the far side of it; and an indexer that reconstructs launch history from chain data - curve state per token, every buy against the curve, graduation events — serving the product's analytics from it, which is U8's problem solved the way C9 solves it. The engagement is under NDA, so this is deliberately capability-level - no client, no contract names, no parameters - and we can walk Logos through it under NDA.
A second launchpad, on Solana. Vacuumlabs designed and built Syndicate, a fully decentralized fair launchpad on Solana, frontend and backend. Between the two, the account-model shape this RFP runs on - PDAs, ATAs, IDL-driven clients, program-owned vaults - is not new to us.
Constant-product AMM correctness, examined at invariant level. We audited WingRiders, a Cardano constant-product AMM DEX, in a full-time engagement of roughly 17.5 person-days. The method is what bears on this bid: we reverse-engineered the pool's on-chain pricing independently of its documentation, extracted its treasury and fee handling, and checked the token1 × token2 = k invariant against live transaction data rather than against the team's account of it. That is what §5 above does to RFP-015's Reference Implementation, and it is what surfaces the two defects a constant-product launchpad is likeliest to ship with: an invariant that does not hold as stated under integer rounding (D-2), and a fee credited in one place and paid from another (D-5, D-8). Also in the public audits repository: Liqwid Finance, Ardana and VyFinance — lending, stablecoin and staking rather than AMM work, presented as track record on on-chain correctness, not domain-matched experience.
Rust systems in production, and indexer-derived state. Autonom is a trustless RWA price oracle Vacuumlabs built from scratch - Rust operator nodes resolving price requests across chains and DEXes, in production behind Adrena ALP2. We have also delivered production infrastructure and client tooling for API3 and FunKit, including its Solana infrastructure. CARP, built with dcSpark, is a Cardano chain-indexing and analytics service reconstructing application-level state from raw chain data - the same shape as the launchpad indexer above and as C9.
Post-Delivery Plan
Vacuumlabs maintains the project as a team - support is not tied to any single individual. Support runs through the public GitHub repository (issues and PRs) and the primary contact listed above. Delivery includes handover of everything an operator needs: the README deployment runbook, the published program addresses with their freeze-commit and image-id records, the M9 audit report and remediation record, and the per-operation compute-cost reports. The entire project is open-sourced under MIT and Apache-2.0, so every component is forkable and community-runnable rather than dependent on us.
We provide a 6-month post-delivery support window from acceptance of the final milestone, included in the total requested budget, covering:
- Defect warranty - reproducible failures against the delivered test suite, fixed at no charge. A confirmed defect in the deployed bonding-curve program is shipped as a new versioned deployment through the same path as M6/M7/M8, with migration notes for the creators and holders of affected sales.
- From the M6 testnet-0.2 delivery onward we track every LEZ and SPEL release. Where a release affects the program or the client surface we port it and publish a compatibility delta within 10 business days; independently of releases we publish a compatibility status at least quarterly, for which "no action required" is a valid status. The client surface is cheap to keep current by construction: the SPEL IDL is generated and frozen in M1 (U9), and the CLI and Basecamp mini-app both consume the single client core module rather than reimplementing it (C8), so a runtime change is one regeneration plus one module rather than three parallel updates. The lockfile pins the LEZ and SPEL revisions we build against, and the platform-triggered M7/M8 milestones already establish the re-verify-and-redeploy mechanism this maintenance follows.
- Analytics event-path activation — when LP-0012's accepted event mechanism merges into mainline LEZ, the U8 observer switches from indexer calldata,
getAccountAtBlock and intra-block replay to the native events, and the interim state-derived history is retired (C9). The observer's schema is fixed in M1 alongside the IDL, so this is activation, not redesign.
- Security-report handling - security-relevant reports follow a documented disclosure path and are acknowledged within 48 hours with a fix or a documented mitigation. A launchpad holds other people's collateral, so this is the one commitment that is not best-effort.
- Integration support for early teams composing against the program. The graduation seam exists precisely for this: close emits the graduation-relevant state - real collateral, seed reserve
R, final spot - as a documented payload, so a DEX integration (RFP-004) or a downstream product can consume it without us reopening the program. The fee-rate and treasury admin authority is separable for the same reason.
- Feature extensions beyond the RFP scope are separate engagements, not assumed here.
One handover artifact worth naming: the M0 decision record - the three declared deviations and the twelve readings, with Logos's confirmation or direction against each - is a delivered artifact, so a future maintainer can see which behaviours were specification decisions rather than implementation choices.
We can operate Synarton, not only deliver it. Everything above covers the software itself - maintenance, defects, compatibility, and disclosure. Operating a launchpad is a separate responsibility: holding the relevant admin authority, monitoring live sales, and responding when something goes wrong. If Logos would like us to take on that operational role, we are happy to scope and price it as a separate engagement. The people who would carry this responsibility are already part of the team above - Uroš as CPO, 0xcr1st0f on business development and the rest of the development team. This same team covers Synarton's bonding-curve and LBP mechanisms if both are awarded to us.
Permissions and Consent
Program Requirements
RFP ID
RFP-015 — Token Launchpad: Bonding Curve
Your Project Name
Synarton
Team or Organization Name
VacuumLabs
Primary Contact
peter.hucik@vacuumlabs.com
Team Members
Project Summary
We propose a privacy-preserving bonding-curve token launchpad on the Logos Execution Zone (LEZ): a constant-product AMM over virtual reserves, in the pump.fun lineage the RFP names as its model, with a price trajectory fully computable before a sale opens. Creators configure a sale from a token pair, a sale quantity
D, an optional DEX seedR& the virtual reserves that shape the curve; participants buy & sell from a public account directly, or privately through the deshield→trade→re-shield pattern, with a mini-app, an SDK, a CLI & sale analytics on top.Two things distinguish this proposal & both are verifiable before you award anything.
First, the pricing core already exists in Logos's own repository. RFP-015's Reference Implementation &
lez-programs/programs/amm/core/src/lib.rsare algebraically identical - checked term for term, not by inspection — including the rounding directions F1 mandates, the fee lattice & the exact-output inverse F1 requires the SDK to expose. The fee arithmetic in particular is exercised rather than dormant:feesis a parameter of the pool-creation instruction,assert_supported_fee_tiergates it on four instructions, the supported set is[1, 5, 30, 100]bps so a pool cannot be created fee-free & the stored rate feeds the kernel on every trade.price_impact_bps&spot_price_q64_64, which U4 needs, are already written & tested. The curve is multiplication & division onu128with U256 intermediates throughout: no fixed-point exponentiation, no approximation, no convergence argument to audit. We are porting a proven kernel, not deriving a new one.Second, the specification contains a defect that makes the sale unclosable, and we can prove it arithmetically. The supply target is unreachable on integer arithmetic: no integer collateral input yields exactly
Dtokens, because the output lattice near exhaustion is ~2,434 tokens wide &Dfalls strictly inside the gap. This is measured, not argued from one example — across 400 randomly generated parameter sets in the shape the RFP names as its model, none is closable. F4's auto-close therefore never fires, hard requirement R3 is unsatisfiable, F5 leaves the entire raised collateral permanently locked, and two of the five test cases S3 explicitly mandates are tests of behaviour the specification cannot produce. The remedy is a partial-fill clamp built on Logos's ownswap_exact_out_amounts— whose "the pool never comes up short" rounding is exactly what F1 asks for, and whose three failure modes are all excluded, one by F2's ownVt > Dconstraint and two by our creation-time envelope. F1's "must revert" forbids the clamp, so we request that deviation explicitly rather than shipping a silent divergence.We found eight defects in total. Four are High or Critical: the unreachable supply target above; a fee the specification credits to the reserve and pays to the treasury, leaving the books 1.0101% ahead of the vault at a 1% rate; the same fee ambiguity unresolved on the sell side, where the losing reading drains the collateral vault before a sold-out position can be sold back; and an F5 withdrawal that hands the creator the entire remaining collateral reserve at close while auto-graduation to a DEX is only a soft requirement — leaving token holders with no backing, no sell path & no DEX. All eight are set out below with resolutions. Where the specification is ambiguous we state the reading we take and what follows from it — twelve such readings, each a decision Logos can review rather than a question Logos has to answer.
We are equally explicit about what argues against this mechanism. A bonding curve structurally penalises the privacy path — a private buy is three transactions & the buyer pays the impact of whatever lands in the gap, a 6.19% worse fill with one buy ahead at sale open. No implementation removes that. We quantify it, mitigate it & disclose it rather than leaving it for an audit to find (§10).
RFP-016 is a separate submission and delivery; awarded together, the two run as one Synarton product.
Technical Approach
1. Architecture & stack
One immutable program; one client core module the mini-app, CLI and third parties all consume, so no quote or saga has a second implementation to drift from. Stack: Rust on RISC0 via SPEL,
u128with U256 intermediates reusingamm_core'smul_div_floor/mul_div_ceilverbatim so F1's rounding is inherited rather than re-implemented; QML/C++ for the Basecamp module;proptestsuites plus the two CI e2e lanes. Dual MIT + Apache-2.0 from M0.2. Platform dependencies — three RFP entries that do not match the code
Verified against local clones:
logos-execution-zone@9a7a71aand tagsv0.2.1–v0.2.4,lez-programs@72a3e74,roadmap@77a9418. Three entries in the RFP's own Platform Dependencies section do not match the code - two stale in opposite directions, and one naming a primitive the prize it cites never delivered. We flag all three, because anyone scoping this RFP will read them as current.emit_event→ 0 hits;TxReceipt→ 0 hits, at every release tag anddevHEAD. The prize is[CLOSED]and was genuinely won, but the code lives in a contributor forkgetAccountAtBlock+ intra-block replay (C9), with a documented migration when events landtransfer_authority,delegate,approvereturn 0 hits acrossprograms/token/, and "transfer" does not occur in the prize text. The RFP's own Resources list titles it "mint authorities", contradicting its dependency blockprograms/amm's pool PDA already usesChainedCallships, but the prize's own success criteria are unchecked and its distinctive deliverables are absent upstreamprograms/ata/, andcreateis idempotentspel#233(opened 2026-06-24) was closed unmerged on 2026-08-19update_config.rs, and migrate if RFP-001 landsCurrent release is v0.2.4 (2026-08-07); v0.2.1 shipped 2026-08-02. Testnet 0.3 stands at 0 of 12 roadmap milestones with no published date - which is why S6 & S7 are gated rather than dated.
3. Scope & boundaries
In scope: the immutable program (create, buy, sell, auto-close, constrained manual close, withdraw) with virtual reserves, two accounting buckets, the partial-fill clamp and per-swap fee accrual; the client core module, SDK, CLI and Basecamp mini-app; public and private paths including private sells; one-directional mode; the optional end timestamp with its minimum-duration check; analytics independent of LP-0012; the validation envelope; tests, benchmarks, README and the privacy document; deployment to testnet 0.2, with 0.3, mainnet & an external audit as gated milestones.
The three soft requirements. One-directional mode - implemented (M2); C22 relies on it as a D-7 mitigation. Optional end timestamp - implemented (M2, C26); it carries the RFP's own minimum-duration floor, the only mechanism the specification offers against the D-7 penalty, and it bounds the >98% of curves that never reach the supply target. Auto-graduation - not implemented (OOS-1); execution depends on RFP-004 and we will not price a milestone against another team's unshipped deliverable. We ship the seam: close emits real collateral,
Rand final spot as a documented payload, so whoever wires the DEX later need not reopen the program.4. The pricing core & the defect that makes the sale unclosable (D-1, DEV-1)
The constant product is
k = Vt · Vc; a buy payseff = C_in − fee& receivestokens_out = Vt − k/(Vc + eff), rounded so the residue stays with the pool. F1 mandates that rounding &amm_corealready implements it -mul_div_flooron the output,mul_div_ceilon the input, documented "so the pool never comes up short". That much is a port.F4 requires the sale to close when the sale reserve is exhausted - when
tokens_outtakes the last ofD. F1 requires a buy that would exceed the remaining reserve to revert. On integer arithmetic those two cannot both hold.Take the RFP's own shape of parameters -
Vt = 1,073,000,191e6,Vc = 30e9,D = 793,100,000e6, fee 100 bps. Exhaustion needseff* = Vc·D/(Vt − D):and collateral is an integer. The two integers either side of it give:
One unit of effective collateral moves
tokens_outby roughly 2,434 units at the boundary, so the output lattice is ~2,434× coarser than the input lattice andDfalls strictly inside the gap.This is not a property of these parameters, and we establish that by measurement. 1,000,001 integer values of
effaround the boundary produce zero hits, and 1,000,000 integerC_invalues through the fee lattice produce none either. Across 400 randomly generated pump.fun-shaped parameter sets, none is closable - 0.00%, median boundary step 1,590. And over 300 sales driven by randomised buy sequences, none reached exact exhaustion. The mechanism is structural -dQ/d(eff) = k/(Vc + eff)², so near exhaustion the output lattice is three orders of magnitude coarser than the input lattice andDhas to be struck by coincidence.The consequence chain terminates in locked funds:
tokens_out == remainingis unreachable, so F4's auto-close never fires.DEV-1 - requested deviation on F1. The single terminal buy that would exhaust the sale reserve is partially filled at exactly the remainder, with the unused collateral refunded in the same transaction, instead of reverting. Every other overshoot condition still reverts. The clamp is computed with Logos's own
swap_exact_out_amounts, whose rounding is already what F1 asks for, and whose totality we checked rather than assumed: it returnsNonein exactly three cases -amount_out >= reserve_out,reserve_in == 0, andfee_bps >= FEE_DENOM. The first is excluded by F2's ownVt > D; the other two by our creation-time envelope. So the clamp cannot fail for any reason the function is allowed to fail, on every sale, at the one call site that matters.Why the boundary cannot be closed from the client. The reasonable first instinct is an SDK-side cap: use F1's own inverse
C_in = k/(Vt − Q) − VcwithQset to the tokens remaining, so the buy is sized never to overshoot and never to revert. SettingQ = Dgives the same non-integereff*, so the client must round, and both directions fail: ceil, which F1 mandates, lands on+1,629and forces the revert the cap existed to avoid; floor lands on−805and F4 still never fires. The reason is structural rather than a rounding choice - a client can only choose an input, and every representable input maps onto a ~2,434-unit output lattice thatDdoes not sit on. No client-side sizing closes the sale, because the target is not in the image of the function.We implement the clamp. F1's must-revert and F4's supply-target close are jointly unsatisfiable on integer arithmetic, so the clamp is the only resolution that leaves F4, R3 and F5 all deliverable. The alternative is not a variant of the same build - it abandons the supply-target close and re-gates withdrawal on a soft requirement - so it is M2's first scope item rather than a late discovery. No competing proposal requests this deviation; those that reach the boundary at all either schedule "revert-on-overshoot", shipping the defect, or propose the client-side cap refuted above.
Derivation, sweep code & the totality argument: §3.5.5.
5. Where the collateral actually goes (D-5 on the buy, D-8 on the sell, D-2)
The contradiction (D-5, High). Three statements in the RFP disagree about what enters the pool on a buy. F1 & the Reference Implementation say the real collateral reserve and
Vceach increase byC_in. The Fee Structure section says the fee is deducted before the pricing formula, so the effective input isC_in − fee, and that the fee "is transferred atomically to a protocol treasury account." The fee cannot both leave to the treasury and be credited to the reserve.Simulated over 200 buys of 5 SOL at 100 bps:
kdriftVc += C_in)Vc += eff)F5's "full real collateral reserve" withdrawal is then unbacked by exactly that margin - it either underflows or fails, at the worst possible moment: on a successful sale, at close.
DEV-2 - requested deviation on F1 & the Reference Implementation. Read
C_inaseffin both, so pricing & the reserve move byeff = C_in − fee. Reversible only into insolvency, so we treat it as settled rather than optional.Where the fee sits between the buy and the treasury, and why it is not moved in the buy. The runtime settles this. A LEZ transaction carries one post-state per account: the account list must be duplicate-free (
validated_state_diffrejects a repeatedaccount_id), and where two writes to one account do reach the state diff the later silently overwrites the earlier -state_diff.insert(...)is a map insert, not a merge, so the loser leaves no error behind. A buy debiting the buyer for collateral and again for the fee would name the payer twice; one chaining a credit into the vault and also writing the vault would lose a write with nothing reported. So the fee accrues: the buy bookseffto the reserve and the remainder to an accrued-fee counter, and an authority-gatedsweep_feesmoves it to the treasury ATA in its own transaction.This is better accounting than an atomic transfer, not a concession to the platform. Revenue is recognised in the buy that earns it and moved when it is taken, and the reserve is never credited with money about to leave. The M2 done gate asserts collateral vault = booked reserve + accrued fees, exactly, after every operation - a three-term equality that fails under the literal F1 reading and additionally catches a sweep taking more than was accrued.
D-8, High - the same ambiguity, unresolved on the sell side. The Fee Structure defines two quantities on a sell,
C_outandC_out_raw; F6's floor names onlyC_out, and nothing disambiguates which the reserve moves by. Simulated over 17 buys of 5 SOL (99.74% of the reserve) then 200 sells clearing the position:kdriftC_outis the grossC_out_rawC_out = C_out_raw − fee(net)The sign is the same as D-5's, so D-8 compounds it rather than mirroring it. We take the gross: the real collateral reserve and
Vceach decrease by the pricing formula's output, the seller receivesC_out_raw − fee, and the difference accrues to the same counter and leaves on the same sweep. This is interpretation, not a fourth deviation: on the buy side F1 namesC_in, which the Fee Structure defines unambiguously, so reading it aseffis a genuine departure; on the sell sideC_outis never defined against either candidate, so choosing the solvent one is a reading.DEV-3 - requested deviation on F6. The sell-side slippage floor is checked on the seller's net proceeds (
C_out_raw − fee), not the gross the pricing formula produces. Checking a seller's minimum against a number they never receive under-protects by exactly the fee rate - 1.0000% at 100 bps. The sell quote shows gross, fee and net.D-2, Medium -
kis non-decreasing, not constant. The RFP saysk"must never change"; on integers it cannot.mul_divrounding leaves a residue, and because F1 rounds against the trader it always accrues to the pool. Over the 200-buy run above the correct reading driftskby 1.6533e-10 % and the literal one by 3.5188e+00 % - the gap is the defect, not the drift. C4: we state the invariant as non-decreasing and enforce it on chain, as a post-state assertion every operation on the U256 product, so it holds even wherekis not representable inu128. Strictly stronger than a property test, and it costs a comparison.Both simulations & the fee-lattice reuse traps: §3.5.6.
6. Close & what happens to the money (F4, F5, D-6, D-3, D-4)
D-6, High - F5 is a rug vector as written. F5 lets the creator withdraw, after close, "the full real collateral reserve plus any unused DEX seed reserve." Auto-graduation to a DEX is only a soft requirement. Take both literally and a completed sale ends with the creator holding every unit of collateral, and holders holding tokens with no backing, no sell path and no DEX - the sell path prices against a reserve that is now empty. The specification contains its own alternative and does not connect them.
C5 - the collateral destination is a creation-time parameter, defaulting to literal F5. The default is the hard requirement, since graduation execution is out of scope (OOS-1) and defaulting to a behaviour we do not ship would be worse; the alternative escrows the collateral for graduation. D-6 is handled by disclosure rather than by silently overriding a hard requirement: the creator's confirmation screen and the participant's pre-buy summary both state which destination the sale uses, so a buyer knows before paying whether anything backs the token after close. This is the most consequential non-arithmetic defect in the RFP, and the one most likely to produce a real loss.
D-3, Medium - "manual close" is required by P2 and S3 and created by no Functionality requirement. P2 asks that "a close transaction (manual or auto-triggered)" complete within one transaction and S3 mandates a "manual close" test, but no Functionality requirement grants the instruction, says who may call it, or says where the collateral goes. Unconstrained, and combined with literal F5, it is a cheaper rug than the one D-6 describes - the creator closes early and withdraws, and buyers never reach the supply target they were pricing against. C24: creator-authorised, only after a documented minimum open period, destination governed by the same F5 parameter. Authority and period are configuration.
D-4, Low-med - the sell curve is not the buy curve's inverse, and bucket re-entry is unspecified. Sells are bounded by the real collateral reserve, not the virtual one, so a sell of the same size does not return the buyer to the price they bought at, and nothing states where sold-back tokens go. We return them to the sale reserve, keeping
Dthe supply target it is defined to be - which makes the target unreachable for a second, independent reason if sells outpace buys, and the DEV-1 clamp is what keeps it closable then too.F5 analysis, the three-bucket drain assertion & C24: §3.5.9.
7. Sale creation, the validation envelope, and
k(F2)Creation fixes the pair,
D, optionalRand the virtual reserves, transfersD + Rreal tokens into program-owned PDA vaults, and initialises the two buckets: a sale reserve starting atDand decreasing with each purchase, and a DEX seed reserve starting atR, untouched until close. Virtual reserves are the net-new core - the curve prices on(Vt, Vc)while custody and the close condition track the real balances.grep -i virtual programs/ammreturns zero hits, so this is where test effort concentrates.F2 states exactly one constraint,
Vt > D. Nothing boundsVc,D,Ror the fee rate, and several unvalidated sets are degenerate or hostile: aVcsmall enough thatp₀rounds to zero,D = 0, or a fee rate at or above the denominator - whichswap_exact_out_amountsrejects withNone, rendering sales unfillable. We validate all of them at creation, in the immutable program, and publish the envelope. Notably we do not imposeVt ≥ D + R, which the RFP does not require:Vtis a pricing parameter, not a deposit.And
kis recorded, not computed with, which is what keeps the parameter space open. The RFP asks fork = Vt · Vc"computed and stored at creation" and separately requires curve state to expose it. Read as an instruction about representation, that is not implementable at the top of the plausible range: an eighteen-decimal pair givesk = 1e51against au128::MAXof about3.4e38, so a program whose arithmetic depended on a storedkwould have to refuse the sale. It does not have to - each formula folds into onemul_divwith a U256 product and a quotient proven to fitu128, an identity leaving the invariant and every rounding direction unchanged. Nor is this our invention: there is nokinamm_coreeither. The invariant is fixed at creation and reported as state; nothing prices from a storedk, and no sale is refused on magnitude.8. The private path (F3, U1, U5–U7, Priv1–Priv4, R1)
The path is three transactions - deshield (PPE) → buy (public) → re-shield (PPE) - and the middle leg staying public is a design decision, not a shortcut.
C14 - the buy stays public on both paths, and R1 is why. The curve PDA is the hottest account in the system, written by every buy, on a sale whose premise is concurrent demand at open. Any design putting the buy inside a PPE transaction fails R1 under exactly the load R1 is about: a client-proved transaction is proved against a pool snapshot, so at most one such buy survives per block and the rest are dropped and must be re-proved. The deshield touches only the buyer's private account and a fresh ephemeral, so it has no contender; the buy is an ordinary public transaction the sequencer orders against live state.
Freshness buys the absence of a contender; it does not by itself buy admission. In
privacy_preserving_circuit/src/execution_state.rs, a post-state claiming an account asserts it is uninitialised, then splits on claim kind:Claim::Authorizedon a public account assertspre_is_authorizedand fails otherwise;Claim::Pda(seed)checks only that the id matches the derived PDA, with no authorization requirement; private accounts are exempt. So a fresh public destination that is neither signed for nor claimed by derivation is refused - inside the circuit, where no receipt carries a reason. Which is why the ephemeral is derived, not merely new: Priv4 already requires non-reuse, derivation is the cheapest guarantee of it, and the same derivation supplies the claim. Where a leg needs the ephemeral to sign - the public buy does - the client holds that keypair and authorises it there.U1's atomic collateral-and-gas deshield is the one genuinely hard clause, and it is not a platform primitive. There is no one-shot "deshield token + gas" instruction: collateral is a token-program asset, native gas moves through
authenticated_transfer. Atomicity must be constructed, and it is constructible because a PPE transaction is not single-program - the circuit walks the chained-call chain and verifies each callee. The construction: one PPE transaction whose entry program emits both legs, atomic under runtime rollback, 2–3 executions against a cap of 10. The question is dormant today: the runtime does not charge fees (TODO: Make this variable when fees are implemented, no fee field on a zone transaction), so the deshield is single-asset and atomic by construction. The gas leg is built parameterised and conditional on fees landing (C25).The execution budget is not uniform, and we state which one we spend. Public execution checks
chain_calls_counter <= MAXbefore incrementing (11); the host PPE prover uses a strict>=(10), so a client-proved transaction has one fewer execution than a public one. Our public buy is 3 of 11, leaving 8 unused; the deshield is 2–3 and the re-shield 1–2 against the cap of 10. Neither the invocation limit nor cycles (MAX_NUM_CYCLES_PUBLIC_EXECUTION = 1024 * 1024 * 32) binds this program.Proving is minutes, and we treat that as a design constant rather than a risk to discover. Logos's own
cycle_benchdocuments real proving as "slow, ~minutes" and PPE composition as "very slow, ~hour", and the reason is structural: each inner program receipt is added as a circuit assumption, so the outer privacy circuit recursively verifies every program in the chain. That cost is not ours to optimise, so the private path is built as an explicitly asynchronous flow with per-leg progress and elapsed time - never a spinner.The saga engine, and why durability is not optional. A private operation that stops after the buy leaves the user's tokens on a public ephemeral account - the exposure Priv4 exists to prevent, reached by a laptop lid rather than an attack. So the saga is durably journalled before the deshield is submitted, and on restart completes the re-shield. C20: resumption is not Priv4 reuse - Priv4 forbids reuse across operations, and finishing an operation already begun is that same operation; the alternative forbids crash recovery and strands funds.
Private sells are in scope (C6). F3 is the only place the RFP generalises to "trade", and it makes both paths mandatory for program and SDK; every other instance - U1, U4–U7, all four Privacy requirements - is written as deshield→buy→re-shield. A launchpad whose privacy covers only the entry is not privacy-preserving: a private buy followed by a public sell links the position that was being protected. The saga engine is direction-generic (C23), so the mirror is incremental. If Logos intends buys only: 1.5–2 dev-weeks off M4, nothing else changes.
The trust split, stated plainly: the program enforces the money (pricing, rounding, solvency, close semantics, atomicity under rollback); the client enforces the privacy (single-use ephemerals, non-reuse, non-skippable re-shield), because the program cannot tell a fresh account from a reused one.
9. Sale analytics without the dependency the RFP calls resolved (U8)
U8 asks for collateral raised, spot price and supply sold over time per sale, without exposing identity or linking buys to accounts. The RFP points at LP-0012 structured events. Those are not delivered at any ref (§2 above), so an event-based observer would be built on a primitive that does not exist.
C9 - the observer does not depend on LP-0012. Every public transaction carries
PublicMessage { program_id, account_ids, nonces, instruction_data }- full instruction data. So the observer filters the indexer byprogram_id, decodesinstruction_dataagainst the U9 IDL, and reads the touched sale PDA viagetAccountAtBlock; where several buys land in one block it replays the pricing function over the decoded calldata to order them. That reconstructs raised, spot and supply-sold from data that exists today, and migrates to events when they land rather than being rewritten. Cost: 1–1.5 dev-weeks, and M0 proves the path against the live chain before M5 depends on it.The privacy property is the part that can fail invisibly. Aggregates are keyed by sale, never by account, and the private path's only on-chain handle is a single-use ephemeral, so there is nothing to key on; the done gate asserts the surface exposes no account-linked field.
10. D-7 - the private path costs money on a bonding curve, and we say so
On a rising curve price moves with every buy, and a private buy is three sequenced transactions, so whatever lands in the gap between a private buyer's deshield and their buy is priced in ahead of them. The RFP acknowledges this only inside a soft requirement. We quantify it - fill degradation on a 1 SOL buy, by buys landing ahead:
The penalty is worst exactly where privacy matters most - at open, when the curve is steepest and the sale most contended. Three reductions, and one disclosure of what is left. The largest is submitting the deshield and the buy in the same block: the ephemeral's id is derivable client-side before the deshield lands, so the buy needs no confirmation to be built, collapsing the exposure window rather than narrowing it - at the cost of a resubmit if the deshield is delayed, so it is a default with a clean fallback. One-directional mode (C22) removes the reflexive sell pressure that would otherwise land in the gap. The minimum-duration floor carried by the end-timestamp soft requirement is the specification's own mitigation, validated at creation so it exists in the build rather than only in the text. Then U5's disclosure states the number, so a private buyer sees the cost of the privacy they are choosing rather than discovering it in the fill.
What none of them does is remove it, and that is structural. Closing the gap entirely would take either a different pricing mechanism - which the RFP fixes - or the buy inside the PPE transaction, which fails R1 under exactly the contention R1 exists for (§8).
Full table, derivation & mitigations: §3.5.35.
11. Declared deviations, and the readings we take
We request three deviations from hard requirements. All three are forced by defects, all three are narrow, and all three are stated here rather than discovered during delivery.
tokens_outwould exceed the remaining sale reserve "must revert"tokens_out == remainingis unreachable (D-1), so F4 never fires, R3 is unsatisfiable, and F5 leaves the entire raise permanently lockedC_in"Vcincrease byeff = C_in − fee, matching the RFP's own Fee Structure sectionC_outis below the stated minimumC_out_raw − fee), not the gross the formula producesC_out(D-8), checking a seller's minimum against a number they never receive under-protects by exactly the fee rate - 1.0000% at 100 bpsTwelve ambiguities, and the readings we take. Each is a decision here, with its reasoning, rather than a question returned to Logos - so anything we read wrongly is visible now rather than at the milestone it lands in. All are reversible at kickoff as configuration or scope unless the row says otherwise.
Four of the twelve are the deviations above, plus the sell-side fee base - never disambiguated, so reserve and
Vcmove by the gross, the seller gets net and the treasury the difference: interpretation, not a fourth deviation. The remaining eight:kk- onemul_divin U256, so no parameter set is refused (§7)Full register, with per-row reasoning: §3.7.
12. Testing
Two CI lanes green on the default branch, and a skipped lane is not a passing lane: an in-process harness carrying the invariant and property suites, and a standalone sequencer job for e2e (S2). The five cases S3 names by hand each exist as a named test. Four more are the ones that would catch the defects above. Two-way solvency: over generated buy/sell sequences interleaved with sweeps, the vault equals booked reserve + accrued fees exactly and the reserve never exceeds the vault - this fails under either the D-5 or the D-8 losing reading, and twice under both. The D-1 boundary: on the canonical parameter set the terminal buy fills the remainder, refunds and closes in one transaction, where a literal-F1 implementation reverts forever. R2: failure injected at each of the buy's three executions, the sale account asserted byte-identical across it, not merely in the fields we thought to check. Priv4: two buys from one shielded account never share an ephemeral address, and killing the process mid-saga generates no second one.
13. Requirements traceability matrix
All 35 hard requirements - 34 numbered plus the unnumbered Supportability milestone mandate - owned by at least one milestone with an objective acceptance test. Zero gaps.
R= reuse of existing Logos code,N= net-new.amm_corekernel ported verbatim, rounding inherited; DEV-1 clamp at exhaustion; DEV-2 prices oneffVt/Vc/k, two bucketskrecorded rather than used as an operand, so no parameter set is refusedRprograms/ata/,createidempotent so a not-yet-existing vault is not a special case; LP-0013's dependency mis-specified and unneeded - PDA-owned vaultsmul_div; two of five values reuse existing code, price impact computed as(Vc+eff)²/Vc² − 1sinceprice_impact_bpsmeasures execution slippage, not U4's metricgetAccountAtBlock+ intra-block replay via the U9 IDL - no LP-0012 dependency (C9); keyed by sale, never by accountcycle_bench, CI-gated (C17), testnet version stated - LEZ has no CU denomination, so we name what it metersPer-requirement test detail: §3.9. Design decisions C1–C26: §3.6.
Milestones, Payout & Timeline
M0–M6 sum to 12 weeks, the upper bound of the RFP's 10–12 week estimate. We plan to the ceiling rather than under it, because two of the three deviations sit in M2's scope and we would rather absorb a re-scope inside the schedule than renegotiate it. M7 and M8 sit outside that envelope on Logos's calendar, not ours - testnet 0.3 stands at 0 of 12 roadmap milestones with no published date, so both are fixed-scope milestones triggered by platform availability (C19). M9 is the audit programme and does not fit inside a 12-week build, so we do not claim it does. Durations are calendar, not dev-weeks; three engineering streams run throughout - A on-chain, B client core/SDK/CLI/analytics, C mini-app and documentation - with the tech lead on stream A, a project manager who also takes utility development, and a part-time advisor.
M0 - Deviation sign-off, scaffolding, CI & the two measurements that are actually open. ≈1.5 wk · $12,000
Decision record confirming the three deviations & the twelve readings, DEV-1 & DEV-2 taken first because both sit in M2's scope; SPEL monorepo scaffold under dual MIT + Apache-2.0; CI with both e2e lanes; the marginal-proving spike; the analytics observer spike against the live indexer.
Every M0 gate closes on a published artifact, not on our word for it and every figure carries the commit, the pinned toolchain versions and the command that produced it - including figures that come back worse than we would like, since a measurement published only when it flatters is not a measurement. Done gate:
spel generate-idlsucceeds, an empty program builds to a guest binary and a loadable Basecamp module, versions pinned in the lockfile; CI green on both lanes; the observer spike, as a re-runnable script, pulls a real transaction from the live indexer, decodes it against the draft IDL and reads the touched account viagetAccountAtBlock- validating C9 with LP-0012 absent, against the live chain rather than against our reading of it; the chain-caller depth sweep fromcycle_bench --prove --ppecommitted with raw output, giving a marginal cost per chained call and a go/no-go on the two-leg deshield, proving time reported separately from waiting time; the private path's asynchronous shape fixed in the mini-app shell; the decision record signed off or formally directed otherwise, minuted either way — the one gate we cannot close alone, which is why it is first.M1 - Curve core. ≈2 wk · $16,000
Pricing, virtual reserves, both buckets, sale creation with the validation envelope, F6 on buy, the SPEL IDL generated and frozen (C8). Done gate: the ported kernel matches
amm_coreon shared vectors term for term; the non-decreasing-kassertion passes on chain; slippage refusal proven not to move state; thek-magnitude case prices rather than refuses; IDL committed and consumed by the core module.M2 - Lifecycle. ≈2 wk · $16,500
The DEV-1 clamp with refund, F4 auto-close atomic with the terminal buy, F5 withdrawal with the destination parameter, fee accrual plus the authority-gated sweep, DEV-3's sell-side floor, sell bounding, the constrained manual close (C24), one-directional mode, the end timestamp with its minimum-duration floor (C26). Done gate: the D-1 boundary regression - on the canonical parameter set the terminal buy fills the remainder, refunds and closes in one transaction, where a literal-F1 implementation reverts forever; the vault equals booked reserve + accrued fees, exactly, after 200 buys and across a sweep, and again after a full buy-then-sell cycle that clears the position; withdrawal rejected before close and paying the configured destination after it, with all three vaults draining to exactly zero.
M3 - SDK and CLI. ≈2 wk · $16,000
Full public-path lifecycle both roles, the exact-cost inverse exposed, U4's pre-trade summary, the U10 error taxonomy, README first pass. Done gate: CLI and SDK suites green against a standalone sequencer; the pre-buy summary asserted equal to the on-chain result; one named test per error class.
M4 - Private path. ≈2 wk · $16,500
deshield→trade→re-shield for buy and sell, the derived single-use ephemeral, durable saga journalling, non-skippable re-shield, Priv2 summary, Priv3 validation, U5-U7 guardrails, the D-7 reductions. Done gate: e2e on both private directions; the process killed mid-saga, the re-shield completing on restart with no second ephemeral generated; a public account offered to the private path rejected explicitly; two buys from one shielded account never sharing an ephemeral address; U7 refusing a deshield that covers the purchase but not the gas.
M5 - Analytics, reliability, measurement. ≈1.5 wk · $12,000
The U8 observer (IDL-driven calldata decoding, per-block PDA reads, intra-block replay), the reliability and concurrency suite, the per-operation cost table under a CI gate (C17). Done gate: the observer reconstructs a known sale's full history from indexer data alone, with no account-linked field on the surface; cycles, wire bytes and chained-call depth published with the testnet version stated; sequencer e2e green with no skipped steps.
M6 - Testnet 0.2 deployment. ≈1 wk · $8,000
Deployment, completed coverage matrix, README walkthroughs, the S5 privacy document. Done gate: deployed with the freeze commit, guest image id and deployment record published; full suite green against the deployed program; someone who did not write the README following it from a clean checkout to deploy, create a sale, buy and close.
M7 - Testnet 0.3. ≈0.5 wk · $1,500 · Trigger: Logos publishes testnet 0.3
Rebuild, redeploy, verify, delta report on anything the runtime moved, plus the C9 migration if LP-0012 has landed. Done gate: suite green against 0.3, deployment record published.
M8 - Mainnet. ≈0.5 wk · $1,500 · Trigger: Logos opens mainnet deployment
Reproducible build from a freeze commit, deployment, verification, record. Done gate: deployment verified and recorded, gated on M9 — we would not deploy an immutable program that had not been audited.
M9 - Audit programme and remediation (runs outside the 12-week build envelope)
Payout: external tier-1 audit fees billed at cost (pass-through to Logos); our remediation and re-verification (≈2 wk) quoted separately once the firm is engaged · Duration: ≈4–6 weeks calendar after the M6 freeze, ≈2 weeks of it ours; the slot is reserved at kickoff so firm lead time is absorbed before it binds.
Primary tier-1 audit by one of our two partners, Zellic or Sherlock, selected at kickoff by availability, of the immutable program & the client core, with four named focus areas - each a place this document argues the specification is unsafe: DEV-1's clamp at the exhaustion boundary, the fee accrual & sweep on both sides of the trade, the F5 withdrawal path & collateral destination & PDA vault custody across the buy's three chained executions. A second independent review on the same frozen commit is available if Logos wants it & is the one addition we would recommend. Done gate: report published with the codebase; all critical & high findings resolved or formally accepted with written rationale; CI green post-remediation including the D-1 boundary regression & the three-term solvency identity. M9 is numbered last because it is charged last, not because it runs last: it opens at the M6 freeze, runs in parallel with M7 & its done gate is a precondition for M8.
Schedule risks we are naming up front
Total Requested Budget (USD)
$100,000
Relevant Experience
What we have built on this exact problem. Vacuumlabs has delivered a production bonding-curve token launchpad end to end. It began as a Solidity system on an EVM L2; we rewrote the launch programs for Solana in Rust with Anchor & extended the product well past the feature set it started with. Three parts of it are the parts RFP-015 asks for: a bonding curve a launch trades on until it completes, with per-token curve state, buys priced against it and a defined completion condition; graduation of a completed launch into a constant-product DEX pool, including a Meteora constant-product pool on the Solana side - we place auto-graduation out of scope here (OOS-1) because its execution depends on RFP-004, but the reason C5 can specify a graduation-escrow destination precisely is that we have built the far side of it; and an indexer that reconstructs launch history from chain data - curve state per token, every buy against the curve, graduation events — serving the product's analytics from it, which is U8's problem solved the way C9 solves it. The engagement is under NDA, so this is deliberately capability-level - no client, no contract names, no parameters - and we can walk Logos through it under NDA.
A second launchpad, on Solana. Vacuumlabs designed and built Syndicate, a fully decentralized fair launchpad on Solana, frontend and backend. Between the two, the account-model shape this RFP runs on - PDAs, ATAs, IDL-driven clients, program-owned vaults - is not new to us.
Constant-product AMM correctness, examined at invariant level. We audited WingRiders, a Cardano constant-product AMM DEX, in a full-time engagement of roughly 17.5 person-days. The method is what bears on this bid: we reverse-engineered the pool's on-chain pricing independently of its documentation, extracted its treasury and fee handling, and checked the
token1 × token2 = kinvariant against live transaction data rather than against the team's account of it. That is what §5 above does to RFP-015's Reference Implementation, and it is what surfaces the two defects a constant-product launchpad is likeliest to ship with: an invariant that does not hold as stated under integer rounding (D-2), and a fee credited in one place and paid from another (D-5, D-8). Also in the public audits repository: Liqwid Finance, Ardana and VyFinance — lending, stablecoin and staking rather than AMM work, presented as track record on on-chain correctness, not domain-matched experience.Rust systems in production, and indexer-derived state. Autonom is a trustless RWA price oracle Vacuumlabs built from scratch - Rust operator nodes resolving price requests across chains and DEXes, in production behind Adrena ALP2. We have also delivered production infrastructure and client tooling for API3 and FunKit, including its Solana infrastructure. CARP, built with dcSpark, is a Cardano chain-indexing and analytics service reconstructing application-level state from raw chain data - the same shape as the launchpad indexer above and as C9.
Post-Delivery Plan
Vacuumlabs maintains the project as a team - support is not tied to any single individual. Support runs through the public GitHub repository (issues and PRs) and the primary contact listed above. Delivery includes handover of everything an operator needs: the README deployment runbook, the published program addresses with their freeze-commit and image-id records, the M9 audit report and remediation record, and the per-operation compute-cost reports. The entire project is open-sourced under MIT and Apache-2.0, so every component is forkable and community-runnable rather than dependent on us.
We provide a 6-month post-delivery support window from acceptance of the final milestone, included in the total requested budget, covering:
getAccountAtBlockand intra-block replay to the native events, and the interim state-derived history is retired (C9). The observer's schema is fixed in M1 alongside the IDL, so this is activation, not redesign.R, final spot - as a documented payload, so a DEX integration (RFP-004) or a downstream product can consume it without us reopening the program. The fee-rate and treasury admin authority is separable for the same reason.One handover artifact worth naming: the M0 decision record - the three declared deviations and the twelve readings, with Logos's confirmation or direction against each - is a delivered artifact, so a future maintainer can see which behaviours were specification decisions rather than implementation choices.
We can operate Synarton, not only deliver it. Everything above covers the software itself - maintenance, defects, compatibility, and disclosure. Operating a launchpad is a separate responsibility: holding the relevant admin authority, monitoring live sales, and responding when something goes wrong. If Logos would like us to take on that operational role, we are happy to scope and price it as a separate engagement. The people who would carry this responsibility are already part of the team above - Uroš as CPO, 0xcr1st0f on business development and the rest of the development team. This same team covers Synarton's bonding-curve and LBP mechanisms if both are awarded to us.
Permissions and Consent
Program Requirements