Pump.fun bonding curve progress: get it via API (with the math)

· 8 min read

bonding curvegraduationreservespriceapi

Every pump.fun token spends its early life on a bonding curve: a deterministic pricing contract that sells tokens for SOL until its inventory runs out and the token graduates to PumpSwap. If you want to check bonding curve progress on pump.fun programmatically — how far along a token is, what the curve-implied price is, how much SOL the curve actually holds — you need exactly four fields, and the Raiden API returns all four on every single swap. This is the full walkthrough: the math, the fields, and a Python function that turns them into a progress number you can trust.

What the bonding curve actually is

Until graduation there is no order book and no liquidity providers. The curve prices every trade with a constant-product invariant over virtual reserves: k = v_sol × v_token. A buy deposits SOL into the virtual SOL side and withdraws tokens from the virtual token side, keeping k constant — so each successive buy gets fewer tokens per SOL and the price rises along a smooth, predetermined path. Sells run the same math in reverse. The reserves are called virtual because they include a synthetic offset that sets the starting price; they are pricing variables, not balances. What the curve actually holds — the SOL paid in, the tokens not yet sold — lives in a separate pair of real reserves. When the curve's real token inventory is exhausted, the token graduates: liquidity moves to a PumpSwap pool and curve math stops applying.

The four reserve fields, on every swap

This is where the pump.fun bonding curve API angle gets simple. Raiden indexes every fill, and each row of /tokens/{mint}/swaps carries the complete curve state after that fill:

FieldWhat it isWhat you use it for
v_sol_reservesvirtual SOL reserve after the fill (lamports, string)price numerator
v_token_reservesvirtual token reserve after the fill (base units, string)price denominator
real_sol_reservesSOL the curve actually holdshow much has been paid into the curve
real_token_reservestokens actually left in the curveprogress — drains toward zero

Because the state rides on every swap, you never need a separate "curve state" endpoint or an on-chain account fetch: the latest swap is the current state, and any historical swap is the state at that moment — which makes progress-over-time charts a pagination exercise, not an archive-node problem.

Pump.fun bonding curve calculation: price from virtual reserves

One call gets the current state — the most recent fill:

curl -H "X-API-Key: $RAIDEN_KEY" \
  "https://terminal.raiden.wtf/api/tokens/MINT/swaps?order=desc&limit=1"
{
  "data": [
    {
      "time": "2026-07-18T11:02:44Z",
      "mint": "9xQm…pump",
      "trader": "8psN…VRtf",
      "is_buy": true,
      "sol_amount": "488888889",
      "token_amount": "16528925619834",
      "price": "0.00000002958",
      "v_sol_reserves": "30488888889",
      "v_token_reserves": "1041666666666666",
      "real_sol_reserves": "488888889",
      "real_token_reserves": "761250000000000",
      "slot": 348812994,
      "block_index": 1287,
      "sig": "4Fr4…ULFK",
      "venue": 0
      … fee, creator_fee, tip, cu_price, cu_limit, priority_fee …
    }
  ],
  "next_cursor": "2026-07-18T11:02:44Z"
}

The pump.fun bonding curve calculation for price is one division — the virtual reserves are the curve's spot price:

# curve spot price in SOL per token, adjusted for decimals:
#   SOL side is in lamports (1 SOL = 1e9), token side in base units (6 decimals)
spot_price = (v_sol_reserves / 1e9) / (v_token_reserves / 1e6)

# with the row above:
#   (30488888889 / 1e9) / (1041666666666666 / 1e6)
#   = 30.488888889 SOL / 1,041,666,666.67 tokens ≈ 0.00000002927 SOL
#   — the price the NEXT fill will trade around
Decimals: all large amounts in the API are strings to avoid float loss — SOL amounts in lamports (1 SOL = 10⁹), token amounts in base units. The token object documents decimals: 6 for pump.fun mints, hence the 1e6. Parse with int(), never float(), before doing math. Each row also ships a precomputed price field — that one is the fill's own execution price, sol_amount / token_amount (decimal-adjusted), so it sits a hair away from the after-fill reserve ratio: the ratio is the curve's spot price for the next trade.

How to check bonding curve progress

Progress is the token side draining. Every buy removes tokens from real_token_reserves; graduation happens when the curve's inventory is gone. So the honest measure is a ratio between two observed states: the curve now, versus the curve at the start of its observable life. Fetch the token's first indexed swap with order=asc&limit=1, reconstruct the pre-fill state (if that first swap was a buy, the curve held its real_token_reserves plus the token_amount it sold), and divide:

progress = 1 − real_token_now / real_token_start

A token at 0.9 has sold 90% of the inventory it started with; a token stuck at 0.05 for hours is dead on the curve. The complementary signal is real_sol_reserves climbing — the SOL actually collected — which is the number worth charting next to volume when you want to separate genuine accumulation from wash churn.

Why a ratio and not a magic number: the curve's initial reserve sizes and its completion criteria are pump.fun implementation details — they are not part of the API contract and can change. Hardcode a "graduation at X SOL" constant and your dashboard breaks silently the day the protocol tunes it. The ratio above only uses observed reserves, and the authoritative graduation signal is the token object itself: status flips to "graduated", graduated_at timestamps it, amm_pool points at the PumpSwap pool.
Coverage: tick-level swaps are kept on a 6-month rolling window, with the complete record since the index launched on May 1, 2026 — nothing has rolled off yet. For any token created after that date, the first indexed swap is genuinely the token's first swap. For tokens created before it, the earliest fills predate the index, and the ratio reads as "progress since the earliest retained swap".

A Python progress function

Everything above, in one function you can drop into a monitor loop:

import requests

BASE = "https://terminal.raiden.wtf/api"
H = {"X-API-Key": "YOUR_KEY"}

def curve_price(row):
    # SOL side: lamports (1e9) · token side: base units (6 decimals)
    return (int(row["v_sol_reserves"]) / 1e9) / (int(row["v_token_reserves"]) / 1e6)

def curve_progress(mint):
    tok = requests.get(f"{BASE}/tokens/{mint}", headers=H).json()
    if tok["status"] == "graduated":
        return {"status": "graduated", "graduated_at": tok["graduated_at"],
                "progress": 1.0}

    swaps = lambda **p: requests.get(f"{BASE}/tokens/{mint}/swaps",
                                       params=p, headers=H).json()["data"]
    last, first = swaps(order="desc", limit=1), swaps(order="asc", limit=1)
    if not last or not first:
        return {"status": tok["status"], "progress": 0.0}
    last, first = last[0], first[0]

    # curve state BEFORE the first observed fill:
    # a buy took token_amount OUT of the curve, so add it back
    start = int(first["real_token_reserves"])
    if first["is_buy"]:
        start += int(first["token_amount"])

    now = int(last["real_token_reserves"])
    return {
        "status": tok["status"],           # "bonding"
        "progress": 1 - now / start,       # share of observed inventory sold
        "price_sol": curve_price(last),
        "sol_in_curve": int(last["real_sol_reserves"]) / 1e9,
    }

print(curve_progress("9xQm…pump"))
# {'status': 'bonding', 'progress': 0.269, 'price_sol': 2.927e-08, 'sol_in_curve': 0.489}

Three calls per check, all cheap. For a progress-over-time chart, paginate order=asc with the cursor and plot real_token_reserves per fill — the full download recipe is in the historical data with Python guide.

After graduation: PumpSwap, candles and the venue tag

The honest caveat: reserve math describes the curve, and the curve ends at graduation. From that point the token trades on PumpSwap, and its price comes from the same places every AMM price comes from — the indexed /tokens/{mint}/swaps fills and /tokens/{mint}/candles (tf=1s|30s|1m|1h|1d), with each swap row tagged by its venue so curve-era and PumpSwap-era fills are cleanly separable in one continuous history. The token object keeps serving last_price either way. If graduations are the event you actually care about, two follow-ups: the graduated tokens guide covers listing and exporting them via the screener's status filter, and a WebSocket stream with the grad kind pushes a graduate event the moment a curve completes — no polling loop required.

That is the whole system: virtual reserves set the price, real reserves measure the progress, status and graduated_at confirm the finish line — all from swap rows you can paginate freely. Full request and response shapes for every endpoint used here are in the API reference, and the pump.fun API overview covers access.

FAQ

Frequently asked questions

What is bonding curve progress on pump.fun?

The share of the curve's token inventory that buyers have already taken out. Every buy drains real_token_reserves toward zero; when the curve is exhausted the token graduates to PumpSwap. The robust way to measure it is a ratio computed from observed reserves — current real_token_reserves against the curve state at the first observed swap — not a comparison against a hardcoded protocol constant.

How is the pump.fun token price calculated on the bonding curve?

From the VIRTUAL reserves, constant-product style: spot price = v_sol_reserves / v_token_reserves, adjusted for decimals. Divide the SOL side by 1e9 (amounts are lamport strings) and the token side by 1e6 (pump.fun tokens have 6 decimals) to get SOL per token. Every indexed swap carries both after-fill virtual reserves, so the curve price is recomputable at any point in a token's history; each row also ships a price field with that fill's own execution price (sol_amount / token_amount, decimal-adjusted).

What is the difference between virtual and real reserves?

Virtual reserves (v_sol_reserves, v_token_reserves) are the pricing variables of the constant-product formula — they include a synthetic offset, so they never reflect actual balances. Real reserves (real_sol_reserves, real_token_reserves) are what the curve actually holds: the SOL collected from buyers and the tokens still available. Price comes from the virtual pair; progress comes from the real pair.

How do I know when a pump.fun token has graduated?

Authoritatively from the token object: status flips to "graduated", graduated_at records when, and amm_pool points at the PumpSwap pool. Do not infer graduation from a hardcoded SOL threshold — curve parameters are protocol internals, not part of the API contract, and can change. For realtime, the grad WebSocket kind pushes a graduate event the moment it happens.

Why shouldn't I hardcode a SOL graduation target in my progress formula?

Because the curve's initial reserve sizes and completion criteria are pump.fun implementation details that can be changed by the protocol at any time. A formula anchored to a magic number silently breaks the day the parameters move. A ratio built from observed reserves keeps working, and tokens.status / graduated_at confirm the actual event.

Can I still compute price from reserves after graduation?

Not with curve math. The constant-product formula describes the bonding curve, and after graduation trading moves to PumpSwap. Post-graduation prices come from the same swap rows and OHLCV candles, tagged with the venue field — the token's price history stays continuous through graduation, it just stops being curve math.

Build on the same data

Private beta — free invite-only keys, full REST + WebSocket access, and the same firehose that powers the Terminal.