Bonding Curves: The Mathematics of Fair Launches
Bonding curves are smart contract mechanisms that automatically determine token prices based on supply and demand. They enable fair launches where everyone trades against the same mathematical formula, eliminating the need for market makers and preventing price manipulation.
Why it matters: Understanding bonding curve math helps you predict price movements, identify bonding thresholds, and build trading strategies around launchpad tokens.
What is a Bonding Curve?
A bonding curve is a mathematical function that defines the relationship between a token’s price and its supply. As tokens are bought, the price increases along the curve. As tokens are sold, the price decreases.
The most common types are:
| Curve Type | Formula | Characteristics |
|---|
| Constant Product (x*y=k) | price = k / supply | Hyperbolic curve, used by Pumpfun |
| Exponential | price = base^supply | Aggressive price growth |
| Linear | price = m * supply + b | Predictable, steady growth |
| Sqrt Price | price = sqrtPrice² / 2^128 | Concentrated liquidity, used by Meteora DBC |
Pumpfun: Constant Product AMM
Pumpfun uses a constant product Automated Market Maker (AMM) with virtual reserves. This is the same formula popularized by Uniswap V2.
Where:
x = Virtual SOL reserves (reserve0)
y = Virtual Token reserves (reserve1)
k = Constant product (invariant)
Understanding Virtual vs Real Reserves
Critical Concept: Pumpfun uses TWO types of reserves:
- Virtual Reserves: Used for price calculation (includes “phantom” liquidity)
- Real Reserves: Actual tokens/SOL locked in the contract
| Reserve Type | SOL | Token | Purpose |
|---|
| Virtual | virtualSolReserves | virtualTokenReserves | Price calculation via AMM formula |
| Real | realSolReserves | realTokenReserves | Actual assets in contract |
| Initial Virtual | 30 SOL | 1,073,000,000,000,000 | Starting point for price curve |
| Initial Real Token | 0 SOL | 793,100,000,000,000 | Tokens available for sale |
Why virtual reserves? The virtual reserves create artificial depth at launch, preventing the first buyer from getting 100% of tokens for pennies. The 30 SOL virtual reserve means the price starts as if there’s already 30 SOL of liquidity.
Price Calculation
The price (exchange rate) is calculated as:
// Returns: how many tokens you get per 1 SOL
exchangeRate = virtualTokenReserves / virtualSolReserves
In code:
getPrice(pool: PumpfunPool): number {
if (pool.reserve0 === 0n || pool.reserve1 === 0n) {
return 0;
}
// reserve0 = virtualSolReserves, reserve1 = virtualTokenReserves
// Returns tokens per SOL (exchange rate)
return bigIntDiv(pool.reserve1, pool.reserve0);
}
To get the token price in SOL:
tokenPriceInSOL = virtualSolReserves / virtualTokenReserves
// Or simply: 1 / exchangeRate
Initial Parameters
When a Pumpfun token is created, the bonding curve initializes with:
| Parameter | Value | Description |
|---|
initialVirtualSolReserves | 30 SOL (30,000,000,000 lamports) | Virtual SOL for price calculation |
initialVirtualTokenReserves | 1,073,000,000,000,000 | Virtual tokens (6 decimals = ~1.073B) |
initialRealTokenReserves | 793,100,000,000,000 | Actual tokens for sale (~793.1M) |
bondedCriteria | 85.005 SOL | Real SOL to trigger migration |
Note: The total supply is 1B tokens, but only ~793.1M are available for purchase. The difference (~280M) is the virtual reserve that creates initial price depth.
How Buying Works
When you buy tokens:
- You send SOL to the bonding curve
- The curve calculates tokens using the constant product formula:
// Classic AMM swap formula
tokens_out = (sol_in * virtualTokenReserves) / (virtualSolReserves + sol_in)
- Both virtual AND real reserves update:
virtualSolReserves += sol_in
virtualTokenReserves -= tokens_out
realSolReserves += sol_in
realTokenReserves -= tokens_out
How Selling Works
When you sell tokens:
sol_out = (tokens_in * virtualSolReserves) / (virtualTokenReserves + tokens_in)
Bonding Percentage
The bonding progress shows how close the token is to migration:
// How many tokens have been sold vs total available
bondingPercentage = (initialVirtualTokenReserves - currentVirtualTokenReserves) * 100
/ initialRealTokenReserves
When tokens are bought, virtualTokenReserves decreases. The bonding percentage tracks what % of the initialRealTokenReserves has been purchased.
Bonding (Migration) Threshold
When virtualSolReserves >= ~85 SOL, the token “bonds” and migrates to Raydium:
bondedCriteria = 86000000000n; // ~86 SOL in lamports
async isBonded(pool: PumpfunPool): Promise<boolean> {
if (pool.bonded) return true;
if (pool.reserve0 >= this.bondedCriteria) return true;
return false;
}
Since virtual SOL starts at 30 SOL, approximately 55-56 SOL of real purchases triggers migration.
For a given SOL input, the price impact is:
priceImpact = sol_in / (virtualSolReserves + sol_in)
Example: If virtual SOL reserves = 30 SOL and you buy with 3 SOL, your price impact is ~9.1%. Early buys have massive impact because of the relatively low virtual reserve.
Numerical Example
At launch:
- Virtual SOL: 30,000,000,000 lamports (30 SOL)
- Virtual Tokens: 1,073,000,000,000,000 (1.073B with 6 decimals)
- Exchange rate: 1,073B / 30 = 35,766,666,666 tokens per SOL
- Token price: 30 / 1,073B = 0.000000028 SOL per token
After 10 SOL buy:
- Tokens received: (10 × 1,073B) / (30 + 10) = 268,250,000,000 tokens (~268M)
- New Virtual SOL: 40 SOL
- New Virtual Tokens: 804,750,000,000,000 (804.75B)
- New exchange rate: 804.75B / 40 = 20,118,750,000 tokens per SOL
- Price increased by ~44%
Meteora DBC: Dynamic Bonding Curve
Meteora’s Dynamic Bonding Curve (DBC) uses a sqrt price model with concentrated liquidity, similar to Uniswap V3 but optimized for launchpads.
Core Concept: Sqrt Price
Instead of storing price directly, Meteora DBC stores the square root of price as a Q64.64 fixed-point number:
sqrtPrice = sqrt(price) * 2^64
To get the actual price:
getPrice(pool: MeteoraDBCPool): number {
const sqrtPrice = pool.extra?.sqrtPriceX64 ?? 0n;
if (sqrtPrice === 0n) return 0;
// Convert from Q64.64 fixed point
return getPriceFromSqrtX(sqrtPrice, 64);
}
// Where getPriceFromSqrtX does:
function getPriceFromSqrtX(sqrtPrice: bigint, resolution: number): number {
const price = (sqrtPrice * sqrtPrice) / (1n << BigInt(resolution * 2));
return Number(price) / (2 ** resolution);
}
The Curve Structure
Meteora DBC defines a curve as an array of price/liquidity points:
type Curve = Array<{
sqrtPrice: bigint; // Price at this point
liquidity: bigint; // Liquidity available
}>;
This creates a piecewise linear bonding curve where:
- Each segment has different liquidity concentration
- Price moves faster in segments with lower liquidity
- Price moves slower in segments with higher liquidity
Quote Reserve Calculation
The virtual quote (SOL) reserve is calculated by integrating along the curve:
calculateQuoteVirtualReserve(pool: MeteoraDBCPool): bigint {
const curve = pool.extra.curve;
const nextSqrtPrice = pool.extra.sqrtPriceX64;
const sqrtStartPrice = pool.extra.sqrtPriceStart;
let totalAmount = 0n;
for (let i = 0; i < curve.length; i++) {
const currentPoint = curve[i];
const lowerSqrtPrice = i === 0
? sqrtStartPrice
: curve[i - 1].sqrtPrice;
if (nextSqrtPrice > lowerSqrtPrice) {
const upperSqrtPrice = nextSqrtPrice < currentPoint.sqrtPrice
? nextSqrtPrice
: currentPoint.sqrtPrice;
// Calculate delta amount for this segment
const maxAmountIn = getDeltaAmountQuoteUnsigned(
lowerSqrtPrice,
upperSqrtPrice,
currentPoint.liquidity
);
totalAmount += maxAmountIn;
}
}
return totalAmount;
}
For each curve segment:
getDeltaAmountQuoteUnsigned(
lowerSqrtPrice: bigint,
upperSqrtPrice: bigint,
liquidity: bigint
): bigint {
// Δquote = L * (√P_upper - √P_lower) / 2^128
const deltaSqrtPrice = upperSqrtPrice - lowerSqrtPrice;
const prod = liquidity * deltaSqrtPrice;
return prod >> BigInt(RESOLUTION * 2); // RESOLUTION = 64
}
Bonding Percentage
Meteora DBC calculates bonding progress as:
bondingPercentage = (quoteReserve / migrationQuoteThreshold) * 100
When bondingPercentage >= 100%, the token migrates to Meteora DAMM.
Migration Thresholds
| Parameter | Description |
|---|
migrationQuoteThreshold | SOL amount needed to trigger migration |
sqrtPriceStart | Initial sqrt price |
curve | Array of (sqrtPrice, liquidity) points defining the curve shape |
Comparison: Pumpfun vs Meteora DBC
| Aspect | Pumpfun | Meteora DBC |
|---|
| Curve Type | Constant Product (x*y=k) | Sqrt Price with Concentrated Liquidity |
| Price Formula | SOL / Token | sqrtPrice² / 2^128 |
| Liquidity Distribution | Uniform across all prices | Concentrated in curve segments |
| Price Impact | Higher for same volume | Variable based on liquidity concentration |
| Bonding Trigger | 86 SOL raised | Custom migrationQuoteThreshold |
| Migration Target | Raydium | Meteora DAMM |
| Complexity | Simple | More complex, more flexible |
Practical Applications
1. Predicting Bonding Time
For Pumpfun:
// Bonding happens when virtualSolReserves reaches ~86 SOL
// Since it starts at 30 SOL, you need ~56 SOL of real purchases
const INITIAL_VIRTUAL_SOL = 30_000_000_000n; // 30 SOL
const BONDED_CRITERIA = 86_000_000_000n; // 86 SOL
// Current progress (using virtual reserves)
const currentVirtualSol = pool.reserve0;
const progress = Number(currentVirtualSol - INITIAL_VIRTUAL_SOL) * 100
/ Number(BONDED_CRITERIA - INITIAL_VIRTUAL_SOL);
// Real SOL needed to bond
const solNeeded = BONDED_CRITERIA - currentVirtualSol;
For Meteora DBC:
// Current progress
const quoteReserve = calculateQuoteVirtualReserve(pool);
const progress = (quoteReserve * 100n) / migrationQuoteThreshold;
// SOL needed to bond
const solNeeded = migrationQuoteThreshold - quoteReserve;
2. Price Impact Estimation
For a buy of X SOL on Pumpfun:
// Current exchange rate (tokens per SOL)
const currentRate = virtualTokenReserves / virtualSolReserves;
// After your buy
const newSolReserves = virtualSolReserves + X;
const tokensReceived = (X * virtualTokenReserves) / newSolReserves;
// Your effective rate (tokens per SOL)
const effectiveRate = tokensReceived / X;
// Price impact (how much worse your rate is vs spot)
const priceImpact = (currentRate - effectiveRate) / currentRate;
// Or equivalently: X / (virtualSolReserves + X)
3. Slippage Calculation
// Expected tokens at current spot rate (no slippage)
const expectedTokens = (solIn * virtualTokenReserves) / virtualSolReserves;
// Actual tokens (accounting for price impact)
const actualTokens = (solIn * virtualTokenReserves) / (virtualSolReserves + solIn);
// Slippage percentage
const slippage = (expectedTokens - actualTokens) / expectedTokens * 100;
// This equals: solIn / (virtualSolReserves + solIn) * 100
4. Calculating Token Price in USD
// Get SOL price from oracle
const solPriceUSD = 150; // example: $150 per SOL
// Token price in SOL
const tokenPriceInSOL = virtualSolReserves / virtualTokenReserves;
// Token price in USD
const tokenPriceUSD = tokenPriceInSOL * solPriceUSD;
API Access
Query bonding curve data through Mobula’s API:
# Get token details for a Pumpfun token (Fartcoin)
curl -X GET "https://demo-api.mobula.io/api/2/token/details?blockchain=solana&address=9BB6NFEcjBCtnNLFko2FqVQBq8HHM13kCyYcdQbgpump" \
-H "Authorization: YOUR_API_KEY"
# Get token details for Wojak
curl -X GET "https://demo-api.mobula.io/api/2/token/details?blockchain=solana&address=8J69rbLTzWWgUJziFY8jeu5tDwEPBwUz4pKBMr5rpump" \
-H "Authorization: YOUR_API_KEY"
The response includes:
bonded - Whether the token has migrated to Raydium/Meteora
bondingPercentage - Progress toward migration (0-100)
bondingCurveAddress - Address of the bonding curve contract
preBondingFactory - Original launchpad (e.g., Pumpfun, Meteora DBC)
liquidityUSD - Current liquidity in the curve