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.
| Component | What it does | State |
|---|---|---|
| Index | Live level of 10 fixed members, refreshed every 30 s | Live |
| Paper book | Pooled long/short book on the level, no leverage, no liquidation | Live, paper USDC |
| Leveraged book | Pooled long/short book, 1x to 5x, USDC margin in a capped custodial vault | Simulated until armed |
| Custom indexes | Your own basket of 2 to 10 pump.fun coins with its own leveraged book | Simulated until armed |
| Vault | Collects fees, buys the ten by weight and burns them | Dry: 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.
| # | Rule | Threshold | Data |
|---|---|---|---|
| 1 | Mint and freeze authority | both disabled | Jupiter audit |
| 2 | Age since first pool | at least 30 days | Jupiter firstPool.createdAt |
| 3 | Liquidity | at least $500K | Jupiter, USD in pools |
| 4 | Liquidity / market cap | at least 2% | Jupiter |
| 5 | Holders | at least 10,000 | Jupiter holderCount |
| 6 | Top holders | at most 55% | Jupiter audit.topHoldersPercentage |
| 7 | 24h volume | at least $250K | max(Jupiter on-chain, CoinGecko all-venue) |
| 8 | Drawdown from 30-day high | at most 70% | Jupiter daily candles |
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/nIn 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.
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 weightWith 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.
| # | Coin | Mint | Raw cap share | Weight at launch | Units | Base price | Launch points |
|---|---|---|---|---|---|---|---|
| 1 | Fartcoin | 9BB6...pump | 34.1% | 20.0% | 1.7176 | $0.1164 | 200.0 |
| 2 | Ban | 9PR7...pump | 13.7% | 16.6% | 3.4167 | $0.04873 | 166.5 |
| 3 | Pnut | 2qEH...pump | 11.2% | 13.7% | 3.5803 | $0.03814 | 136.5 |
| 4 | MOODENG | ED5n...PJBY | 10.0% | 12.1% | 3.5475 | $0.03409 | 120.9 |
| 5 | CATE | Ai66...pump | 9.7% | 11.8% | 3.4516 | $0.03405 | 117.5 |
| 6 | neet | Ce2g...pump | 7.6% | 9.3% | 3.5494 | $0.02608 | 92.6 |
| 7 | GOAT | CzLS...pump | 4.0% | 4.8% | 3.5517 | $0.01356 | 48.2 |
| 8 | pippin | Dfh5...pump | 3.9% | 4.7% | 3.5495 | $0.01335 | 47.4 |
| 9 | GRIFFAIN | KENJ...YvEP | 3.3% | 4.0% | 3.5377 | $0.01127 | 39.9 |
| 10 | CHILLGUY | Df6y...pump | 2.5% | 3.0% | 3.5272 | $0.008645 | 30.5 |
Recomputing the capped weights from the stored market caps gives the same weights to 5 decimals.
| Coin | Market cap | Liquidity | Liq / cap | Holders | Top holders | 24h volume | Age | Below 30d high | First pool |
|---|---|---|---|---|---|---|---|---|---|
| Fartcoin | $116M | $5.3M | 4.6% | 193K | 35.5% | $11.8M | 746 d | 39.3% | 2024-10-18 |
| Ban | $46.6M | $1.2M | 2.5% | 29K | 36.3% | $3.1M | 738 d | 30% | 2024-10-25 |
| Pnut | $38.2M | $2.8M | 7.2% | 89K | 38% | $4.1M | 732 d | 31.2% | 2024-10-31 |
| MOODENG | $33.8M | $1.2M | 3.4% | 94K | 45.8% | $5.9M | 783 d | 28.4% | 2024-09-10 |
| CATE | $32.9M | $1.6M | 4.7% | 142K | 16.1% | $2.1M | 99 d | 59.4% | 2026-07-26 |
| neet | $25.9M | $1.2M | 4.8% | 34K | 17.4% | $1.0M | 555 d | 41.4% | 2025-04-27 |
| GOAT | $13.5M | $1.4M | 10.5% | 89K | 43.1% | $1.2M | 753 d | 31.7% | 2024-10-10 |
| pippin | $13.3M | $1.6M | 12.2% | 49K | 53.4% | $1.1M | 723 d | 29.8% | 2024-11-09 |
| GRIFFAIN | $11.1M | $798K | 7.2% | 51K | 44.1% | $2.8M | 730 d | 48.1% | 2024-11-02 |
| CHILLGUY | $8.5M | $574K | 6.7% | 118K | 39% | $4.0M | 758 d | 36.3% | 2024-10-06 |
Top cuts
The largest verified coins that did not get a seat, with the first rule each one failed:
| Coin | Market cap | Failed rule |
|---|---|---|
| jellyjelly | $41.5M | top holders own 60% |
| ANSEM | $40.2M | top holders own 65% |
| arc | $39.0M | top holders own 63% |
| ALCH | $36.1M | top holders own 86% |
| ZEREBRO | $24.2M | top holders own 71% |
| PUMPCADE | $19.4M | $398K liquidity |
| Verse | $16.6M | $240K liquidity |
| DESSA | $13.7M | only 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.8M | top 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).
| Flag | Condition |
|---|---|
| Mint authority re-opened | audit.mintAuthorityDisabled is explicitly false |
| Freeze authority re-opened | audit.freezeAuthorityDisabled is explicitly false |
| Price crash | 24h price change at or below -50% |
| Liquidity pulled | 24h liquidity change at or below -50% |
| Thin liquidity | liquidity 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
| Source | Endpoint | Batch | Used for |
|---|---|---|---|
| Jupiter Price v3 | lite-api.jup.ag/price/v3 | 50 mints per call | usdPrice |
| DexScreener | api.dexscreener.com/tokens/v1/solana | 30 mints per call | priceUsd of the pair with the most liquidity; backup cap, liquidity, 1h/24h change, volume |
| Jupiter Tokens v2 | lite-api.jup.ag/tokens/v2/search | the ten in one call | market 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/indexevery 30 s. The route is edge-cacheds-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
sourcesin 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:
| Condition | Limit |
|---|---|
| Price data age | older than 90 s |
| Unpriced members | 1 or more |
| A source down | Jupiter or DexScreener returned nothing |
| Value-weighted spread | above 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.
| Range | Candle | Step | Candles | Window |
|---|---|---|---|---|
| 24h | 15_MINUTE | 15 min | 100 | 25 h |
| 7d | 1_HOUR | 1 h | 172 | 172 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.
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
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.
| Limit | Value |
|---|---|
| Position size | 50 to 5,000 USDC |
| Open positions per desk | 5 |
| Open positions in the book | 400 |
| Trade cooldown per desk | 3 s |
| Trades per UTC day, whole book | 60 |
| Trades per hour per IP, per server instance | 20 |
| Desks kept | 3,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.
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 - marginU_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 vaultThere 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.
| Pos | Side | Margin | Lev | Notional | Units | Fill | Open fee | Liq level |
|---|---|---|---|---|---|---|---|---|
| A | long | 200.00 | 5x | 1,000.00 | 1.0000 | 62.5% | 1.00 | 712.0 |
| B | long | 300.00 | 2x | 600.00 | 0.6000 | 62.5% | 0.60 | 280.0 |
| C | short | 500.00 | 2x | 1,000.00 | 1.0000 | 100.0% | 1.00 | 1,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.
| Pos | Side | Margin | Equity | PnL | Liq level | Status |
|---|---|---|---|---|---|---|
| A | long | 200.00 | 137.50 | -62.50 | 712.0 | open |
| B | long | 300.00 | 262.50 | -37.50 | 280.0 | open |
| C | short | 500.00 | 600.00 | +100.00 | 1,450.0 | open |
Equity in the book stays exact: 1,000.00 USDC before, 1,000.00 after, rounding dust 0.000000.
Opening a position
- You sign an
openmessage with index, side, margin and leverage. - 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.
- 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.
- The position opens at the book's settled level: entry = L, units = N / L, equity = margin.
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_sideClosing a position
- You sign a
closemessage with the position id. - Closing needs a good oracle price and is allowed while the book is in withdraw-only mode; it stops only on a full pause.
- In one transaction: the nonce is spent, the book is settled, and if the position survived it pays out.
fee_close = min(N × 0.1%, E)
payout = E - fee_close credited to your balance
exit = L at settlementExample: 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
| Cap | Value |
|---|---|
| Leverage | 1x to 5x, integer |
| Minimum margin | 5 USDC |
| Maximum notional per position | 2,500 USDC |
| Open interest per side, P10 | 25,000 USDC notional |
| Open interest per side, custom index | 5,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.
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.
| Lev | Move at fill 100% | Long liq | Short liq | Move at fill 50% | Long liq | Short liq |
|---|---|---|---|---|---|---|
| 1x | 90.0% | 100.0 | 1,900.0 | 180.0% | none above 0 | 2,800.0 |
| 2x | 45.0% | 550.0 | 1,450.0 | 90.0% | 100.0 | 1,900.0 |
| 3x | 30.0% | 700.0 | 1,300.0 | 60.0% | 400.0 | 1,600.0 |
| 4x | 22.5% | 775.0 | 1,225.0 | 45.0% | 550.0 | 1,450.0 |
| 5x | 18.0% | 820.0 | 1,180.0 | 36.0% | 640.0 | 1,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
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.- Your wallet signs and sends it.
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.- 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
- You sign a
withdrawmessage with the amount andtoset to your own wallet. USDC only ever goes back to the wallet that signed: there is no destination to choose. - 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.
- 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.
- Sim mode stops here with status
simulated; the simulation uses a funded public USDC holder as a stand-in for the empty vault. - 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 itsent.
Reconciliation
A withdrawal is re-credited only when it provably did not move USDC. The scheduled pass checks open withdrawals against the chain:
| State | Chain says | Result |
|---|---|---|
| pending for more than 2 min | never signed | failed, re-credited |
| sending or sent | transaction error | failed, re-credited |
| sending or sent | confirmed or finalized | confirmed |
| sending or sent | unknown, block height past expiry + 10 | failed (expired), re-credited |
| any | RPC did not answer | left for the next pass |
Limits
| Limit | Value |
|---|---|
| Vault total held for users | 10,000 USDC |
| Lifetime net deposits per wallet | 500 USDC |
| Minimum deposit | 1 USDC |
| Minimum withdrawal | 1 USDC |
| Withdrawal cooldown per wallet | 10 min |
| Withdrawals per wallet, rolling 24 h | 500 USDC |
| Withdrawals, all wallets, rolling 24 h | 2,000 USDC |
| Deposit credit window | 24 h |
Switches and the arming gate
| Switch | Deposits | Opens | Closes | Withdrawals |
|---|---|---|---|---|
| normal | yes | yes | yes | yes |
withdraw_only | no (sim, prepare) | no | yes | yes |
paused | no (sim, prepare) | no | no | no |
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.
P10 withdraw
site: pump10.io
wallet: <your wallet, base58>
amount: 25
to: <your wallet, base58>
nonce: 9f2c4e1a7b3d5f6008e1c2a4b6d8f0a1
expires: 2026-11-03T12:04:00.000Z| Action | Fields, in order, between wallet and nonce |
|---|---|
open | index, side, margin, leverage |
close | position |
deposit | amount, mode (simulated; sim mode only) |
withdraw | amount, to (must equal wallet) |
create | name, 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;adlrows 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/vaultshows 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.
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
| Rule | Value |
|---|---|
| Coins per index | 2 to 10, each coin once |
| Weights | whole percents from 1 to 100, summing to 100 |
| Launchpad | Jupiter reports pump.fun |
| Liquidity per coin | at least $250K |
| Age per coin | at least 7 days |
| Authorities | mint and freeze both disabled |
| Price | a live Jupiter price, and Jupiter and DexScreener within the spread limit |
| Name | 3 to 24 characters: letters, numbers, spaces and $ . _ -; the flagship's name is reserved |
| Creations per wallet | 3 per 24 h |
| Creations, all wallets | 20 per hour |
| Open interest per side | 5,000 USDC notional |
Flow
- 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. - Create: sign a
createmessage withnameandcoins(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). - 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.
| Module | Key | Value | Meaning |
|---|---|---|---|
rules.js RULES | size | 10 | Members in the index |
rules.js RULES | capWeight | 0.2 (20%) | Max weight of one coin at the pick |
rules.js RULES | tickMs | 30,000 ms (30 s) | Price refresh cadence of the site |
rules.js RULES | maxPriceSpread | 0.05 (5%) | Jupiter vs DexScreener gap above which a coin is priced from Jupiter alone and flagged |
rules.js RULES | baseLevel | 1,000 | Index level at launch time |
rules.js PICK | minAgeDays | 30 days | Minimum age since first pool |
rules.js PICK | minLiquidity | $500,000 | Minimum pool liquidity |
rules.js PICK | minLiqToCap | 0.02 (2%) | Minimum liquidity as a share of market cap |
rules.js PICK | minHolders | 10,000 | Minimum holder count |
rules.js PICK | maxTopHolders | 55% | Maximum share held by the top holders |
rules.js PICK | minVolume24h | $250,000 | Minimum 24h volume (larger of Jupiter on-chain and CoinGecko all-venue) |
rules.js PICK | maxDrawdown30d | 0.7 (70%) | Maximum distance below the 30-day high |
rules.js WATCH | drop24h | -50% | Flag when the 24h price change is at or below this |
rules.js WATCH | liqDrop24h | -50% | Flag when the 24h liquidity change is at or below this |
rules.js WATCH | minLiquidity | $100,000 | Flag when liquidity is under this |
book/math.js BOOK | fee | 0.003 (0.30%) | Paper book fee on open (of size) and on close (of value) |
book/math.js BOOK | startCash | 10,000 USDC | Paper USDC per new desk |
book/math.js BOOK | minSize | 50 USDC | Minimum position size |
book/math.js BOOK | maxSize | 5,000 USDC | Maximum position size |
book/math.js BOOK | maxOpenPerOwner | 5 | Open positions per desk |
book/math.js BOOK | maxOpen | 400 | Open positions in the whole book |
book/math.js BOOK | cooldownMs | 3,000 ms (3 s) | Minimum time between two trades of one desk |
book/math.js BOOK | burnAt | $25 | Vault balance that triggers a dry burn |
book/math.js BOOK | maxTradesPerDay | 60 | Trades per UTC day, whole book |
book/math.js BOOK | maxOwners | 3,000 | Desks kept before idle ones are pruned |
book/math.js BOOK | keepTape | 60 | Tape rows kept |
book/math.js BOOK | keepBurns | 40 | Burn records kept |
perp/config.js PERP | maxLeverage | 5x | Leverage ceiling (integer, 1 to this) |
perp/config.js PERP | minMarginUsd | 5 USDC | Minimum margin per position |
perp/config.js PERP | maxPositionNotional | 2,500 USDC | Maximum notional per position |
perp/config.js PERP | maxOiPerSide | flagship 25,000 USDC, custom 5,000 USDC | Maximum open notional per index per side |
perp/config.js PERP | feeBps | 10 bps (0.1%) | Fee on notional at open and at close, to the vault |
perp/config.js PERP | maintenance | 0.1 (10%) | Liquidation when equity falls to this share of margin |
perp/config.js PERP | oracleMaxAgeMs | 90,000 ms (90 s) | Oracle price older than this is stale: no opens, closes or settlement |
perp/config.js PERP | oracleMaxSpread | 0.05 (5%) | Value-weighted Jupiter/DexScreener gap that freezes the book |
perp/config.js PERP | oracleMinLegLiquidity | 100,000 | |
perp/config.js PERP | tradeMaxPriceAgeMs | 10,000 | |
perp/config.js PERP | minHoldMs | 30,000 | |
perp/config.js CUSTODY | maxVaultTvl | 10,000 USDC | Total the custody vault holds for users, all wallets |
perp/config.js CUSTODY | maxPerWallet | 500 USDC | Lifetime net deposits per wallet |
perp/config.js CUSTODY | minDeposit | 1 USDC | Minimum deposit |
perp/config.js CUSTODY | minWithdraw | 1 USDC | Minimum withdrawal |
perp/config.js CUSTODY | withdrawCooldownMs | 600,000 ms (10 min) | One withdrawal per wallet per this window |
perp/config.js CUSTODY | withdrawDailyPerWallet | 500 USDC | Withdrawals per wallet per rolling 24 h |
perp/config.js CUSTODY | withdrawDailyGlobal | 2,000 USDC | Withdrawals per rolling 24 h, all wallets |
perp/config.js CUSTODY | signedMessageTtlMs | 300,000 ms (5 min) | Longest lifetime a signed message may claim |
perp/config.js CUSTODY | depositMaxAgeMs | 86,400,000 ms (24 h) | A deposit transaction older than this is not credited |
perp/config.js CUSTODY | swapSlippageBps | 50 | |
perp/config.js CUSTODY | maxPriceImpact | 0.0100 | |
perp/config.js CUSTODY | minSolDeposit | 10 USDC | |
perp/config.js CUSTODY | maxVaultFeeLamports | 0 USDC | |
perp/config.js CUSTODY | autoRefundMax | 50 USDC | |
perp/config.js INDEXES | minLegs | 2 | Minimum coins in a custom index |
perp/config.js INDEXES | maxLegs | 10 | Maximum coins in a custom index |
perp/config.js INDEXES | minLiquidityUsd | $250,000 | Minimum liquidity per coin |
perp/config.js INDEXES | maxLegWeight | 40 | |
perp/config.js INDEXES | minAgeDays | 7 days | Minimum age per coin |
perp/config.js INDEXES | perWalletPerDay | 3 | Custom indexes one wallet can create per 24 h |
perp/config.js INDEXES | globalPerHour | 20 | Custom indexes created per hour, all wallets |
Fixed in code, not exported:
| Where | Value |
|---|---|
| Server price cache per mint set | 15 s |
| Token card cache | 60 s |
| Candle cache per coin per range | 5 min |
| Upstream timeout and retry | 8 s, 1 retry on 429 or 5xx after 0.7 to 1.3 s |
| Jupiter and DexScreener batch | 50 and 30 mints per call |
| Paper book writes per IP | 20 per hour per instance |
| Burn quote slippage | 100 bps |
| Leveraged book: settle on read if older than | 5 s |
| Settlement heartbeat max age | 60 s |
| Pending withdrawal refund after | 2 min |
| Expired withdrawal: block height past | last valid height + 10 |
| Custody tx compute budget | 60,000 units at 50,000 micro-lamports |
| Signed message clock grace | 30 s |
| Site signing expiry and nonce | 4 min, 32 hex characters |
| Request body limit | 8,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.