← Hypotenuse Labs

Cega Finance · SVM/EVM (via Hypotenuse Labs)

Solana options program — parity with EVM

Cega’s exotic-options structured products on Solana, matched to the behavior of the established EVM implementation — an autocallable vault program with knock-in/knock-out barriers monitored on-chain.

x.com/cega_fi ↗

Stack

  • Rust
  • Anchor 0.30
  • Solana (SPL Token)
  • Pyth (pull oracle, PriceUpdateV2)
  • Metaplex token metadata
  • TypeScript SDK
  • ts-mocha E2E suites
  • Solidity / EVM (parity target)

Protocol, in one paragraph

Cega is an on-chain vault for exotic, yield-bearing options. Depositors put in USDC and earn a fixed-APR coupon; in exchange their principal is sold as a worst-of basket of puts with knock-in / knock-out barriers. The vault itself never takes the option risk: at trade time it moves the pooled USDC out to whitelisted market makers OTC and keeps only custody, accounting, and barrier-monitoring on-chain. A permissionless crank reads Pyth each day to accrue the coupon and flip knock-in / knock-out; a knock-out auto-calls and ends the epoch early. The mature version lived on EVM, and the engagement was to bring the Solana program to behavioral and economic parity, so both settle the same products to the same numbers.

What I shipped — deposit/withdraw queue reads, from O(N) to O(1)

The problem. Deposits and withdrawals that land while no window is open sit in an on-chain queue. It was built as a singly-linked list: a header with head and tail pointers, and each entry its own account that points to the next one. The catch is how those entry accounts were addressed — each was keyed by a running counter, so nothing could compute a given user's entry address up front. To show “your pending deposit” in the app, the client had to start at the head and hop pointer by pointer through the whole list, one network round-trip per node, because it only learns the next address after reading the current one. The more people in the queue, the slower it got — for everyone.

My fix. I gave each user one deterministic account instead — keyed by the product and their own wallet ([deposit-queue, product, user]), accumulating their pending amount. Now the client computes that exact address and reads it in a single call, with no walking the list and no full-program scan. Cancelling is just closing that one account. I verified in the program first that the linked list was load-bearing on-chain and there was no per-user index to read from, so this was the right lever, not a client workaround.

Before: the app reads head then follows next pointers through counter-keyed nodes, one network round-trip per node. After: the app derives one per-user account address and reads it in a single call.
Before, reading one user's pending position cost a hop per node down the whole list; after, it is one address and one call — no matter how big the queue gets

Why it matters. Client views load faster and stay fast as the protocol grows: the queue panel reads in one round-trip instead of dozens, so the UI feels instant and the read cost no longer depends on how many other users are in line. The one thing it trades away is strict first-in first-out ordering across users, and I checked that this is free: every batched deposit is minted at the same price, so the order entries are processed in never changes anyone's payout.

Cega is a third-party client protocol; this engagement worked against a private codebase, so no public repository is linked.