A deep dive into how the Pump protocol calculates token prices using a constant-product bonding curve.
The Pump bonding curve uses a constant-product AMM formula (similar to Uniswap) to determine token prices. When a token is created, it starts with virtual reserves that define the initial price. As users buy tokens, the price increases along the curve. When the curve is "complete," the token graduates to a full AMM pool.
The bonding curve tracks two sets of reserves:
| Reserve | Purpose |
|---|---|
virtualTokenReserves |
Token side of the constant-product formula — includes both real and virtual liquidity |
virtualSolReserves |
SOL side of the constant-product formula — includes both real and virtual liquidity |
realTokenReserves |
Actual tokens available for purchase — decreases as users buy |
realSolReserves |
Actual SOL deposited by buyers — increases as users buy |
The "virtual" reserves are larger than the "real" reserves. This virtual liquidity ensures the price starts at a reasonable level instead of zero.
When a new token is created, its bonding curve is initialized from the Global config:
import { newBondingCurve } from "@nirholas/pump-sdk";
const curve = newBondingCurve(global);
// {
// virtualTokenReserves: global.initialVirtualTokenReserves,
// virtualSolReserves: global.initialVirtualSolReserves,
// realTokenReserves: global.initialRealTokenReserves,
// realSolReserves: BN(0),
// tokenTotalSupply: global.tokenTotalSupply,
// complete: false,
// creator: PublicKey.default,
// isMayhemMode: global.mayhemModeEnabled,
// }The core invariant is:
This product
virtualSolReservesincreases (more SOL in the pool)virtualTokenReservesdecreases (tokens leave the pool)- The ratio changes → price goes up
The SDK provides getBuyTokenAmountFromSolAmount:
import { getBuyTokenAmountFromSolAmount } from "@nirholas/pump-sdk";
const tokensOut = getBuyTokenAmountFromSolAmount({
global,
feeConfig,
mintSupply: bondingCurve.tokenTotalSupply, // or null for new tokens
bondingCurve, // or null for new tokens
amount: solAmount, // SOL in lamports
});Under the hood, the calculation works in three steps:
- Deduct fees from the input SOL amount:
- Apply constant-product formula:
- Cap at real reserves — you can never buy more than
realTokenReserves:
The inverse: getBuySolAmountFromTokenAmount:
import { getBuySolAmountFromTokenAmount } from "@nirholas/pump-sdk";
const solNeeded = getBuySolAmountFromTokenAmount({
global,
feeConfig,
mintSupply: bondingCurve.tokenTotalSupply,
bondingCurve,
amount: tokenAmount,
});Formula:
Then fees are added on top:
The SDK provides getSellSolAmountFromTokenAmount:
import { getSellSolAmountFromTokenAmount } from "@nirholas/pump-sdk";
const solOut = getSellSolAmountFromTokenAmount({
global,
feeConfig,
mintSupply: bondingCurve.tokenTotalSupply,
bondingCurve,
amount: tokenAmount,
});Formula:
Then fees are subtracted:
The bonding curve market cap is computed as:
import { bondingCurveMarketCap } from "@nirholas/pump-sdk";
const mcap = bondingCurveMarketCap({
mintSupply: bondingCurve.tokenTotalSupply,
virtualSolReserves: bondingCurve.virtualSolReserves,
virtualTokenReserves: bondingCurve.virtualTokenReserves,
});
// Returns BN in lamportsThe market cap is used by the fee tier system to determine which fee rates apply.
A bonding curve is "complete" when realTokenReserves reaches zero — all available tokens have been purchased. At that point:
bondingCurve.completebecomestrue- The token is eligible for migration to a PumpAMM pool
- No more buy/sell operations are possible on the bonding curve
- Use
migrateInstruction()to move the token to an AMM pool
if (bondingCurve.complete) {
// Token has graduated — migrate to AMM
const ix = await PUMP_SDK.migrateInstruction({
withdrawAuthority: global.withdrawAuthority,
mint,
user: wallet.publicKey,
});
}Once a bonding curve has been migrated, its virtualTokenReserves is set to zero. All SDK math functions return BN(0) when they detect this:
// migrated bonding curve
if (bondingCurve.virtualTokenReserves.eq(new BN(0))) {
return new BN(0); // No more bonding curve trading
}Starting with typical initial reserves:
| Parameter | Value |
|---|---|
initialVirtualTokenReserves |
1,073,000,000,000,000 |
initialVirtualSolReserves |
30,000,000,000 (30 SOL) |
initialRealTokenReserves |
793,100,000,000,000 |
tokenTotalSupply |
1,000,000,000,000,000 (1B tokens) |
Initial price per token:
Buying 0.1 SOL worth of tokens (ignoring fees):
After this trade, the new reserves would be:
virtualTokenReserves≈ 1,069,436,879,946,880virtualSolReserves= 30,100,000,000
The price has increased slightly because the ratio changed.
Larger trades cause more price impact (slippage). The constant-product formula naturally provides:
- Small trades → minimal price impact
- Large trades → significant price impact
- Approaching graduation → very high price impact (fewer
realTokenReservesleft)
Use the slippage parameter in buyInstructions() / sellInstructions() to set maximum acceptable slippage as a percentage.
- Fee Tiers — How fee rates are determined by market cap
- Token Lifecycle — Full lifecycle from creation to AMM
- API Reference — Complete function signatures
- Examples — Working code samples