Buy & Sell Tokens
Trading on the bonding curve before graduation. Quote, then send the exact amount. Examples use ethers v6.
Interface
const CURVE_ABI = [
"function buy(uint256 token_lots) payable",
"function sell(uint256 token_lots)",
"function quote_buy_price(uint256) view returns (uint256,uint256,uint256)",
"function quote_sell_price(uint256) view returns (uint256,uint256,uint256)",
"function get_supply_lots() view returns (uint32)",
"function get_user_token_balance(address) view returns (uint256)",
"function is_graduated() view returns (bool)",
"function is_refund_mode() view returns (bool)"
];
const curve = new ethers.Contract(CURVE_ADDRESS, CURVE_ABI, signer);from web3 import Web3
# web3.py needs a JSON ABI — it has no equivalent of ethers' human-readable
# fragments. Load the compiler output rather than hand-writing it.
CURVE_ABI = json.load(open("BondingCurveTemplate.abi.json"))
w3 = Web3(Web3.HTTPProvider(RPC_URL))
acct = w3.eth.account.from_key(PRIVATE_KEY)
curve = w3.eth.contract(address=CURVE_ADDRESS, abi=CURVE_ABI)Buying
const lots = 100n;
// (base, tax, total) — send exactly `total`
const [base, tax, total] = await curve.quote_buy_price(lots);
const tx = await curve.buy(lots, { value: total });
await tx.wait();lots = 100
# (base, tax, total) — send exactly `total`
base, tax, total = curve.functions.quote_buy_price(lots).call()
tx = curve.functions.buy(lots).build_transaction({
"from": acct.address,
"value": total,
"nonce": w3.eth.get_transaction_count(acct.address),
})
signed = acct.sign_transaction(tx)
w3.eth.wait_for_transaction_receipt(
w3.eth.send_raw_transaction(signed.raw_transaction)
)buy() asserts msg.value == total. Overpaying reverts rather than refunding the difference. Quote and submit tightly — any buy that lands before yours changes the price and your transaction fails with Incorrect payment. Re-quote and retry.
Limits to check first
- Per-wallet cap — post-trade balance may not exceed 36,000 lots. Check
get_user_token_balance(you) + lots <= 36,000. - Supply cap —
get_supply_lots() + lots <= 800,000. - Minimum — at least 1 lot.
The graduating buy
The buy that brings supply to exactly 800,000 lots also runs graduation in the same transaction — minting the liquidity allocation, unlocking transfers, creating the pool and funding the secondary. Budget substantially more gas for it, and expect Graduated, TokenRegistered and SecondaryReserved in the same receipt.
Selling
const [base, tax, proceeds] = await curve.quote_sell_price(lots);
// No approval needed — the curve is the token's minter and burns directly
const tx = await curve.sell(lots);
await tx.wait();base, tax, proceeds = curve.functions.quote_sell_price(lots).call()
# No approval needed — the curve is the token's minter and burns directly
tx = curve.functions.sell(lots).build_transaction({
"from": acct.address,
"nonce": w3.eth.get_transaction_count(acct.address),
})
signed = acct.sign_transaction(tx)
w3.eth.wait_for_transaction_receipt(
w3.eth.send_raw_transaction(signed.raw_transaction)
)The curve is the token's minter and calls burnFrom directly without consuming allowance. It verifies your balance itself. Do not call approve() before selling on the curve — it does nothing.
Restrictions
- The deployer can never sell.
sell()rejects the deployer address outright, in every state. - No selling at the floor. Supply cannot drop below 60,000 lots.
- Selling is unavailable once the curve has graduated or entered refund mode — trade on the DEX instead.
Checking tradability
const [graduated, refunding] = await Promise.all([
curve.is_graduated(),
curve.is_refund_mode()
]);
if (graduated) {
// Curve is closed — trade the DEX pool
} else if (refunding) {
// Curve is closed — see the refund guide
} else {
// Curve is open
}graduated = curve.functions.is_graduated().call()
refunding = curve.functions.is_refund_mode().call()
if graduated:
pass # Curve is closed — trade the DEX pool
elif refunding:
pass # Curve is closed — see the refund guide
else:
pass # Curve is openBefore trading against any curve, check Factory.is_curve(curveAddress). Anyone can deploy a contract with the same ABI; only a factory-born curve can graduate a token, and only that one is subject to the rules documented here. The token address starting 0x24 is a useful filter but not proof.
Understanding the quote
Both quotes return (base, tax, net). The tax falls from 1,200 (12%) at the floor to 120 (1.2%) once supply passes 740,000,000, and is computed on the trade's average supply, so it is symmetric between a buy and a sell of the same range. See Price Calculation for the full derivation.