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.
| Mode | Pools | Pricing | Liquidity |
|---|---|---|---|
| Curve (0) | A launch pool, until it graduates | Constant 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_pool | Constant product on real reserves (sol_reserve, token_reserve), with an LP fee | Anyone 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.
| Field | Meaning |
|---|---|
mode | 0 curve, 1 AMM. |
launch | The launch, or zero for a pool that isn’t a launch pool. |
token_reserve | Tokens the pool trades with: the curve’s unsold tokens, or the AMM reserve. |
sol_reserve | Lamports the pool trades with. |
virtual_sol, virtual_tokens | The curve’s price reserves. Zero after graduation. |
graduation_tokens | Tokens set aside for the AMM at graduation. |
bonus_sol | Sniper fees, waiting to join the AMM’s liquidity at graduation. |
protocol_fees | HELIX’s fees not yet collected. |
lp_supply, locked_lp | All LP, and the part nobody can withdraw. |
protocol_fee_bps, lp_fee_bps | The fees in force, copied from the config at creation. |
grad_protocol_fee_bps, grad_lp_fee_bps | The AMM fees a launch pool switches to at graduation, also copied at creation. |
volume_sol, trades | Running totals. |
side: 0 buy, 1 sell. A buy spends at mostamount_inlamports; a sell sends exactlyamount_intokens.min_outis 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 withSlippageExceeded.- The trader's holding must be the trader's own (
OwnerMismatch). Prependcreate_holdingon a first buy; it is idempotent. launchis 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].
- Refused if buys are paused.
- 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).
- The four SOL fees are taken from the SOL offered; what remains is the net.
- 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.
- 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).
- 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.
- The creator fee is recorded on the launch, and the holder fee is added to the rewards, before any token moves.
- The buy burn is burned from the pool's holding, through the hook (context Buy).
- 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.
- If the trader received less than
min_out, everything is undone. - A Swap event is logged.
- If the curve has just sold out, the pool graduates, in the same transaction.
- Last, the creator and holder fees move from the pool to the launch account, and HELIX checks the pool is solvent.
- There is no pause check: sells can't be paused.
- 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.
- The sell burn is burned from what reached the pool, through the hook (context Sell).
- 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.
- The protocol, creator and holder fees are taken from that SOL. No sniper fee on sells.
- The trader must receive at least
min_out, and more than zero. - Reserves, the protocol fee, the creator fee and the holder rewards are updated; a Swap event is logged.
- 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.
create_pool
- 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
- 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 (hookAccountsdoes).
remove_liquidity
- Never paused. Pays the position's share of both reserves: LP × reserve ÷ LP supply, each.
- The tokens move through the hook (context Liquidity);
min_tokensis checked on what arrives,min_solon the SOL. - Locked LP belongs to no position, so it can never be withdrawn.
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.