Skip to content
helixLaunch

Docs

DEX

One pool per token, in SOL

Every HELIX token has at most one pool, at ["pool", mint], quoted in native SOL: no wrapped SOL, no token pairs. The pool account holds the SOL itself; its tokens sit in the pool's own holding, ["holding", mint, pool]. Because the DEX is part of the token program, every trade runs the token's hook and tells it whether it is a buy or a sell.

ModePoolsPricingLiquidity
Curve (0)A launch pool, until it graduatesConstant product on virtual reserves (virtual_sol, virtual_tokens)Buys and sells only. Adding or removing liquidity is refused (CurveLocked).
AMM (1)A graduated launch pool, or a pool made with create_poolConstant product on real reserves (sol_reserve, token_reserve), with an LP feeAnyone can add and withdraw. Locked LP stays for ever.

The curve and its graduation into an AMM are described on Launches.

216 bytes. Its lamports are always at least its rent plus sol_reserve + bonus_sol + protocol_fees; HELIX checks this at the end of every instruction that touches it.

FieldMeaning
mode0 curve, 1 AMM.
launchThe launch, or zero for a pool that isn’t a launch pool.
token_reserveTokens the pool trades with: the curve’s unsold tokens, or the AMM reserve.
sol_reserveLamports the pool trades with.
virtual_sol, virtual_tokensThe curve’s price reserves. Zero after graduation.
graduation_tokensTokens set aside for the AMM at graduation.
bonus_solSniper fees, waiting to join the AMM’s liquidity at graduation.
protocol_feesHELIX’s fees not yet collected.
lp_supply, locked_lpAll LP, and the part nobody can withdraw.
protocol_fee_bps, lp_fee_bpsThe fees in force, copied from the config at creation.
grad_protocol_fee_bps, grad_lp_fee_bpsThe AMM fees a launch pool switches to at graduation, also copied at creation.
volume_sol, tradesRunning totals.
swap(side u8, amount_in u64, min_out u64) · tag 33 accounts: trader (s, w), config, mint (w), pool (w), pool_holding (w), trader_holding (w), launch (w), system, …hook
  • side: 0 buy, 1 sell. A buy spends at most amount_in lamports; a sell sends exactly amount_in tokens.
  • min_out is the least the trader accepts: tokens received on a buy, lamports received on a sell, both after every fee and any hook cut. Below it the transaction fails with SlippageExceeded.
  • The trader's holding must be the trader's own (OwnerMismatch). Prepend create_holding on a first buy; it is idempotent.
  • launch is the pool's launch; for a pool that isn't a launch pool it may be any account, and is not read. The built-in kit runs on it, so a kit token needs no hook accounts; an external hook needs [hook_program, hook_signer, …extras].
  1. Refused if buys are paused.
  2. Rates: the pool's protocol fee; on a launch pool also the creator fee, the buy burn, the holder fee (only if enough tokens are held to share it) and the sniper fee (unless this is the creator's first buy, which uses up its one exemption).
  3. The four SOL fees are taken from the SOL offered; what remains is the net.
  4. Tokens out: on the curve, from the virtual reserves; on the AMM, constant product on the real reserves with the LP fee taken from the net.
  5. If the buy would take every token left on the curve, it takes exactly those, and the SOL charged is recomputed to what they cost (rounded up, never more than offered).
  6. The trader pays the pool. The protocol fee is set aside in the pool, the sniper fee goes to the bonus, the net to the reserves.
  7. The creator fee is recorded on the launch, and the holder fee is added to the rewards, before any token moves.
  8. The buy burn is burned from the pool's holding, through the hook (context Buy).
  9. The rest moves from the pool to the trader through the hook (context Buy); a custom hook may cut it, the kit checks its rules.
  10. If the trader received less than min_out, everything is undone.
  11. A Swap event is logged.
  12. If the curve has just sold out, the pool graduates, in the same transaction.
  13. Last, the creator and holder fees move from the pool to the launch account, and HELIX checks the pool is solvent.
  1. There is no pause check: sells can't be paused.
  2. The tokens move from the trader to the pool through the hook (context Sell). A custom hook's cut comes out of them; the kit checks the creator and early-buyer locks.
  3. The sell burn is burned from what reached the pool, through the hook (context Sell).
  4. The rest is priced: on the curve from the virtual reserves, never more than the real SOL reserve; on the AMM, constant product with the LP fee taken from the tokens in.
  5. The protocol, creator and holder fees are taken from that SOL. No sniper fee on sells.
  6. The trader must receive at least min_out, and more than zero.
  7. Reserves, the protocol fee, the creator fee and the holder rewards are updated; a Swap event is logged.
  8. Last, the SOL moves to the trader and the creator and holder fees to the launch account.

In both directions, SOL leaves the pool by direct lamport moves made after every cross-program call of the swap (hooks, system transfers), never in between.

quoteBuy and quoteSell in the SDK repeat the program's arithmetic to the lamport, and the tests check that a swap does exactly what was quoted. They don't include a custom hook's cut, which only the hook knows; set min_out with that in mind.

curve buy: tokens = vt − ceil(vs × vt / (vs + net)) curve sell: SOL = vs − ceil(vs × vt / (vt + tokens)) AMM: out = reserve_out × in' / (reserve_in + in'), in' = in − floor(in × lp_fee / 10,000)

create_pool

create_pool(token_amount u64, sol_amount u64) · tag 30 accounts: payer (s, w), config (w), mint, pool (w), pool_holding (w), payer_holding (w), position (w), system, …hook
  • For any HELIX token that has no pool yet; a second one fails (PoolExists). Kit tokens can't: they come with their launch pool. Refused while new pools are paused.
  • The tokens move through the hook (context Liquidity); LP is √(tokens that arrived × SOL), and must be more than 1,000.
  • 1,000 LP are locked for ever (MIN_LIQUIDITY); the creator's position at ["position", pool, owner] gets the rest.
  • The pool takes the config's AMM fees at that moment (0.05% protocol, 0.25% LP by default) and keeps them.

add_liquidity

add_liquidity(sol_amount u64, max_tokens u64, min_lp u64) · tag 31 accounts: owner (s, w), config, mint, pool (w), pool_holding (w), owner_holding (w), position (w), system, …hook
  • AMM pools only, and refused while new pools and liquidity are paused.
  • Tokens needed: ceil(SOL × token reserve ÷ SOL reserve), at most max_tokens. They move through the hook (context Liquidity).
  • LP minted: the smaller of SOL × LP supply ÷ SOL reserve and tokens that arrived × LP supply ÷ token reserve, at least min_lp.
  • For a kit token, pass [launch] as the hook accounts (hookAccounts does).

remove_liquidity

remove_liquidity(lp u64, min_sol u64, min_tokens u64) · tag 32 accounts: owner (s, w), mint, pool (w), pool_holding (w), owner_holding (w), position (w), …hook
  • Never paused. Pays the position's share of both reserves: LP × reserve ÷ LP supply, each.
  • The tokens move through the hook (context Liquidity); min_tokens is checked on what arrives, min_sol on the SOL.
  • Locked LP belongs to no position, so it can never be withdrawn.
collect_protocol_fees · tag 34 accounts: config, pool (w), fee_collector (w)

Permissionless: anyone can call it, for any pool. It sends the pool's accumulated protocol fees to the fee collector named in the config, and nowhere else.