← Docs / API-Contract-BondingCurve.md ⬇ Download

BondingCurve Contract API Documentation

Overview

BondingCurve is the AMM pricing engine for the Bullshot Protocol. Each token created through BullshotFactory gets its own BondingCurve clone. Trading happens on the bonding curve until the ETH reserve crosses the launchThreshold, at which point all liquidity is migrated to PancakeSwap automatically.

Key Features:


Bonding Curve Formula

The curve uses a constant-product formula with virtual reserves:

k = virtualEthReserve × virtualTokenReserve

Initial state (at deployment):

Variable Value
virtualTokenReserve 1,000,000,000 ether
tokenReserve 800,000,000 ether
virtualEthReserve 6 ether
correlation (k) 6,000,000,000 ether²
launchThreshold 24 ether

The initial price per token is approximately:

price = virtualEthReserve / virtualTokenReserve = 6 / 1,000,000,000 = 6e-9 BNB/token

Constants

Name Type Value Description
FEE_DENOMINATOR uint16 1000 Per-mille denominator for fee calculations

State Variables

Name Type Visibility Description
totalSupply uint256 public Fixed token total supply (1,000,000,000 ether)
virtualTokenReserve uint256 public Current virtual token reserve (decreases on buy, increases on sell)
tokenReserve uint256 public Real tokens remaining for sale on the curve
virtualEthReserve uint256 public Current virtual ETH reserve (increases on buy, decreases on sell)
ethReserve uint256 public Real BNB held by this contract
correlation uint256 public Constant k = virtualEthReserve × virtualTokenReserve
launchThreshold uint256 public ETH amount that triggers DEX launch (~24 BNB)
buyFeePercent uint8 public Buy fee per-mille (charged on top of amountIn)
sellFeePercent uint8 public Sell fee per-mille (deducted from amountOut)
launchFeePercent uint8 public Fee per-mille of the ETH pool taken at DEX launch
feeRecipient address payable public Fee recipient address
uniswapV2Factory IUniswapV2Factory public PancakeSwap factory
uniswapV2Router IUniswapV2Router02 public PancakeSwap router
token BCToken public The BCToken associated with this bonding curve
pair address public PancakeSwap LP pair address (address(0) until first buy or DEX launch)
factory address public The BullshotFactory address

Structs

Data

Returned by getData().

Field Type Description
totalSupply uint256 Total token supply
tokenReserve uint256 Remaining tokens for sale
virtualTokenReserve uint256 Current virtual token reserve
ethReserve uint256 Real BNB held in the curve
virtualEthReserve uint256 Current virtual ETH reserve
launchThreshold uint256 BNB level that triggers DEX launch
buyFeePercent uint8 Buy fee per-mille
sellFeePercent uint8 Sell fee per-mille
pair address PancakeSwap pair address
balance uint256 Token balance of the queried account

Events

Buy

event Buy(address indexed buyer, uint amountIn, uint amountOut);

Note: This event is emitted on the BondingCurve. The BullshotFactory also proxies this to its global Buy event.

Sell

event Sell(address indexed seller, uint amountIn, uint amountOut);

Launch

Emitted when the bonding curve triggers DEX migration.

event Launch(uint tokenAmount, uint ethAmount);

Parameters:


Public Functions

buy

function buy(
    uint256 amountIn,
    uint256 amountOutMin,
    address to,
    uint256 deadline
) external payable returns (uint256 amountOut)

Buy tokens from the bonding curve. msg.value must exactly equal amountIn + buyFee.

Parameters:

msg.value requirement:

buyFee = amountIn * buyFeePercent / FEE_DENOMINATOR
msg.value = amountIn + buyFee

Returns: amountOut — tokens received in wei

Pricing formula:

newVirtualEthReserve  = virtualEthReserve + amountIn
newVirtualTokenReserve = k / newVirtualEthReserve
amountOut              = virtualTokenReserve - newVirtualTokenReserve

Effects:

  1. Collects buy fee and sends to feeRecipient
  2. Updates virtualEthReserve and virtualTokenReserve
  3. Creates PancakeSwap pair if not yet created (lazily)
  4. Transfers tokens to to
  5. Emits Buy (via BullshotFactory)
  6. If ethReserve >= launchThreshold, triggers DEX launch

DEX Launch Sequence:

  1. Calls token.launch() — unlocks free transfers
  2. Takes launch fee from ETH pool
  3. Calls uniswapV2Router.addLiquidityETH — migrates everything to PancakeSwap
  4. LP tokens are permanently locked (sent to address(0))
  5. Emits Launch

sell

function sell(
    uint256 amountIn,
    uint256 amountOutMin,
    uint256 deadline
) external returns (uint256 amountOut)

Sell tokens back to the bonding curve.

Prerequisites:

token.approve(bondingCurveAddress, amountIn);

Parameters:

Returns: amountOut — BNB received (after sell fee) in wei

Pricing formula:

newVirtualTokenReserve = virtualTokenReserve + amountIn
newVirtualEthReserve   = k / newVirtualTokenReserve
amountOut              = virtualEthReserve - newVirtualEthReserve
sellFee                = amountOut * sellFeePercent / FEE_DENOMINATOR
net BNB to seller      = amountOut - sellFee

calcBuyExactIn

function calcBuyExactIn(uint256 amountIn) public view returns (
    uint256 amountOut,
    uint256 amountInMax,
    uint256 amountOutMax
)

Simulates a buy: given a BNB amount, returns expected token output.

Parameters:

Returns:


calcBuyExactOut

function calcBuyExactOut(uint256 amountOut) public view returns (
    uint256 amountIn,
    uint256 amountOutMax
)

Simulates a buy: given a desired token amount, returns the BNB cost.

Returns:


calcSellExactIn

function calcSellExactIn(uint256 amountIn) public view returns (
    uint256 amountOut,
    uint256 amountInMax,
    uint256 amountOutMax
)

Simulates a sell: given a token amount, returns expected BNB output (after fee).


calcSellExactOut

function calcSellExactOut(uint256 amountOut) public view returns (
    uint256 amountIn,
    uint256 amountOutMax
)

Simulates a sell: given a desired BNB amount, returns the token cost required.


getData

function getData(address account) public view returns (Data memory data)

Returns a full state snapshot of the bonding curve plus the token balance of account.


getPostLaunchPrice

function getPostLaunchPrice() public view returns (uint256)

Returns the current token price in wei from PancakeSwap reserves. Only valid after DEX launch (pair != address(0)).

Formula:

if token0 == tokenAddress:
    price = reserve1 * 1e18 / reserve0
else:
    price = reserve0 * 1e18 / reserve1

Example Usage

Buy Tokens

const bondingCurve = new ethers.Contract(bondingCurveAddress, BondingCurveABI, signer);

const amountIn = ethers.utils.parseEther("0.1"); // 0.1 BNB
const buyFeePercent = await bondingCurve.buyFeePercent();
const buyFee = amountIn.mul(buyFeePercent).div(1000);
const msgValue = amountIn.add(buyFee);

// Pre-calculate expected output
const [amountOut] = await bondingCurve.calcBuyExactIn(amountIn);
const slippage = amountOut.mul(95).div(100); // 5% slippage tolerance

const deadline = Math.floor(Date.now() / 1000) + 60; // 1 min
const tx = await bondingCurve.buy(amountIn, slippage, signerAddress, deadline, {
    value: msgValue
});
await tx.wait();

Sell Tokens

const token = new ethers.Contract(tokenAddress, BCTokenABI, signer);
const bondingCurve = new ethers.Contract(bondingCurveAddress, BondingCurveABI, signer);

const amountIn = ethers.utils.parseEther("1000000"); // 1M tokens

// Approve bondingCurve to spend tokens
await token.approve(bondingCurveAddress, amountIn);

// Pre-calculate expected BNB output (after fee)
const [amountOut] = await bondingCurve.calcSellExactIn(amountIn);
const slippage = amountOut.mul(95).div(100);

const deadline = Math.floor(Date.now() / 1000) + 60;
const tx = await bondingCurve.sell(amountIn, slippage, deadline);
await tx.wait();

Get Full State

const data = await bondingCurve.getData(userAddress);
console.log("Token reserve:", ethers.utils.formatEther(data.tokenReserve));
console.log("ETH reserve:", ethers.utils.formatEther(data.ethReserve));
console.log("Launch threshold:", ethers.utils.formatEther(data.launchThreshold));
console.log("User balance:", ethers.utils.formatEther(data.balance));
console.log("Launched:", data.pair !== ethers.constants.AddressZero);

Important Notes

  1. msg.value precision: msg.value must equal exactly amountIn + (amountIn * buyFeePercent / 1000). Any mismatch causes "Wrong value" revert.
  2. DEX Launch is irreversible: Once ethReserve >= launchThreshold, the launch sequence runs within the same buy() call and cannot be reversed.
  3. LP tokens burned: At launch, LP tokens are sent to address(0) — liquidity is permanently locked.
  4. Sell after launch: After DEX launch, ethReserve = 0 so all sells on the bonding curve will revert. Users must trade on PancakeSwap directly.
  5. Virtual reserves: The virtual reserves ensure a non-zero initial price. The real tokenReserve starts at 800M (80% of supply); the remaining 200M are reserved for the PancakeSwap LP.