Programs

Options engine


Copied from the Tender programs' own docs on 2026-09-28: current.

Program id (mainnet): DX36A7TVBCynjRaMoptt341QrDRJCkxR71M992Th3udF. M1 at commit 78ae4fc (2026-09-28); M3 (47ea6a4) added exercise_cashless_pump and made sol_usd public so the launch vault can price premiums with the same checks. Described as of c18c8c1. The put vaults do not use the engine.

Source for this page: programs/tender/src/state.rs and lib.rs at c18c8c1; SPEC-SOLANA section 1; commit 47ea6a4.

Series account

One Series is one (underlying, quote, strike, expiry, kind, tier, writer, cashless pool, exercise window, strike unit) contract.

FieldTypeMeaning
underlying_mint, quote_mintPubkeythe two listed mints; must differ
long_mint, short_mintPubkeyclassic SPL mints created per series
underlying_vault, quote_vaultPubkeythe series' collateral accounts
underlying_token_program, quote_token_programPubkeyclassic SPL Token or Token-2022, per mint
strike_num, strike_denu64strike as a rational in lowest terms: quote base units (or micro-dollars) per underlying base unit
expiry_tsi64a whole UTC hour
kindu80 call, 1 put
tieru80 TIER_OPEN (anyone writes; writer must be the default key), 1 TIER_A (calls only, one named writer)
writerPubkeythe only address allowed to write, or the default key
cashless_poolPubkeythe pool the cashless path sells into (a Raydium CPMM pool, or a PumpSwap pool for exercise_cashless_pump), or the default key; calls only
exercise_windowu32seconds; 0 = American, otherwise exercise only in the last N seconds before expiry
strike_unitu80 UNIT_QUOTE, 1 UNIT_USD
bumpu8

Source: programs/tender/src/state.rs at 78ae4fc.

Seeds

AccountSeeds
Series["series", underlying_mint, quote_mint, strike_num (u64 le), strike_den (u64 le), expiry_ts (i64 le), [kind], [tier], writer, cashless_pool, terms]
terms (5 bytes)exercise_window (u32 le) followed by strike_unit
LONG mint["long", series]
SHORT mint["short", series]
Underlying vault["uvault", series]
Quote vault["qvault", series]

Because the strike must be in lowest terms and the expiry must be canonical, one option has exactly one address. The pool is part of the address, so a series cannot be squatted with a thin look-alike pool.

Source: programs/tender/src/state.rs and lib.rs (InitSeries accounts) at 78ae4fc.

Instructions

InstructionWhoEffect
init_series(strike_num, strike_den, expiry_ts, kind, tier, writer, cashless_pool, exercise_window, strike_unit)anyoneCreates the series and its mints and vaults. Refuses: a strike not in lowest terms, an expiry in the past or not a whole UTC hour, Tier A that is not a call or has no writer, TIER_OPEN with a writer, same mint twice, UNIT_USD unless a call quoted in wSOL, a cashless pool on a put, an inadmissible mint
write(amount)writer (Tier A) or anyone (open)Before expiry. Call: deposits amount of underlying. Put: deposits strike x amount of quote, rounded up. Mints amount LONG and amount SHORT to the writer
close_position(amount)holder of both legsBefore expiry. Burns amount LONG and SHORT and returns the collateral (put side rounds down)
exercise(amount)LONG holderInside the exercise period. Call: pays the strike (rounded up) and receives amount underlying. Put: delivers amount underlying and receives the strike (rounded down). The holder's leg moves in first
exercise_cashless(amount, min_proceeds)LONG holderCalls with a pinned pool only. Sells amount of the series' own underlying into the pinned pool, keeps the strike, pays the holder the rest; fails unless proceeds are at least strike plus min_proceeds, so an out-of-the-money or sandwiched attempt reverts and the holder keeps the option. This path is Raydium CPMM
exercise_cashless_pump(amount, min_proceeds)LONG holderSame shape through the series' pinned PumpSwap pool: the series PDA is PumpSwap's user and sells exactly amount with PumpSwap sell, keeps the strike, pays the holder the rest. PumpSwap pays out at most the pool's real SOL (its virtual BOOST quote is pricing only), so a sale into a drained pool fails and the holder keeps the option
settle(amount)SHORT holderAt or after expiry. Burns amount SHORT and pays amount / SHORT supply of each vault as it stands now

Source: programs/tender/src/lib.rs at 78ae4fc and c18c8c1 (exercise_cashless_pump from 47ea6a4).

Exercise windows

exercisable(now) is true when now is before expiry_ts and either exercise_window is 0 or now is at least expiry_ts minus exercise_window. The window is therefore [expiry - window, expiry). There is no post-expiry exercise. M1 added windows for European puts written through the engine (1,800 s in the first put spec); the as-built put vault settles in cash and does not use the engine, so no current product sets a window.

Source: state.rs Series::exercisable at 78ae4fc; SPEC-SOLANA section 3.

USD strikes and the Pyth checks

For UNIT_USD, strike_num / strike_den is micro-dollars per underlying base unit. At exercise (physical or cashless) the engine computes the USD strike for amount (rounded up), reads the posted PriceUpdateV2, and converts to lamports as usd_micro x 1,000 x 10^(-expo) / price, rounded up because the exerciser pays. Example from the unit test: $150 of strike at SOL = $120 is exactly 1.25 SOL.

The account must pass every check or the instruction fails with BadPrice:

  • 8-byte discriminator of PriceUpdateV2, owned by the Pyth receiver rec5EKMGg6MxZYaMdyBfgwp4d5rB9T1VQH5pJv5LtFJ;
  • verification level Full;
  • feed id is SOL/USD (ef0d8b6f...b56d);
  • price above 0, exponent between -18 and 0;
  • published no later than now and at most 60 s before now;
  • confidence at most 1% of the price (100 bps).

A refused price never voids the option. The exerciser posts a fresh update in the same transaction and retries. Report 22 section 7 is explicit that refusing a stale price at expiry must not turn into a void predicate.

Auditor note: any Full-verified update published in the last 60 s passes, so an exerciser can choose the most favourable one in that span. With SOL/USD moving 0.61% per hour (sd, report 22 section 7) the option this gives is small, but it is not zero.

Source: lib.rs sol_usd and strike_quote, state.rs usd_micro_to_lamports at 78ae4fc; SPEC-SOLANA section 1.

Admitted mints

  • Classic SPL Token and Token-2022 only.
  • No freeze authority, with one exception: USDC (EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v), admitted in M1 for the then-physical put vaults. Circle's freeze power is the standard risk of any USDC product; every other freezable mint is refused.
  • Token-2022 extensions must be on the allowlist: MetadataPointer, TokenMetadata, GroupPointer, TokenGroup, GroupMemberPointer, TokenGroupMember. Anything else, and any extension the build cannot parse, is refused.
  • TransferFeeConfig is admitted only when the transfer fee config authority is empty and the older and newer fees are equal, so the fee can never change during a series. ZCAT qualifies (3%, authority null), though with cash-settled put spreads no product currently moves ZCAT through the engine.

Source: lib.rs admit_mint at 78ae4fc; SPEC-SOLANA section 1.

Transfer-fee handling

  • Deposits are grossed up. For a fixed-fee mint the sender sends amount plus the inverse epoch fee, so the vault receives amount. The vault's balance must rise by at least amount and at most the gross; any excess from rounding stays in the vault.
  • Withdrawals are checked net. The vault's balance must fall by exactly amount and the recipient's must rise by exactly amount minus the epoch fee.
  • For every other mint the fee is 0 and both checks reduce to exact equality.

Source: lib.rs deposit, withdraw and transfer_fee at 78ae4fc.

Invariants

  • For a call series before expiry, underlying_vault equals LONG supply.
  • Deposits round up, withdrawals round down, so dust accrues to the vault and the last claimant is never short. Two partial closes never reclaim more than one whole write deposited.
  • settle divides each vault as it stands by the SHORT still outstanding. There is no frozen snapshot, so a seizure between claims is shared by the claims still outstanding, and the last claimant sweeps the dust.
  • Only the named writer can write a Tier A series; Tier A is calls only.
  • A put's exercise and a call's physical exercise do not depend on any pool, so they keep working if a DEX is switched off.

These are checked by the unit tests in state.rs (11) and the LiteSVM suite (tests-svm/tests/tender.rs: 28 tests at 78ae4fc; 22 at c18c8c1, after the 6 tests of the old StonkFun vault left with that program in 47ea6a4). The PumpSwap cashless path is exercised by the launch vault's cash-out test in tests-svm/tests/launch.rs.