Bonding Curves & Dynamic Token Pricing

Speedrun Ethereum guide · Web3 Solidity course style

Solidity Tokenomics DeFi

Why Dynamic Pricing?

  • Fixed price launches → gas wars, unfair distribution, poor price discovery
  • Bonding curve = contract sets price from math → reacts to demand
  • Always-on liquidity: contract buys/sells at curve price
  • Transparent & predictable: function is public and auditable

What is a Bonding Curve?

  • Price P depends on current supply S (P = f(S))
  • Buy: mint → S ↑ → price rises (depending on curve)
  • Sell: burn → S ↓ → price falls
  • Contract is counterparty; no order book needed

Curve Types (and when)

Linear

P = mS + b

  • Steady, predictable growth
  • Good for community tokens

Exponential

P = a · e^{kS}

  • Cheap early, ramps fast
  • Rewards early adopters

Logarithmic

P = k · ln(S + c)

  • Rises fast, then stabilizes
  • Bootstraps then levels

Sigmoid (S-curve)

P = L / (1 + e^{-k(S - S0)})

  • Slow start → rapid growth → plateau
  • Good for cap/plateau behavior

How to Use the Demos

  • Drag sliders (m, b, k, etc.) to show price reaction
  • X-axis: supply (S). Y-axis: price P(S)
  • Tell a story: "early buyers pay X, late buyers pay Y"

Linear Curve (P = mS + b)

  • Example: m = 0.2, b = 1 → starts at 1 token/ETH, grows steadily
  • Use when you want predictability and fairness
  • Cost to mint Δ from S → S+Δ: ∫P(s)ds = m/2 · ((S+Δ)^2 − S^2) + bΔ
  • Contract: contracts/LinearBondingToken.sol

Linear Solidity (Community Token)

Use case: community or access tokens with predictable price steps.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract LinearBondingToken {
    uint256 public immutable m; // slope (wei per token)
    uint256 public immutable b; // base price
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    event Bought(address indexed buyer, uint256 amount, uint256 cost);
    event Sold(address indexed seller, uint256 amount, uint256 refund);
    constructor(uint256 _m, uint256 _b) { m = _m; b = _b; }
    function price(uint256 s) public view returns (uint256) { return m * s + b; }
    function costToMint(uint256 amount) public view returns (uint256) {
        uint256 s1 = totalSupply;
        uint256 s2 = totalSupply + amount;
        return m * (s1 + s2 + 1) * amount / 2 + b * amount;
    }
    function mint(uint256 amount) external payable {
        uint256 cost = costToMint(amount);
        require(msg.value >= cost, "pay cost");
        totalSupply += amount;
        balanceOf[msg.sender] += amount;
        emit Bought(msg.sender, amount, cost);
        if (msg.value > cost) payable(msg.sender).transfer(msg.value - cost);
    }
    function refund(uint256 amount) external {
        require(balanceOf[msg.sender] >= amount, "bal");
        uint256 s1 = totalSupply;
        uint256 s2 = totalSupply - amount;
        uint256 refundAmt = m * (s1 + s2 + 1) * amount / 2 + b * amount;
        balanceOf[msg.sender] -= amount;
        totalSupply -= amount;
        emit Sold(msg.sender, amount, refundAmt);
        payable(msg.sender).transfer(refundAmt);
    }
}

Exponential Curve (P = a · e^{kS})

  • Example: a = 0.5, k = 0.08 → very cheap early, rockets later
  • Good for hype launches / strong early adopter rewards
  • Warn: can get expensive fast; cap supply or add fee spread
  • Contract: contracts/ExpoBondingToken.sol

Exponential Solidity (Hype Launch)

Use case: hype mint/NFT drop with strong early adopter reward.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {PRBMathUD60x18} from "prb-math/PRBMathUD60x18.sol";
contract ExpoBondingToken {
    using PRBMathUD60x18 for uint256;
    uint256 public immutable a; // base
    uint256 public immutable k; // growth (scaled 1e18)
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    event Bought(address indexed buyer, uint256 amount, uint256 cost);
    constructor(uint256 _a, uint256 _k) { a = _a; k = _k; }
    function price(uint256 s) public view returns (uint256) {
        return a.mul(k.mul(s).exp()); // a * e^{k*s}
    }
    function costToMint(uint256 amount) public view returns (uint256) {
        uint256 cost; uint256 s = totalSupply;
        for (uint256 i = 0; i < amount; i++) { cost += price(s + i); }
        return cost;
    }
    function mint(uint256 amount) external payable {
        require(amount <= 50, "cap per tx");
        uint256 cost = costToMint(amount);
        require(msg.value >= cost, "pay cost");
        totalSupply += amount;
        balanceOf[msg.sender] += amount;
        emit Bought(msg.sender, amount, cost);
        if (msg.value > cost) payable(msg.sender).transfer(msg.value - cost);
    }
}

Use integral approximation (not loop) in production.

Logarithmic Curve (P = k · ln(S + c))

  • Example: k = 2, c = 1 → quick lift then stabilizes
  • Good to bootstrap liquidity, then keep price from exploding
  • Pick c to avoid ln(0); k tunes slope
  • Contract: contracts/LogBondingToken.sol

Log Solidity (Stabilizing Token)

Use case: stabilize price after early lift (e.g., utility token).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {PRBMathUD60x18} from "prb-math/PRBMathUD60x18.sol";
contract LogBondingToken {
    using PRBMathUD60x18 for uint256;
    uint256 public immutable k;
    uint256 public immutable c;
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    event Bought(address indexed buyer, uint256 amount, uint256 cost);
    constructor(uint256 _k, uint256 _c) { k = _k; c = _c; }
    function price(uint256 s) public view returns (uint256) { return k.mul((s + c).ln()); }
    function costToMint(uint256 amount) public view returns (uint256) {
        uint256 cost; uint256 s = totalSupply;
        for (uint256 i = 0; i < amount; i++) { cost += price(s + i); }
        return cost;
    }
    function mint(uint256 amount) external payable {
        uint256 cost = costToMint(amount);
        require(msg.value >= cost, "pay cost");
        totalSupply += amount;
        balanceOf[msg.sender] += amount;
        emit Bought(msg.sender, amount, cost);
        if (msg.value > cost) payable(msg.sender).transfer(msg.value - cost);
    }
}

Sigmoid (S-curve)

  • Example: L = 10, k = 0.2, S0 = 50 → slow start, mid surge, plateau ~10
  • Good when you want a soft cap / saturation behavior
  • Adjust S0 to shift inflection; k for steepness; L for ceiling
  • Contract: contracts/SigmoidBondingToken.sol

Sigmoid Solidity (Capped Growth)

Use case: memberships/credits with soft ceiling and mid-growth ramp.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {PRBMathUD60x18} from "prb-math/PRBMathUD60x18.sol";
contract SigmoidBondingToken {
    using PRBMathUD60x18 for uint256;
    uint256 public immutable L;  // ceiling price
    uint256 public immutable k;  // steepness (1e18)
    uint256 public immutable S0; // inflection
    uint256 public totalSupply;
    mapping(address => uint256) public balanceOf;
    event Bought(address indexed buyer, uint256 amount, uint256 cost);
    constructor(uint256 _L, uint256 _k, uint256 _S0) { L=_L; k=_k; S0=_S0; }
    function price(uint256 s) public view returns (uint256) {
        if (s >= S0) {
            uint256 expPos = k.mul(s - S0).exp();
            return L.mul(expPos).div(1e18 + expPos);
        } else {
            uint256 expPos = k.mul(S0 - s).exp();
            return L.div(1e18 + expPos);
        }
    }
    function costToMint(uint256 amount) public view returns (uint256) {
        uint256 cost; uint256 s = totalSupply;
        for (uint256 i = 0; i < amount; i++) cost += price(s + i);
        return cost;
    }
    function mint(uint256 amount) external payable {
        uint256 cost = costToMint(amount);
        require(msg.value >= cost, "pay cost");
        totalSupply += amount;
        balanceOf[msg.sender] += amount;
        emit Bought(msg.sender, amount, cost);
        if (msg.value > cost) payable(msg.sender).transfer(msg.value - cost);
    }
}

Use sigmoid to simulate saturation/ceiling pricing for memberships or bandwidth credits.

Solidity Building Blocks

  • Track total supply (S) and reserve asset
  • Price function P(S); buy/sell use integral to avoid loops
  • Events: Bought/Sold (buyer, amount, cost, price)
  • Security: reentrancy guard, fixed-point math, caps per tx

Risks & Tips

  • Front-running around large buys/sells → add spread/fees
  • Precision loss on expo/log → fixed-point libs
  • Liquidity drain if sells not fee-adjusted
  • Test edge cases: start/end supply, large Δ, pause switches

Resources

  • Guide: speedrunethereum.com/guides/solidity-bonding-curves-token-pricing
  • Course style: metana-bootcamp/web3-solidity-course
  • Reveal.js docs: revealjs.com
  • Math lib: prb-math

Q&A

Ask about curve selection, fees, edge cases, or demo steps.