Docs · liquidity engine

How creator fees become
PumpSwap liquidity.

A technical reference for how every coin's pump.fun creator fees are claimed, split into the coin and SOL, deposited into the coin's PumpSwap pool, and locked for good with the LP burned.

On this page
  1. Overview
  2. Coin lifecycle
  3. The liquidity engine
  4. 1 · Claim
  5. 2 · Split
  6. 3 · Deposit
  7. 4 · Burn
  8. Rebalance math
  9. Engine wallets
  10. Transactions
  11. Why PumpSwap
  12. The keeper
  13. Trade indexing
  14. Failure handling
  15. Data model
  16. Parameters
  17. Trust model
  18. FAQ

Overview#

Every coin launched here is a standard pump.fun coin: minted with create_v2 on the bonding curve, Token-2022, 1,000,000,000 supply, graduating to PumpSwap when the curve completes. The only difference is who the coin's creator is.

On pump.fun the creator fee is income for whoever launched the coin. Here, the creator is a per-coin engine wallet, and a keeper process turns its income into liquidity: half the value is converted into the coin, the two sides are deposited into the coin's pool, and the LP tokens are burned.

Every trade on a liquidtokens.fun coin, on the curve or on PumpSwap, here or on pump.fun, makes its pool permanently deeper. Liquidity added by the engine can never be withdrawn by anyone.

Coin lifecycle#

PhaseWhere fees accrueWhat the engine does
Launch—The server derives the engine wallet, sets it as creator in create_v2, and the launcher signs as fee payer.
On the curveCurve creator vault (SOL)Claims, buys the coin on the curve with half the value, holds the rest as SOL: a 50/50 inventory.
Graduation—pump.fun migrates liquidity to the canonical PumpSwap pool. The engine's inventory is now deployable.
On PumpSwapAMM creator vault (WSOL)Claims and unwraps, rebalances to the pool price, deposits both sides, burns the LP.

The liquidity engine#

Each keeper cycle runs four steps per coin. Each step is its own transaction, simulated before it is sent.

1 · Claim#

When the creator vaults hold at least 0.01 SOL, both are swept to the engine wallet in one transaction: collect_creator_fee on the curve program (lamports) and collect_coin_creator_fee on PumpSwap (WSOL), followed by CloseAccount on the WSOL account to unwrap it to native SOL.

2 · Split#

The engine targets an inventory that is 50% coin and 50% SOL by value. Before graduation it buys on the bonding curve; after graduation it buys on PumpSwap with buyQuoteInput. The buy size comes from the rebalance math, so tokens left over from earlier rounds are used before buying more.

3 · Deposit#

Once the coin has graduated and the spendable SOL is at least 0.02 SOL, the engine deposits into the canonical pool. The deposit is sized by SOL with slippage headroom, and falls back to sizing by tokens when the pool price moved and the coin side is the constraint. Dust that does not pair stays in inventory for the next cycle.

4 · Burn#

The LP tokens minted by the deposit are burned with a Token-2022 Burn in the same transaction. The pool keeps the reserves; nobody holds the claim on them. Each coin page shows the pool's burned share as lp_burned / lp_supply.

Rebalance math#

Let S be spendable SOL (balance minus the 0.01 SOL rent reserve), T the engine's token balance and p the price in SOL per token.

On the curve (p = virtualSol / virtualTokens):
  value  = S + T·p
  buy    = value · 0.5 − T·p          // bring the coin side to 50%
  if buy ≥ 0.002 SOL and buy ≤ S: buy on the curve

On PumpSwap (p = poolQuote / poolBase):
  buy    = (S − T·p) / 2                // equal value on both sides at the pool price
  if buy ≥ 0.002 SOL: buyQuoteInput(buy)

Deposit:
  quote  = S / (1 + 2%)              // leave room for maxQuote
  r      = depositQuoteInput(quote)
  if r.maxBase > T:                     // coin side is short
    r    = depositBaseInput(T / (1 + 2%))
  deposit(r.lpToken, r.maxBase, r.maxQuote) + burn(r.lpToken)

Because the PumpSwap buy happens immediately before the deposit, the post-buy price is slightly above p. The fallback to base-sized deposits absorbs that drift; the unpaired remainder is a few basis points and is deployed next cycle.

Engine wallets#

Each coin has its own engine wallet, so its fees, inventory and deposits never mix with another coin's. The key is derived, never stored:

seed   = HMAC-SHA256(KOTH_MASTER_SEED, "koth:fee-authority:" + mint)
engine = Ed25519.fromSeed(seed)

The launch route derives it to set creator in create_v2; the keeper re-derives it to sign. The public key is stored with the coin and checked against the derivation before every engine run. The engine wallet pays its own token-account rent from a 0.01 SOL reserve that is never deployed; the keeper wallet pays network fees.

Transactions#

StepInstructionsSigners
ClaimComputeBudget · collect_creator_fee · collect_coin_creator_fee · CloseAccount(WSOL) · rent refund to keeperkeeper, engine
Curve buyComputeBudget · create ATA (idempotent) · buykeeper, engine
Pool buyComputeBudget · wrap SOL · buy (buyQuoteInput) · unwrapkeeper, engine
Deposit + burnComputeBudget · wrap SOL · create LP ATA · deposit · unwrap · Burn(LP)keeper, engine

All are v0 transactions compiled against pump.fun's address lookup table, with a 600k compute-unit limit.

Why PumpSwap#

pump.fun coins graduate into a canonical PumpSwap pool, and that is where their price and volume live. Depositing anywhere else, such as a separate Raydium pool, would split liquidity across two venues, leave the main pool thin, and hand the spread between them to arbitrageurs. Deepening the canonical pool is the only placement that lowers slippage for every trader. It also closes the loop: PumpSwap trades pay creator fees back into the engine.

The keeper#

A long-running process (pnpm keeper) owns every state transition. Each cycle (default 15s):

  1. Resolve launches whose browser confirmation timed out.
  2. Refresh bonding-curve state for all live coins, 100 accounts per RPC call.
  3. Per coin: index new trades → snapshot holders (at most once a minute) → run the engine.
pnpm keeper            # live
pnpm keeper --dry-run  # simulate every engine transaction, send nothing
pnpm keeper --once     # single cycle, then exit

Trade indexing#

Trades come from chain, so trades placed on pump.fun appear too. The keeper pages getSignaturesForAddress on the bonding curve and, after graduation, on the pool, then parses each transaction with jsonParsed (v1 transactions included):

  • Curve: SOL is the curve account's lamport delta; the trader is the largest token-balance change for the mint.
  • Pool: SOL and tokens are the pool vaults' deltas. When both vaults move in the same direction it is a deposit or withdrawal, not a trade, and it is skipped.

Failure handling#

FailureBehaviour
Simulation failsNothing is sent. An error row is written to the engine log (deduplicated for 10 minutes) and the step retries next cycle.
Sent but confirmation times outThe next cycle reads real balances, so no state is double-counted.
Keeper offlineFees keep accruing in the vaults. Nothing is lost; the next run claims everything.
Engine wallet mismatchThe coin is skipped and logged. Nothing is signed.
Coin never graduatesInventory stays on the engine wallet as coin + SOL. Its buys still supported the price on the curve.

Data model#

Postgres (Supabase), every table prefixed koth_, row-level security on, public read, server-only writes.

TableKeyHolds
koth_coinsmintMetadata, engine wallet, curve and pool snapshots, engine totals (claimed, bought, deposited, burned).
koth_engine_eventsidOne row per claim, buy, deposit+burn or error, with amounts, pool depth after, and signature.
koth_tradessignatureSide, SOL, tokens, price and trader for curve and pool trades.
koth_holders(mint, wallet)Balance, share and kind (wallet, curve, pool, engine, program).

Parameters#

The live values, set in src/lib/koth.ts and picked up by the keeper on restart.

KeyValueNote
claim.minSol0.01 SOLVaults below this wait, so claims never cost more than they collect.
claim.sourcescurve SOL · AMM WSOLBoth creator vaults are swept in one transaction; WSOL is unwrapped.
split.tokenBps50%Target share of inventory value held as the coin.
split.rebalancePool ratioAfter graduation, buys only what is needed to pair SOL at the pool price.
swap.curve.slippage3%Bonding-curve buys before graduation.
swap.amm.slippage3%buyQuoteInput on the canonical pool.
deposit.minSol0.02 SOLInventory below this keeps accumulating.
deposit.slippage2%Bounds maxBase / maxQuote on the deposit.
lp.modeBurnBurned in the deposit transaction: added liquidity can never be withdrawn.
reserve.sol0.01 SOLNever deployed; pays token-account rent.
keeper.cycle15 sIndex trades → refresh curves → snapshot holders → run engine.
keeper.feePayerKeeper walletPays network fees; engine wallets pay their own rent.
launch.creatorHMAC(seed, mint)Per-coin engine wallet set as the create_v2 creator. Fixed at mint.
launch.maxDevBuy50 SOLOptional buy in the launch transaction.
launch.supply1,000,000,000Token-2022, 6 decimals, mint authority revoked.

Trust model#

  • Trading, curve math, graduation and pool accounting are pump.fun's programs. The site only builds transactions.
  • Whoever holds KOTH_MASTER_SEED controls every engine wallet's unclaimed fees and inventory. LP that has been burned is out of anyone's reach, including ours.
  • Every engine action is a public transaction linked from the Engine page.
  • The launch server never broadcasts a transaction that does not match the prepared mint and fee payer.

FAQ#

Does the creator earn anything?+

No creator fee income. Creators get a coin whose liquidity grows with its volume, and can take the dev buy in the launch transaction.

What happens to fees before graduation?+

They are claimed and split: half buys the coin on the curve, half is held as SOL. That inventory is deposited the moment the coin graduates.

Can the added liquidity be pulled?+

No. The LP tokens are burned in the same transaction as the deposit.

Why not Raydium?+

Graduated pump.fun coins trade on PumpSwap. A second pool elsewhere would split liquidity instead of deepening it. See Why PumpSwap.

liquidtokens.fun+ Create token