Solana tools · User guide

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:

  1. Customizable SPL supply and mint authority
  2. A constant-product liquidity pool (token / native SOL)
  3. Dynamic holder redistribution from sell-side swap fees
  4. LP time-locks with early withdrawal
  5. 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. minted starts at the current SPL supply.
  • If take_mint_authority is true, CPI SetAuthority(MintTokens) moves mint authority from the signer to ["mint_auth", mint]. From then on the only legal print path is controlled_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 MintTo signed by the mint-auth PDA.

set_token_config(mint_enabled, max_supply, mint_authority_action)

  • max_supply may only stay ≥ minted.
  • mint_authority_action: 0 keep, 1 release mint authority back to config authority, 2 revoke (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_supply

Swap (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 only

The 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 += f

claim_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, then last_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 == 0 uses pool.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.
  • paused blocks 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

  1. Create the SPL mint (Token Creator). Revoke freeze if the pool should be public.
  2. Optional: Supply Cap — move mint authority onto the program.
  3. Create Liquidity — set fee / redistrib / penalty / lock, seed token + SOL.
  4. Manage Liquidity — add/remove at the current ratio, swap, adjust params.
  5. LP Lock — withdraw after unlock, or early-exit with the quoted penalty.
  6. 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 devnet

After 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 min of reserve ratios
  • swap fee + redistribution split, k non-decreasing
  • reward index round-trip (1_000 tokens 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_rewards registration.
  • 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.