Velix pool
On-chain token config, AMM, redistribution and LP lock — implementation notes.
Velix pool — token mechanics and liquidity
This document describes the on-chain program in programs/velix-pool and the browser UI under /sol.php. It is the technical spec for:
- Customizable SPL supply and mint authority
- A constant-product liquidity pool (token / native SOL)
- Dynamic holder redistribution from sell-side swap fees
- LP time-locks with early withdrawal
- Authority-only liquidity adjustment (fees, penalty, pause)
The existing Token Creator (/sol.php?slug=token-creator) still mints a standard SPL token in one wallet signature. Everything below is opt-in on top of that mint.
Layout
programs/velix-pool/ Anchor workspace (Rust)
programs/velix_pool/src/ on-chain program
includes/velixpool.php program id helper
includes/_sol_pool.php PHP UI fragments
assets/js/velix-pool.js wallet client (instruction encoding, PDAs)
sol.php suite host (wallet bar + nav)Program id (placeholder until anchor deploy — then update config.php and declare_id!):
D52k4HhBhkLFY3kKEuWrkFmrwvzePNnrhXe4TWnQEdmo
Default cluster: devnet. Quote asset is native SOL (not WSOL). Token side is classic SPL (Tokenkeg…), not Token-2022.
Accounts
All PDAs are derived with the program id.
| Account | Seeds | Role | |----------------|--------------------------------------------|------| | TokenConfig | ["config", mint] | Max supply, mint-enable flag, who may print | | mint authority | ["mint_auth", mint] | PDA that holds SPL mint authority when the cap is enforced | | AmmPool | ["pool", token_mint] | Reserves, fees, reward index | | vault auth | ["vault", pool] | Signs token vault + LP mint | | quote vault | ["quote_vault", pool] | System account holding SOL | | LP mint | ["lp_mint", pool] | 9-decimal LP token | | token vault | ATA(vault, token_mint) | Token reserve | | rewards vault | ["rewards", pool] | Token account for holder dividends | | Position | ["position", pool, owner] | LP amount + unlock timestamp | | Holder | ["holder", pool, owner] | Last claimed reward index |
AmmPool.quote_mint is stored as the System Program id (11111…) to mean native SOL.
Token module — supply and mint authority
initialize_token_config(max_supply, take_mint_authority)
- Writes
TokenConfig.mintedstarts at the current SPL supply. - If
take_mint_authorityis true, CPISetAuthority(MintTokens)moves mint authority from the signer to["mint_auth", mint]. From then on the only legal print path iscontrolled_mint.
controlled_mint(amount)
- Signer must be
TokenConfig.authority. - Fails if minting is disabled, the program is not mint authority, or
minted + amount > max_supply. - CPI
MintTosigned by the mint-auth PDA.
set_token_config(mint_enabled, max_supply, mint_authority_action)
max_supplymay only stay ≥minted.mint_authority_action:0keep,1release mint authority back to config authority,2revoke (None). After 1 or 2 the program no longer enforces the cap.
Frontend: /sol.php?slug=token-config.
This is how “customizable supply and minting authority” is implemented: the wallet still creates the mint (name, decimals, initial supply, freeze/update revokes). The program then optionally owns further minting and refuses to exceed the cap.
Pool module — constant product
Invariant: token_reserve × quote_reserve is non-decreasing except when liquidity is removed.
First deposit:
lp = floor(sqrt(token_in * quote_in)) - 1000
lp_supply = lp + 1000 # 1000 is permanently locked (never minted)Later deposits take the minimum of the two reserve ratios so the price cannot be shifted by an unbalanced add:
lp = min(token_in * supply / token_reserve,
quote_in * supply / quote_reserve)Withdrawals are pro-rata:
token_out = lp * token_reserve / lp_supply
sol_out = lp * quote_reserve / lp_supplySwap (x * y = k), input amount dx:
fee = dx * swap_fee_bps / 10_000
dx' = dx - fee
out = reserve_out * dx' / (reserve_in + dx')
redistrib = fee * redistrib_bps / 10_000 # token→SOL swaps onlyThe LP share of the fee stays in the input vault (k increases). The redistrib cut of a sell (token in, SOL out) is moved to the rewards vault. Buys (SOL in) keep the whole fee with LPs — the rewards vault is token-denominated.
swap_fee_bps ≤ 1000 (10%). early_penalty_bps ≤ 5000 (50%).
Frontend: /sol.php?slug=liquidity (create + seed) and /sol.php?slug=liquidity-manage (add, remove, swap, adjust).
Redistribution module
A scaled accumulator:
PRECISION = 1e12
on token fee f, circulating = mint.supply - token_reserve
reward_index += f * PRECISION / circulating
reward_pending += fclaim_rewards:
- First call (
Holder.owner == default) registers the wallet at the current index. No retroactive share (stops a buy-claim-sell drain of the vault). - Later:
payout = token_balance * (reward_index - last_index) / PRECISION, thenlast_index = reward_index.
Frontend: /sol.php?slug=redistribute.
Early-exit token penalties also credit this index. Early-exit SOL penalties stay in the quote vault (remaining LPs).
LP lock and early withdrawal
add_liquidity(..., lock_secs):
lock_secs == 0usespool.default_lock_secs.unlock_ts = max(existing, now + lock_secs)(adding more can only extend the lock).
remove_liquidity requires now >= unlock_ts (or unlock_ts = 0).
withdraw_early requires now < unlock_ts and applies early_penalty_bps:
- User receives
(1 - penalty)of both sides. - Token penalty → rewards vault (holders).
- SOL penalty remains in the pool (other LPs).
Frontend: /sol.php?slug=lp-lock.
Token vesting for team/investors is TimeLocky, not this program (/sol.php?slug=locker).
Liquidity adjustment (authority)
adjust_pool(swap_fee_bps, redistrib_bps, early_penalty_bps, default_lock_secs, paused)
- Only
AmmPool.authority. pausedblocks swaps and new deposits; withdrawals still work.
transfer_pool_authority(new_authority) moves that role.
Instruction discriminators
Anchor sha256("global:<name>")[0..8]:
| Instruction | Hex | |-----------------------------|------------------| | initialize_token_config | 3c0e725619545d95 | | controlled_mint | 0c8f0e5c0627e24c | | set_token_config | 9ee7234e6220d2c4 | | initialize_pool | 5fb40aac54aee828 | | add_liquidity | b59d59438fb63448 | | remove_liquidity | 5055d14818ceb16c | | withdraw_early | ade6f60c72e40f6a | | swap | f8c69e91e17587c8 | | claim_rewards | 0490844774179750 | | adjust_pool | bbe70dce4424e0af | | transfer_pool_authority | 3a59745afbd127ae |
The browser client hardcodes these in assets/js/velix-pool.js. If you rename an instruction, recompute and update both sides.
Frontend flow
- Create the SPL mint (Token Creator). Revoke freeze if the pool should be public.
- Optional: Supply Cap — move mint authority onto the program.
- Create Liquidity — set fee / redistrib / penalty / lock, seed token + SOL.
- Manage Liquidity — add/remove at the current ratio, swap, adjust params.
- LP Lock — withdraw after unlock, or early-exit with the quoted penalty.
- Redistribute — register, then claim.
Wallet: Phantom / Solflare / Backpack via @solana/web3.js. Transactions are signed in the wallet; Velix never holds keys or LP tokens.
Build and deploy
Requires Solana CLI 1.18 and Anchor 0.30.1 (same pin as TimeLocky).
bash
cd programs/velix-pool
cargo test -p velix_pool --lib # CPMM / reward math
anchor build
solana-keygen grind # optional vanity program id
# put the keypair in target/deploy/velix_pool-keypair.json
anchor keys sync
# update declare_id!, Anchor.toml, config.php VELIX_POOL_PROGRAM_ID
anchor deploy --provider.cluster devnetAfter deploy, set VELIX_POOL_CLUSTER to devnet or mainnet in config.php. Point the Advanced · RPC field at a reliable endpoint (the UI default is https://solana-rpc.publicnode.com).
Tests
programs/velix-pool/programs/velix_pool/src/math.rs covers:
- integer sqrt and first-LP mint (minus
MIN_LIQUIDITY) - subsequent LP as
minof reserve ratios - swap fee + redistribution split, k non-decreasing
- reward index round-trip (
1_000tokens of fee to a 100% holder)
On-chain integration tests need a local validator (solana-test-validator) after anchor build.
Threat notes
- Do not keep mint or freeze authority if you are presenting the token as immutable. The cap module exists for controlled emissions (games, mining), not stealth inflation.
- First liquidity sets the price. Seed with amounts you mean.
- Holder dividends accrue only after
claim_rewardsregistration. - Pause is an admin switch. Transfer pool authority to a burned key if you want parameters frozen.
- This is not investment advice and not a Raydium replacement; Raydium remains available from the same UI as an external venue.