This guide walks you through the complete flow of creating a new token on the Bullshot Protocol — from reading fee parameters to calling createToken on-chain.
| Step | Action | Description |
|---|---|---|
| 1 | Read fee params | Fetch creationFeeAmount, buyFeePercent, FEE_DENOMINATOR from BullshotFactory |
| 2 | Calculate msg.value |
initAmountIn + buyFee + creationFee |
| 3 | Call createToken |
Deploy a new BCToken + BondingCurve pair |
| 4 | Listen for Created event |
Get the new token and bonding curve addresses |
| 5 | (Optional) Buy more tokens | Call bondingCurve.buy() with additional BNB |
const factory = new ethers.Contract(FACTORY_ADDRESS, BullshotFactoryABI, provider);
const creationFeeAmount = await factory.creationFeeAmount(); // 150000000000000 wei = 0.00015 BNB
const buyFeePercent = await factory.buyFeePercent(); // 10 = 1%
const FEE_DENOMINATOR = 1000;
Current live values:
FEE_DENOMINATOR= 1000buyFeePercent= 10 → 1% buy feesellFeePercent= 10 → 1% sell feecreationFeeAmount= 150000000000000 wei (0.00015 BNB)
const initAmountIn = ethers.constants.Zero;
const msgValue = creationFeeAmount; // just the creation fee
const initAmountIn = ethers.utils.parseEther("0.5");
const buyFee = initAmountIn.mul(buyFeePercent).div(FEE_DENOMINATOR);
const msgValue = initAmountIn.add(buyFee).add(creationFeeAmount);
Formula:
msg.value = initAmountIn
+ (initAmountIn × buyFeePercent / FEE_DENOMINATOR)
+ creationFeeAmount
function createToken(
string memory name,
string memory ticker,
uint256 initAmountIn
) external payable
const signer = provider.getSigner();
const factory = new ethers.Contract(FACTORY_ADDRESS, BullshotFactoryABI, signer);
const tx = await factory.createToken(
"Bullshot", // token name
"BULL", // ticker symbol
initAmountIn, // BNB to spend on initial buy (0 = skip)
{ value: msgValue }
);
const receipt = await tx.wait();
// Parse the Created event from the receipt
const iface = new ethers.utils.Interface(BullshotFactoryABI);
let bondingCurveAddress, tokenAddress;
for (const log of receipt.logs) {
try {
const parsed = iface.parseLog(log);
if (parsed.name === "Created") {
bondingCurveAddress = parsed.args.bondingCurve;
tokenAddress = parsed.args.token;
console.log("BondingCurve:", bondingCurveAddress);
console.log("Token:", tokenAddress);
}
} catch (_) {}
}
After token creation, the bonding curve is live and ready for trading.
const bondingCurve = new ethers.Contract(bondingCurveAddress, BondingCurveABI, signer);
const additionalBuy = ethers.utils.parseEther("0.1"); // 0.1 BNB
const additionalFee = additionalBuy.mul(buyFeePercent).div(FEE_DENOMINATOR);
const buyMsgValue = additionalBuy.add(additionalFee);
// Pre-calculate expected output
const [expectedOut] = await bondingCurve.calcBuyExactIn(additionalBuy);
const minOut = expectedOut.mul(95).div(100); // 5% slippage
const deadline = Math.floor(Date.now() / 1000) + 60;
const buyTx = await bondingCurve.buy(
additionalBuy,
minOut,
signerAddress,
deadline,
{ value: buyMsgValue }
);
await buyTx.wait();
| Parameter | Type | Description | Example |
|---|---|---|---|
name |
string |
Full token name | "Bullshot" |
ticker |
string |
Token symbol / ticker | "BULL" |
initAmountIn |
uint256 |
BNB to spend on initial buy (excluding fee). Pass 0 to skip |
ethers.utils.parseEther("0.5") |
msg.value |
uint256 |
Must equal initAmountIn + buyFee + creationFeeAmount |
Calculated above |
| Parameter | Description |
|---|---|
totalSupply |
Always 1,000,000,000 tokens (18 decimals) |
tokenReserve |
800M tokens available for sale on bonding curve |
lpTokens |
200M tokens reserved for PancakeSwap LP at launch |
launchThreshold |
~24 BNB collected triggers DEX migration |
virtualEthReserve (initial) |
6 BNB (sets starting price) |
const { ethers } = require("ethers");
const BullshotFactoryABI = require("./BullshotFactory.abi.json");
const BondingCurveABI = require("./BondingCurve.abi.json");
const FACTORY_ADDRESS = "0x..."; // deployed BullshotFactory address
const RPC_URL = "https://bsc-dataseed.binance.org/";
async function createToken(privateKey, name, ticker, initBnbAmount) {
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(privateKey, provider);
const factory = new ethers.Contract(FACTORY_ADDRESS, BullshotFactoryABI, signer);
// Read fee params
const creationFeeAmount = await factory.creationFeeAmount();
const buyFeePercent = await factory.buyFeePercent();
const FEE_DENOMINATOR = 1000;
// Calculate msg.value
const initAmountIn = ethers.utils.parseEther(initBnbAmount.toString());
const buyFee = initAmountIn.mul(buyFeePercent).div(FEE_DENOMINATOR);
const msgValue = initAmountIn.add(buyFee).add(creationFeeAmount);
console.log(`Creating token "${name}" (${ticker})`);
console.log(`Initial buy: ${initBnbAmount} BNB`);
console.log(`Total msg.value: ${ethers.utils.formatEther(msgValue)} BNB`);
// Send transaction
const tx = await factory.createToken(name, ticker, initAmountIn, { value: msgValue });
const receipt = await tx.wait();
// Parse Created event
const iface = new ethers.utils.Interface(BullshotFactoryABI);
for (const log of receipt.logs) {
try {
const parsed = iface.parseLog(log);
if (parsed.name === "Created") {
console.log("\nToken created successfully!");
console.log("BondingCurve:", parsed.args.bondingCurve);
console.log("Token:", parsed.args.token);
console.log("Creator:", parsed.args.creator);
return {
bondingCurve: parsed.args.bondingCurve,
token: parsed.args.token
};
}
} catch (_) {}
}
}
// Example: Create "Bullshot" token with 0.5 BNB initial buy
createToken(process.env.PRIVATE_KEY, "Bullshot", "BULL", 0.5);
msg.value doesn't match the formula exactly, the transaction reverts with "Wrong value". Always read fee params on-chain before sending.pair field in the Created event is always address(0). The actual PancakeSwap pair is created lazily on the first post-creation buy.initAmountIn > 0 performs an initial buy in the same transaction as token creation — the creator gets the first tokens at the lowest price.