P10

Docs

P10 technical documentation

How the index is built, priced and traded. Every number on this page is read from the code that runs the site, so the docs change when the code changes.

#Overview

P10 ($P10) is a fixed index of the 10 strongest pump.fun coins. The members were picked once, on 3 November 2026, 09:46 UTC, by the rules in lib/index/rules.js, and are held the way the S&P 500 holds its members: a fixed number of units of each coin, no rebalances, no rotation. The level started at 1,000 at launch time, 3 November 2026, 00:00 UTC.

ComponentWhat it doesState
IndexLive level of 10 fixed members, refreshed every 30 sLive
Paper bookPooled long/short book on the level, no leverage, no liquidationLive, paper USDC
Leveraged bookPooled long/short book, 1x to 5x, USDC margin in a capped custodial vaultSimulated until armed
Custom indexesYour own basket of 2 to 10 pump.fun coins with its own leveraged bookSimulated until armed
VaultCollects fees, buys the ten by weight and burns themDry: real quotes

Rules that hold across all of it: the house never takes a side (longs trade against shorts), the vault never pays a trader, money in the ledger is integer micro-USDC, and every on-chain transaction is simulated against mainnet before anything else happens. Until custody is armed, the leveraged book runs in simulated mode: play USDC, with every deposit and withdrawal simulated against mainnet.

#Universe and selection

Universe

The pick script built the universe from two sources:

  • CoinGecko's Pump.fun Ecosystem category, top 500 by market cap, mapped to Solana mints with CoinGecko's platform list.
  • Jupiter's top-traded, top-trending and top-organic-score 24h lists (100 each), filtered to tokens Jupiter tags as pump.fun launches.

Union: 530 mints. Each mint was looked up on Jupiter's token API (100 per call) and kept only if Jupiter reports launchpad: "pump.fun". 477 passed verification.

Selection rules

Verified coins were sorted by market cap, descending, and walked from the top. A coin took a seat only if it passed every rule below, checked in this order; the first rule it failed is recorded as the reason. The walk stopped at 10 seats, after 23 coins had been screened.

#RuleThresholdData
1Mint and freeze authorityboth disabledJupiter audit
2Age since first poolat least 30 daysJupiter firstPool.createdAt
3Liquidityat least $500KJupiter, USD in pools
4Liquidity / market capat least 2%Jupiter
5Holdersat least 10,000Jupiter holderCount
6Top holdersat most 55%Jupiter audit.topHoldersPercentage
724h volumeat least $250Kmax(Jupiter on-chain, CoinGecko all-venue)
8Drawdown from 30-day highat most 70%Jupiter daily candles
Volume and drawdown
volume24h = max(jupiter.buyVolume24h + jupiter.sellVolume24h,
                coingecko.total_volume)
drawdown  = 1 - close(last daily candle)
                / max(high over the last 31 daily candles)
no candles at all: cut ("no 30-day price history")

Volume takes the larger of the two sources because Jupiter only sees on-chain swaps; for large coins most volume trades on centralized venues. Rule 8 is fetched last, only for coins that passed rules 1 to 7.

#Weighting and level

Capped market-cap weights

Each member was weighted by its market cap at the pick, with a 20% cap per coin. The cap is applied iteratively (cappedWeights in lib/index/engine.js):

raw_i = cap_i / Σ cap_j
repeat, at most n rounds:
  free = Σ cap_j over coins not yet fixed
  left = 1 - Σ w_j over fixed coins
  w_i  = cap_i / free × left     for every coin not yet fixed
  if any w_i > 0.2: set it to 0.2, mark it fixed, repeat
  else: stop
if 0.2 × n < 1 the cap is raised to 1/n

In this pick Fartcoin had 34.1% of the ten's combined cap and was clipped to 20%. The remaining 80% was spread over the other coins in proportion to their caps, which scaled each of their raw shares by 1.2149.

Units, fixed at launch prices

Weights become a fixed number of units of each coin using its price at launch time t0 = 3 November 2026, 00:00 UTC. The base price is the close of the last 1-hour Jupiter candle at or before t0.

Index level
w_i      weight of coin i at launch (capped)
p_i(t)   live USD price of coin i
p_i(t0)  base price, at launch time
B        base level = 1,000

u_i    = w_i / p_i(t0)                  units, fixed forever
D      = Σ u_i × p_i(t0) = 1             divisor, 1 by construction

L(t)   = B × Σ u_i × p_i(t) / D
       = B × Σ w_i × p_i(t) / p_i(t0)

w_i(t) = u_i × p_i(t) / Σ_j u_j × p_j(t)    live weight

With the units stored in lib/index/ten.js the divisor evaluates to 1.000000000, so L(t0) = 1,000.0000.

Weights float with price

Units never change, so a coin's weight moves with its price relative to the rest. Example: if Fartcoin (launch weight 20.0%) doubles and nothing else moves, the level goes from 1,000 to 1,200.0 and Fartcoin's live weight becomes 33.3%. The 20% cap applies at the pick only; after that the index behaves like a market-cap index of a fixed basket.

Why no rebalances

  • A fixed basket has one price history. The chart, the books and the oracle all read the same series, with no jumps at rebalance times.
  • A matched position tracks the level exactly. There is no rebalance trade to front-run and no seat change to game.
  • Winners are allowed to grow. A coin that runs takes more of the index, as in the S&P 500.

Re-picking the ten would be a new index version (a new ten.js), never something the site does on its own.

#The ten

Weights, units and base prices as stored in lib/index/ten.js. Launch points = B × u_i × p_i(t0), each coin's share of the 1,000 launch level.

Weights and units
#CoinMintRaw cap shareWeight at launchUnitsBase priceLaunch points
1Fartcoin9BB6...pump34.1%20.0%1.7176$0.1164200.0
2Ban9PR7...pump13.7%16.6%3.4167$0.04873166.5
3Pnut2qEH...pump11.2%13.7%3.5803$0.03814136.5
4MOODENGED5n...PJBY10.0%12.1%3.5475$0.03409120.9
5CATEAi66...pump9.7%11.8%3.4516$0.03405117.5
6neetCe2g...pump7.6%9.3%3.5494$0.0260892.6
7GOATCzLS...pump4.0%4.8%3.5517$0.0135648.2
8pippinDfh5...pump3.9%4.7%3.5495$0.0133547.4
9GRIFFAINKENJ...YvEP3.3%4.0%3.5377$0.0112739.9
10CHILLGUYDf6y...pump2.5%3.0%3.5272$0.00864530.5

Recomputing the capped weights from the stored market caps gives the same weights to 5 decimals.

Stats at the pick
CoinMarket capLiquidityLiq / capHoldersTop holders24h volumeAgeBelow 30d highFirst pool
Fartcoin$116M$5.3M4.6%193K35.5%$11.8M746 d39.3%2024-10-18
Ban$46.6M$1.2M2.5%29K36.3%$3.1M738 d30%2024-10-25
Pnut$38.2M$2.8M7.2%89K38%$4.1M732 d31.2%2024-10-31
MOODENG$33.8M$1.2M3.4%94K45.8%$5.9M783 d28.4%2024-09-10
CATE$32.9M$1.6M4.7%142K16.1%$2.1M99 d59.4%2026-07-26
neet$25.9M$1.2M4.8%34K17.4%$1.0M555 d41.4%2025-04-27
GOAT$13.5M$1.4M10.5%89K43.1%$1.2M753 d31.7%2024-10-10
pippin$13.3M$1.6M12.2%49K53.4%$1.1M723 d29.8%2024-11-09
GRIFFAIN$11.1M$798K7.2%51K44.1%$2.8M730 d48.1%2024-11-02
CHILLGUY$8.5M$574K6.7%118K39%$4.0M758 d36.3%2024-10-06

Top cuts

The largest verified coins that did not get a seat, with the first rule each one failed:

CoinMarket capFailed rule
jellyjelly$41.5Mtop holders own 60%
ANSEM$40.2Mtop holders own 65%
arc$39.0Mtop holders own 63%
ALCH$36.1Mtop holders own 86%
ZEREBRO$24.2Mtop holders own 71%
PUMPCADE$19.4M$398K liquidity
Verse$16.6M$240K liquidity
DESSA$13.7Monly 7 days old
GATO$13.3M$269K liquidity
MarsMi$12.7M$163K liquidity
GOLD$10.3M$133K liquidity
Bert$10.1M$474K liquidity
BTC$8.8Mtop holders own 71%

#No rebalances, watch flags

Members are never swapped automatically. Each member is checked on every snapshot against Jupiter's token data, and any hit is published as a watch flag on the site and in /api/index (seats[].watch and the top-level watch list).

FlagCondition
Mint authority re-openedaudit.mintAuthorityDisabled is explicitly false
Freeze authority re-openedaudit.freezeAuthorityDisabled is explicitly false
Price crash24h price change at or below -50%
Liquidity pulled24h liquidity change at or below -50%
Thin liquidityliquidity under $100K

What happens on a dying coin

  • It stays in the index with the same units. Its live weight shrinks with its price, so the most it can take off the level is its current live weight. At launch prices, CHILLGUY going to zero would cost 30.5 points and Fartcoin 200.0 points.
  • If a member has no price on a tick, the public level counts it at its base price (flat since launch), so one missing quote cannot crash the number. The snapshot reports how many members were priced (priced).
  • The leveraged book is stricter: its oracle refuses any tick with an unpriced member and the book freezes (see Price oracle).
  • Replacing a member is a human decision, announced first, and ships as a new index version.

#Price oracle

Sources

SourceEndpointBatchUsed for
Jupiter Price v3lite-api.jup.ag/price/v350 mints per callusdPrice
DexScreenerapi.dexscreener.com/tokens/v1/solana30 mints per callpriceUsd of the pair with the most liquidity; backup cap, liquidity, 1h/24h change, volume
Jupiter Tokens v2lite-api.jup.ag/tokens/v2/searchthe ten in one callmarket cap, liquidity, holders, 24h stats, audit (watch flags)

Averaging and the spread flag

spread_i = |jup_i - dex_i| / min(jup_i, dex_i)

both quotes, spread_i ≤ 0.05:  p_i = (jup_i + dex_i) / 2
both quotes, spread_i > 0.05:  p_i = jup_i, flagged
one quote:                    p_i = that quote
no quote:                     p_i = base price (public level only)

Example: Jupiter 0.1000 and DexScreener 0.1030 is a 3.0% spread, within the limit, so the price is the mean, 0.1015. Jupiter 0.1000 and DexScreener 0.1080 is 8.0%, above the limit, so the price is 0.1000 from Jupiter alone and the coin is flagged.

Cadence, caching and retries

  • The site polls /api/index every 30 s. The route is edge-cached s-maxage=10, stale-while-revalidate=20, so a crowd costs one upstream refresh per 10 s.
  • On the server, prices are cached 15 s per mint set and token cards 60 s. Concurrent requests on one instance share one in-flight fetch.
  • Every upstream call has an 8 s timeout and never throws. A 429 or 5xx is retried once after a jittered 0.7 to 1.3 s wait.
  • If both price sources fail on a refresh, the cache keeps the last good prices. If one fails, the other is used alone and sources in the snapshot shows which one is down.

Oracle for the leveraged book

The leveraged book settles on the same level formula, read through a stricter gate (lib/perp/oracle.js). A tick is refused, and the book holds its last good level, when any of these is true:

ConditionLimit
Price data ageolder than 90 s
Unpriced members1 or more
A source downJupiter or DexScreener returned nothing
Value-weighted spreadabove 5%
spread_index = Σ (spread_i × u_i × p_i) / Σ (u_i × p_i)

A refused tick means no opens, no closes and no settlement for that index; the API reports oracle.ok: false with the reason. When prices come back, the book settles from the last good level to the new one in one step.

#Index history

The chart is the same fixed basket priced at every grid time: L(t) = B × Σ u_i × close_i(t). Before launch time the same units are priced backwards, and the chart shades that stretch. Custom indexes use the same method from their creation time.

RangeCandleStepCandlesWindow
24h15_MINUTE15 min10025 h
7d1_HOUR1 h172172 h
  • Candles come from Jupiter's chart API (USD price, per mint), cached 5 min per coin per range, so one chart rebuild is about ten upstream calls whatever the traffic.
  • Grid points start at now minus (candles - 1) × step, aligned to the step. The last point of the flagship history is the live snapshot.
  • A candle with no trades is missing and the last close carries forward. A time before a coin's first candle uses its first close. A coin with no candles counts at its base price.
  • Each flagship member also gets a 48-point sparkline over the same window.

#Paper book

The original book, on paper USDC: every desk starts with 10,000. It is a pooled long/short book on the P10 level with no leverage and no liquidation, and no wallet.

Settlement
P_long, P_short   pool sizes, USDC
M  = min(P_long, P_short)                matched size
r  = clamp(L_now / L_last - 1, -1, +1)   level return since last settlement
x  = M × r
P_long  += x
P_short -= x
fill(side) = M / P_side
  • Each position owns shares of its side's pool. On open: fee = size × 0.30%, net = size - fee, new shares = net × S / P (or net when the side is empty). Value = shares / S × P.
  • On close: fee = value × 0.30%. Both fees go to the vault. A round trip with no move costs about 0.60%.
  • The stored book settles to the live level before every write. Reads settle a copy and write nothing.
  • A snapshot with zero members priced is a blind tick: no money moves and the old marks are kept.
  • If a side's pool reaches zero, its shares are wiped and its epoch increments, so new money on that side starts clean.

How the book pays

Longs1000
matched 600
Shorts600

1,000 USDC of longs and 600 of shorts. 600 is matched: that much moves with the index on each side.

Desks and limits

A desk is a random 128-bit key generated in your browser and kept in local storage. The server accepts 32 to 64 hex characters and stores only the first 24 hex characters of SHA-256("pump10:" + key). Public views show a 6-character tag.

LimitValue
Position size50 to 5,000 USDC
Open positions per desk5
Open positions in the book400
Trade cooldown per desk3 s
Trades per UTC day, whole book60
Trades per hour per IP, per server instance20
Desks kept3,000, idle ones pruned oldest first

#Leveraged book

Runs in simulated mode until custody is armed: play USDC balances, real mainnet simulations of every transfer.

Model

One pooled book per index, per mode. Longs and shorts trade against each other; the house never takes a side and the vault never pays a trader, so the book cannot go insolvent. A position holds units of index exposure. When the level moves, only matched exposure moves money.

Position
k    leverage, integer 1 to 5
N    = margin × k                      notional
u    = N / L_entry                     units, USDC per index point
E    equity, starts at margin
PnL  = E - margin
Settlement from L_old to L_new
U_L = Σ u over open longs      U_S = Σ u over open shorts
Mu  = min(U_L, U_S)                    matched units
f   = Mu / U_side                      fill of a side, 0 to 1
ΔL  = L_new - L_old
losing side = shorts if ΔL > 0, longs if ΔL < 0

loser i:   owed_i = floor(u_i × (Mu / U_lose) × |ΔL|)
           paid_i = min(owed_i, E_i)        E_i -= paid_i
winner j:  got_j  = floor(Σ paid × u_j / U_win)
                                             E_j += got_j

ADL  = Σ owed - Σ paid                 shortfall the winners absorb
dust = Σ paid - Σ got                  to the vault

There is no funding rate. Unmatched exposure is not exposed: it neither earns nor pays until the other side joins, so imbalance shows up as fill instead of a periodic payment. A fully matched position earns or loses u × ΔL, the same as a linear perp.

Worked example

Three positions open at L = 1,000. The numbers below are computed by the settlement code itself on every render.

Open at 1,000
PosSideMarginLevNotionalUnitsFillOpen feeLiq level
Along200.005x1,000.001.000062.5%1.00712.0
Blong300.002x600.000.600062.5%0.60280.0
Cshort500.002x1,000.001.0000100.0%1.001,450.0

U_L = 1.6000, U_S = 1.0000, matched Mu = 1.0000. Longs are 62.5% filled: of A's 1,000 USDC notional, 625 moves with the index and 375 waits for shorts.

Level 1,000 to 1,050 (+5%). Shorts lose. C owes 1.0000 × (1.0000 / 1.0000) × 50 = 50.00 USDC and pays it in full. Longs split 50.00 by units: A gets 31.25, B gets 18.75.

Level 1,050 to 900 (-14.3%). Longs lose 150.00 USDC in total, each on its matched share; C receives all of it.

After both moves, at 900
PosSideMarginEquityPnLLiq levelStatus
Along200.00137.50-62.50712.0open
Blong300.00262.50-37.50280.0open
Cshort500.00600.00+100.001,450.0open

Equity in the book stays exact: 1,000.00 USDC before, 1,000.00 after, rounding dust 0.000000.

Opening a position

  1. You sign an open message with index, side, margin and leverage.
  2. The server refuses if the side is not long or short, leverage is not an integer from 1 to 5, margin is under 5 USDC, notional is over 2,500 USDC, the oracle is refusing, or trading is paused.
  3. In one database transaction: the nonce is spent, the book is settled to the oracle level (and liquidations run), the side's open interest is checked against the cap, and your balance is debited margin plus the open fee.
  4. The position opens at the book's settled level: entry = L, units = N / L, equity = margin.
Open
fee_open = N × feeBps / 10,000            = N × 0.1%
balance  -= margin + fee_open             refused if balance < margin + fee_open
OI_side  = Σ u (this side) × L
refused if OI_side + N > cap_side

Closing a position

  1. You sign a close message with the position id.
  2. Closing needs a good oracle price and is allowed while the book is in withdraw-only mode; it stops only on a full pause.
  3. In one transaction: the nonce is spent, the book is settled, and if the position survived it pays out.
Close
fee_close = min(N × 0.1%, E)
payout    = E - fee_close                 credited to your balance
exit      = L at settlement

Example: 200 USDC margin at 5x is 1,000 USDC notional. Opening debits 200 + 1.00 = 201.00 USDC. If the index is flat at close, payout is 200 - 1.00 = 199.00 USDC. Round trip cost: 2.00 USDC, 1.0% of margin. Fees are charged on notional at open, both times.

Settlement cadence

  • Every open and close settles its book first, under the book's row lock.
  • Reads of an index or of a wallet settle any book whose last settlement is more than 5 s old, without waiting on a lock another request holds.
  • A scheduled pass settles every book that has open positions, liquidates, reconciles withdrawals and stamps a heartbeat. If the heartbeat is more than 60 s old, the next status read runs the pass itself.
  • Each book is settled under its row lock with SKIP LOCKED, so overlapping passes skip a book instead of settling it twice.

Caps

CapValue
Leverage1x to 5x, integer
Minimum margin5 USDC
Maximum notional per position2,500 USDC
Open interest per side, P1025,000 USDC notional
Open interest per side, custom index5,000 USDC notional

#Liquidation and ADL

Liquidation at maintenance

After every settlement, any open position with equity at or below 10% of its margin is liquidated. Its remaining equity goes to the vault as the liquidation fee, the position is marked liquidated at the settlement level, and it leaves the book.

Liquidation level
m     = 0.1                          maintenance
room  = E - m × margin                  USDC it can still lose
loss per index point = u × f            only the matched share moves

L_liq(long)  = max(0, L - room / (u × f))
L_liq(short) = L + room / (u × f)

at open (E = margin, u = k × margin / L0):
|ΔL| / L0 = (1 - m) / (k × f)

The level depends on the book's fill, which changes as other positions open and close, so the displayed liquidation level is an estimate at the current fill. A position with zero fill has no liquidation level (the API returns null): it cannot lose while unmatched.

Adverse move to liquidation from 1,000
LevMove at fill 100%Long liqShort liqMove at fill 50%Long liqShort liq
1x90.0%100.01,900.0180.0%none above 02,800.0
2x45.0%550.01,450.090.0%100.01,900.0
3x30.0%700.01,300.060.0%400.01,600.0
4x22.5%775.01,225.045.0%550.01,450.0
5x18.0%820.01,180.036.0%640.01,360.0

Auto-deleveraging

If the level gaps past a loser's liquidation level between two settlements, the loser pays only the equity it has, never below zero. Winners receive exactly what losers paid, pro rata by units. The difference is the ADL amount: winners are paid less, and the vault never tops anyone up.

Example, computed by the same code: long D posts 100 USDC at 5x (500 USDC notional, liquidation level 820.0) against short E with 500 USDC at 1x. The level gaps from 1,000 to 500 in one tick. D owes 250.00 USDC but has 100.00; it pays 100.00 and is liquidated. E receives 100.00 and ends with 600.00 USDC. ADL = 150.00 USDC, written to the vault ledger as a record.

Accounting invariants

per settlement:
  Σ equity before = Σ equity after + rounding dust + liquidation fees

whole ledger, per mode:
  deposits - withdrawals = balances + open equity + vault take
  (vault take = fees + liquidation + dust; adl rows are records only)

Money is BigInt micro-USDC; units and levels are floats. Every split is floored and the dust goes to the vault, so both hold to the micro-unit.

#Custody

Collateral is USDC on Solana. Until custody is armed, deposits and withdrawals are simulated against mainnet and balances are play USDC.

Modes

The ledger has two modes, sim and live, and every table row carries one, so simulated money can never become a live balance. The server is in live mode only when the arming gate is open; otherwise it is in sim mode. /api/perp/status reports the mode.

Deposits, live mode

  1. prepare: the server checks the amount against the minimum and your remaining room, builds an unsigned transfer from your wallet to the custody vault (compute limit 60,000 units at 50,000 micro-lamports, an idempotent create of the vault's USDC account, a checked USDC transfer), simulates it on mainnet and returns it. If the vault's USDC account does not exist yet, the depositor pays its rent once, about 0.002 SOL.
  2. Your wallet signs and sends it.
  3. confirm: the server reads the transaction from chain and credits it only if it exists, succeeded, is less than 24 h old, was signed by your wallet, and moved USDC from your wallet into the vault. The credit is the smaller of what left your wallet and what reached the vault. The client retries confirm for about 30 s while the transaction lands.
  4. The transaction signature is the deposit's primary key, so a deposit is credited exactly once, to the wallet that sent it.

Real USDC that reached the vault is always credited, even over a cap or while paused: it is yours. Such a deposit is flagged over-cap on its ledger row and can be withdrawn.

Deposits, sim mode

You sign a deposit message (amount, mode simulated). The server builds the same transfer from your wallet and simulates it on mainnet. If the simulation passes (you hold the USDC plus a little SOL for the fee), the amount is credited as play USDC. Nothing is sent. Simulated deposits obey every cap and the pause switches.

Deposit room

room = min( 500 - (deposited - withdrawn) for this wallet,
            10,000 - (Σ balances + Σ open equity), all wallets )

Withdrawals

  1. You sign a withdraw message with the amount and to set to your own wallet. USDC only ever goes back to the wallet that signed: there is no destination to choose.
  2. In one transaction the server spends the nonce, checks your balance, the cooldown, your rolling 24 h total and the vault's rolling 24 h total, then debits your balance and records the withdrawal as pending.
  3. It builds the USDC transfer from the vault to your wallet (creating your USDC account if needed, paid by the vault) and simulates it on mainnet. A failed build or simulation re-credits you at once.
  4. Sim mode stops here with status simulated; the simulation uses a funded public USDC holder as a stand-in for the empty vault.
  5. Live mode re-checks the arming gate, signs with the vault key, stores the signature and the blockhash expiry height, marks it sending, broadcasts, then marks it sent.

Reconciliation

A withdrawal is re-credited only when it provably did not move USDC. The scheduled pass checks open withdrawals against the chain:

StateChain saysResult
pending for more than 2 minnever signedfailed, re-credited
sending or senttransaction errorfailed, re-credited
sending or sentconfirmed or finalizedconfirmed
sending or sentunknown, block height past expiry + 10failed (expired), re-credited
anyRPC did not answerleft for the next pass

Limits

LimitValue
Vault total held for users10,000 USDC
Lifetime net deposits per wallet500 USDC
Minimum deposit1 USDC
Minimum withdrawal1 USDC
Withdrawal cooldown per wallet10 min
Withdrawals per wallet, rolling 24 h500 USDC
Withdrawals, all wallets, rolling 24 h2,000 USDC
Deposit credit window24 h

Switches and the arming gate

SwitchDepositsOpensClosesWithdrawals
normalyesyesyesyes
withdraw_onlyno (sim, prepare)noyesyes
pausedno (sim, prepare)nonono

Both switches live in the ledger's settings row and flip without a redeploy. A live deposit confirm is never blocked: USDC already in the vault is always credited. The server signs a real transaction only when three independent switches agree: the vault signing key is present in the server environment, a deploy-level arming flag is on, and the armed switch in the settings row is on. The gate is checked again right before the key is used. The money limits are code constants and change only with a deploy.

Ledger

Postgres, one schema. Every amount is BIGINT micro-USDC, with non-negative checks on balances and equity. Every money change is one transaction that locks what it touches (account rows and the book row FOR UPDATE, named advisory locks for the vault total, the daily withdrawal total and index creation) and commits all-or-nothing, so two requests cannot spend the same balance or both squeeze under a cap.

#Signed messages

Every action that moves money is authorized by an ed25519 signature from your wallet over a plain-text message (Phantom sign-message, not a transaction). The server rebuilds the exact text from the request's fields and verifies the signature against the wallet, so a signature cannot be reused with different fields.

Exact text, withdraw
P10 withdraw
site: pump10.io
wallet: <your wallet, base58>
amount: 25
to: <your wallet, base58>
nonce: 9f2c4e1a7b3d5f6008e1c2a4b6d8f0a1
expires: 2026-11-03T12:04:00.000Z
ActionFields, in order, between wallet and nonce
openindex, side, margin, leverage
closeposition
depositamount, mode (simulated; sim mode only)
withdrawamount, to (must equal wallet)
createname, coins
  • The site line stops replay on another site. Field values may not contain line breaks.
  • Nonce: 16 to 64 hex characters (the site sends 32), spent on first use inside the same transaction as the action, refused after.
  • Expiry: must be in the future and at most 5 min ahead, plus 30 s of clock grace. The site signs with a 4 min expiry.
  • Signature 64 bytes and public key 32 bytes, both base58.
  • Amounts are decimal USDC strings with up to 6 decimals, for example 12.5.

#Vault and burns

The burn is dry: real Jupiter quotes, no transaction is built, signed or sent.

What flows in

  • Paper book: 0.30% of size on open and of value on close.
  • Leveraged book: 0.1% of notional at open and close, liquidation leftovers and rounding dust, each as its own vault ledger row (fee, liquidation, dust; adl rows record shortfalls and hold no money).

Burn plan

for each member i, one after another:
  spend_i  = vault_usd × w_i(t)          live weight
  amount_i = floor(spend_i × 10^6)        USDC base units
  quote    = Jupiter swap/v1/quote, USDC to coin_i
             slippage 100 bps, intermediate tokens restricted
             skipped if amount_i < 1,000 (0.001 USDC)
             one retry after 1.5 s, 350 ms between coins
  no quote: estimate spend_i / p_i, marked estimated
  • In the paper book a plan runs when the vault holds $25 or more after a trade. Quotes are fetched first, then one conditional write records the burn; if another trade already burned, the write is skipped.
  • /api/vault shows what $100 of fees buys right now, cached 5 min (30 s when more than two legs are estimates).
  • Going live means a funded vault wallet signs each Jupiter swap, then an SPL burn of what it bought.

#Custom indexes

Built on the leveraged book; simulated until custody is armed, like the rest of it.

A custom index is a basket you pick, with its own leveraged book. It is fixed at creation the way P10 is fixed at launch: each coin's units are set from its weight and its live price at that moment, and the level starts at 1,000.

Custom level
legs   = "MINT:weight,MINT:weight,..."    whole percents, sum = 100
u_i    = (weight_i / 100) / p_i(created)
L(t)   = 1,000 × Σ u_i × p_i(t)

Rules

RuleValue
Coins per index2 to 10, each coin once
Weightswhole percents from 1 to 100, summing to 100
LaunchpadJupiter reports pump.fun
Liquidity per coinat least $250K
Age per coinat least 7 days
Authoritiesmint and freeze both disabled
Pricea live Jupiter price, and Jupiter and DexScreener within the spread limit
Name3 to 24 characters: letters, numbers, spaces and $ . _ -; the flagship's name is reserved
Creations per wallet3 per 24 h
Creations, all wallets20 per hour
Open interest per side5,000 USDC notional

Flow

  1. Check: POST the legs string with check: true. The server vets every coin and returns symbol, name, icon, weight and liquidity, or the first failure. No signature, nothing saved.
  2. Create: sign a create message with name and coins (the same legs string) and POST it. The server vets again, applies the rate limits under a lock, spends the nonce and returns the new slug (the name slugified plus 4 random hex characters).
  3. Trade it with the same open and close calls, passing the slug as index.

The liquidity floor exists because a thin coin could be pushed to move a small index and win its book.

#API reference

Public, no API keys, JSON. Errors return { "error": "..." }: 400 for a refusal with a readable reason, 404 for an unknown index, 500 with "Nothing was moved" for anything unexpected. Responses are no-store unless a cache header is listed.

Index

GET/api/index

The live P10 snapshot.

{
  t: number,              // price fetch time, ms
  level: number,          // L(t)
  priced: number,         // members with a live price this tick
  launch: { time, level },
  pickedAt: string,
  seats: [{
    mint, symbol, name, icon,
    weightAtLaunch, units, basePrice,
    price, jup, dex,      // final price and each source's quote
    spread, flagged,      // source gap, and gap > 0.05
    sinceLaunch,          // price / basePrice - 1
    liveWeight, pair,
    mcap, liquidity, holders, volume24h,
    change1h, change24h, topHolders,
    atPick: { ... },      // stats at the pick
    watch: [string]       // watch flags
  }],
  basket: [{ mint, w, open }],
  watch: [{ mint, symbol, reasons }],
  universe: { coingecko, mints, verifiedPumpFun, screened },
  sources: { jupiter: bool, dexscreener: bool }
}

Cache: public, s-maxage=10, stale-while-revalidate=20. 503 while warming up.

GET/api/history?range=24h|7d

P10 level history and per-coin sparklines. Any other range value means 24h.

{
  range: "24h" | "7d",
  points: [[ms, level], ...],   // oldest first, last = live
  spark: { [mint]: [48 closes] },
  launch: number                // launch time, ms
}

Cache: public, s-maxage=60, stale-while-revalidate=120.

Leveraged book

Money values in these responses are micro-USDC as decimal strings (divide by 1,000,000). Money values in requests are decimal USDC strings. Request bodies are capped at 8,000 bytes. The flagship's slug is pump10.

GET/api/perp/status

Is trading open, sim or live, is the settlement pass alive. Also the backup trigger: a heartbeat older than 60 s makes this request run a pass.

{
  ready: bool,            // false until the ledger is connected
  mode: "sim" | "live",
  paused: bool, withdrawOnly: bool,
  vault: string | null,   // custody vault address
  tick: { at, age, info } | null,
  caps: { maxLeverage, minMargin, maxPosition, feeBps, maintenance,
          perWallet, vaultTvl, minDeposit, minWithdraw,
          withdrawDaily, withdrawCooldownMin }
}

GET/api/perp/index?slug=

One index: live level, legs, oracle health and its book. Slug defaults to pump10.

{
  slug, name, kind: "flagship" | "custom", creator, createdAt,
  level, at,
  oracle: { ok: bool, reason: string | null },
  legs: [{ mint, symbol, name, icon, weight, units, basePrice,
           price, since, value, liveWeight }],
  book: { mode, level, at, positions,
          long:  { oi, fill, count },
          short: { oi, fill, count } } | null
}

Cache: public, s-maxage=3, stale-while-revalidate=5. The read settles the book if it is more than 5 s old.

GET/api/perp/indexes?q=

Browse and search. The flagship first, then up to 60 custom indexes, newest first. q matches name, coin symbol or coin address.

{ indexes: [{ slug, name, kind?, creator, createdAt,
              legs: [{ mint, symbol, icon, weight }] }] }

Cache: public, s-maxage=5, stale-while-revalidate=10.

GET/api/perp/history?slug=&range=24h|7d

Level history of a custom index; the flagship keeps /api/history.

{ range, points: [[ms, level], ...], launch: number }

Cache: public, s-maxage=60, stale-while-revalidate=120.

GET/api/perp/me?wallet=

A wallet's account in the current mode. Settles that wallet's books first.

{
  mode, balance, deposited, withdrawn,
  room,                   // how much more this wallet may deposit
  positions: [{ id, slug, side, leverage, margin, notional, entry,
                status: "open" | "closed" | "liquidated",
                equity, pnl,
                level,    // mark if open, exit if closed
                liq,      // null when closed or unmatched
                fill, openedAt, closedAt, paidOut }],   // last 30
  withdrawals: [{ id, amount, status, signature, at }]  // last 10
}

POST/api/perp/open

body:    { wallet, index, side, margin, leverage,
           nonce, expires, signature }
returns: { mode, id, entry, fee, balance }

POST/api/perp/close

body:    { wallet, position, nonce, expires, signature }
returns: { mode, status: "closed", exit, payout, fee }
      or { mode, status: "liquidated", payout: "0" }

POST/api/perp/create

check:   { check: true, legs: "MINT:60,MINT:40" }
returns: { ok: true, legs: [{ mint, symbol, name, icon,
                              weight, liquidity }] }

create:  { wallet, name, legs, nonce, expires, signature }
         (signed fields: name, coins = legs)
returns: { slug }

Custody

POST/api/custody/deposit

live, step 1: { step: "prepare", wallet, amount }
  returns { tx }                 // base64 unsigned transaction
live, step 2: { step: "confirm", wallet, signature }
  returns { mode: "live", credited, already }
sim:          { step: "sim", wallet, amount, mode: "simulated",
                nonce, expires, signature }
  returns { mode: "sim", credited, balance }

POST/api/custody/withdraw

body:    { wallet, amount, to, nonce, expires, signature }
returns: { mode, id, status, signature?, error? }
status:  "simulated" | "sent" | "sending" | "failed"
         (400 when failed; the balance is back)

Paper book, vault, media

GET/api/book

The paper book, settled to the live level.

{
  long:  { pool, fill, open },
  short: { pool, fill, open },
  matched, level, settledAt, indexLevel, t,
  positions: [{ id, tag, side, size, value, pnl,
                entryLevel, openedAt }],
  tape: [{ t, kind, side, usd, id, pnl, level }],
  vault: { usd, collected, burned },
  burns: [{ t, usd, level,
            legs: [{ symbol, mint, spend, tokens, route }] }],
  stats: { trades, volume, seq, day, today },
  desks: number
}

Cache: public, s-maxage=3, stale-while-revalidate=10, plus a 4 s in-memory view per instance.

POST/api/book

{ "action": "desk",  "key": "<hex>" }
  returns { owner, cash }
{ "action": "open",  "key": "<hex>", "side": "long", "size": 100 }
  returns { position, cash, owner, book }
{ "action": "close", "key": "<hex>", "id": "p1a" }
  returns { value, fee, pnl, cash, owner, book }

Size is rounded to cents. Open and close are limited to 20 per hour per IP per server instance (429), on top of the book's daily cap.

GET/api/vault

What $100 of fees buys right now, one real Jupiter quote per member.

{ usd: 100,
  legs: [{ symbol, mint, spend, tokens, impact, route, estimated? }],
  quotedAt: number, dry: true }

Cache: public, s-maxage=300, stale-while-revalidate=600, or public, s-maxage=20 when more than two legs are estimates.

GET/api/icon/[mint]

Coin images served from this domain. Only the ten members resolve, to the icon recorded at the pick. HTTPS on every redirect hop (at most 4), public IPs only (checked when the socket connects), 1.5 MB max, no SVG, 9 s timeout. Unknown or broken images return a 1x1 transparent PNG; a malformed mint returns 400. Cache: public, max-age=86400, s-maxage=604800, immutable.

GET/api/og

1200 x 630 PNG share card: live level, 24h move, the ten. Edge runtime. Cache: public, s-maxage=300, stale-while-revalidate=600.

The settlement pass also has an authenticated scheduler route; it answers 404 to anyone else.

#Parameters

Every constant the modules export, read at render time.

ModuleKeyValueMeaning
rules.js RULESsize10Members in the index
rules.js RULEScapWeight0.2 (20%)Max weight of one coin at the pick
rules.js RULEStickMs30,000 ms (30 s)Price refresh cadence of the site
rules.js RULESmaxPriceSpread0.05 (5%)Jupiter vs DexScreener gap above which a coin is priced from Jupiter alone and flagged
rules.js RULESbaseLevel1,000Index level at launch time
rules.js PICKminAgeDays30 daysMinimum age since first pool
rules.js PICKminLiquidity$500,000Minimum pool liquidity
rules.js PICKminLiqToCap0.02 (2%)Minimum liquidity as a share of market cap
rules.js PICKminHolders10,000Minimum holder count
rules.js PICKmaxTopHolders55%Maximum share held by the top holders
rules.js PICKminVolume24h$250,000Minimum 24h volume (larger of Jupiter on-chain and CoinGecko all-venue)
rules.js PICKmaxDrawdown30d0.7 (70%)Maximum distance below the 30-day high
rules.js WATCHdrop24h-50%Flag when the 24h price change is at or below this
rules.js WATCHliqDrop24h-50%Flag when the 24h liquidity change is at or below this
rules.js WATCHminLiquidity$100,000Flag when liquidity is under this
book/math.js BOOKfee0.003 (0.30%)Paper book fee on open (of size) and on close (of value)
book/math.js BOOKstartCash10,000 USDCPaper USDC per new desk
book/math.js BOOKminSize50 USDCMinimum position size
book/math.js BOOKmaxSize5,000 USDCMaximum position size
book/math.js BOOKmaxOpenPerOwner5Open positions per desk
book/math.js BOOKmaxOpen400Open positions in the whole book
book/math.js BOOKcooldownMs3,000 ms (3 s)Minimum time between two trades of one desk
book/math.js BOOKburnAt$25Vault balance that triggers a dry burn
book/math.js BOOKmaxTradesPerDay60Trades per UTC day, whole book
book/math.js BOOKmaxOwners3,000Desks kept before idle ones are pruned
book/math.js BOOKkeepTape60Tape rows kept
book/math.js BOOKkeepBurns40Burn records kept
perp/config.js PERPmaxLeverage5xLeverage ceiling (integer, 1 to this)
perp/config.js PERPminMarginUsd5 USDCMinimum margin per position
perp/config.js PERPmaxPositionNotional2,500 USDCMaximum notional per position
perp/config.js PERPmaxOiPerSideflagship 25,000 USDC, custom 5,000 USDCMaximum open notional per index per side
perp/config.js PERPfeeBps10 bps (0.1%)Fee on notional at open and at close, to the vault
perp/config.js PERPmaintenance0.1 (10%)Liquidation when equity falls to this share of margin
perp/config.js PERPoracleMaxAgeMs90,000 ms (90 s)Oracle price older than this is stale: no opens, closes or settlement
perp/config.js PERPoracleMaxSpread0.05 (5%)Value-weighted Jupiter/DexScreener gap that freezes the book
perp/config.js PERPoracleMinLegLiquidity100,000
perp/config.js PERPtradeMaxPriceAgeMs10,000
perp/config.js PERPminHoldMs30,000
perp/config.js CUSTODYmaxVaultTvl10,000 USDCTotal the custody vault holds for users, all wallets
perp/config.js CUSTODYmaxPerWallet500 USDCLifetime net deposits per wallet
perp/config.js CUSTODYminDeposit1 USDCMinimum deposit
perp/config.js CUSTODYminWithdraw1 USDCMinimum withdrawal
perp/config.js CUSTODYwithdrawCooldownMs600,000 ms (10 min)One withdrawal per wallet per this window
perp/config.js CUSTODYwithdrawDailyPerWallet500 USDCWithdrawals per wallet per rolling 24 h
perp/config.js CUSTODYwithdrawDailyGlobal2,000 USDCWithdrawals per rolling 24 h, all wallets
perp/config.js CUSTODYsignedMessageTtlMs300,000 ms (5 min)Longest lifetime a signed message may claim
perp/config.js CUSTODYdepositMaxAgeMs86,400,000 ms (24 h)A deposit transaction older than this is not credited
perp/config.js CUSTODYswapSlippageBps50
perp/config.js CUSTODYmaxPriceImpact0.0100
perp/config.js CUSTODYminSolDeposit10 USDC
perp/config.js CUSTODYmaxVaultFeeLamports0 USDC
perp/config.js CUSTODYautoRefundMax50 USDC
perp/config.js INDEXESminLegs2Minimum coins in a custom index
perp/config.js INDEXESmaxLegs10Maximum coins in a custom index
perp/config.js INDEXESminLiquidityUsd$250,000Minimum liquidity per coin
perp/config.js INDEXESmaxLegWeight40
perp/config.js INDEXESminAgeDays7 daysMinimum age per coin
perp/config.js INDEXESperWalletPerDay3Custom indexes one wallet can create per 24 h
perp/config.js INDEXESglobalPerHour20Custom indexes created per hour, all wallets

Fixed in code, not exported:

WhereValue
Server price cache per mint set15 s
Token card cache60 s
Candle cache per coin per range5 min
Upstream timeout and retry8 s, 1 retry on 429 or 5xx after 0.7 to 1.3 s
Jupiter and DexScreener batch50 and 30 mints per call
Paper book writes per IP20 per hour per instance
Burn quote slippage100 bps
Leveraged book: settle on read if older than5 s
Settlement heartbeat max age60 s
Pending withdrawal refund after2 min
Expired withdrawal: block height pastlast valid height + 10
Custody tx compute budget60,000 units at 50,000 micro-lamports
Signed message clock grace30 s
Site signing expiry and nonce4 min, 32 hex characters
Request body limit8,000 bytes

#Glossary

Level
The index value L(t). 1,000 at launch time.
Member
One of the ten coins. Also called a seat.
Units
Fixed amount of a coin held by an index: u_i = w_i / p_i(t0).
Base price
A coin's price at launch (P10) or at creation (custom), p_i(t0).
Weight at launch
Capped market-cap weight from the pick, at most 20%.
Live weight
A coin's share of the basket value right now. Floats with price.
Watch flag
A published sign that a member may be dying. Never triggers a swap.
Spread flag
Jupiter and DexScreener disagree by more than 5%; the coin is priced from Jupiter alone.
Blind tick
A snapshot with no member priced. The paper book moves no money on it.
Slug
An index's id in the API. The flagship is pump10.
Notional
Margin × leverage: the size of a leveraged position's exposure.
Open interest
Σ units × level on one side of a book, in USDC.
Matched
The part of the book with a counterparty: min(long, short).
Fill
Matched divided by your side's total. The share of each position that moves with the index.
Equity
What a leveraged position is worth now: margin plus settled PnL.
Maintenance
Equity floor, 10% of margin. At or below it the position is liquidated.
ADL
Auto-deleveraging: winners receive less when a loser's equity cannot cover a gap.
Dust
Rounding remainder from floored splits. Goes to the vault.
Room
How much more a wallet may deposit under the per-wallet and vault caps.
Desk
A paper trading account identified by a key in your browser.
Epoch
Paper book counter for a side that was wiped to zero.
Micro-USDC
1 USDC = 1,000,000 micro-USDC. The ledger's integer unit.
Sim and live mode
Ledger partition. Simulated money never mixes with real money.
Arming gate
Three switches that must all be on before the server signs a real transaction.
Reconcile
Settling withdrawals left between steps from what the chain says.
Dry
Quotes and simulations only. Nothing is signed or sent.