BCToken is the ERC20 token contract deployed for every token created through the Bullshot Protocol. Each BCToken is a minimal proxy clone (ERC1167) of a master implementation, making deployment extremely gas-efficient. Token transfers to the PancakeSwap pair address are restricted until the associated BondingCurve triggers the launch() function — preventing sniping before DEX liquidity is added.
Key Features:
launched = trueinit() instead of a constructor (proxy pattern)| Name | Type | Visibility | Description |
|---|---|---|---|
name |
string |
public | Token name (e.g., "Bullshot") |
symbol |
string |
public | Token ticker symbol (e.g., "BULL") |
decimals |
uint8 |
public constant | Always 18 |
totalSupply |
uint256 |
public | Total minted supply (set at init, equals BondingCurve's totalSupply) |
launched |
bool |
public | false until BondingCurve calls launch(). When false, transfers to the PancakeSwap pair are blocked |
balanceOf |
mapping(address => uint256) |
public | ERC20 token balances |
allowance |
mapping(address => mapping(address => uint256)) |
public | ERC20 spending allowances |
event Transfer(address indexed from, address indexed to, uint256 value);
Emitted on every token transfer including minting (from = address(0)).
event Approval(address indexed owner, address indexed spender, uint256 value);
Emitted when approve() is called.
function transfer(address to, uint256 amount) public returns (bool)
Standard ERC20 transfer. Will revert with Forbidden() if launched == false and to is the PancakeSwap pair address.
Parameters:
to: Recipient addressamount: Token amount in weiReturns: true on success
function transferFrom(
address from,
address to,
uint256 amount
) public returns (bool)
Standard ERC20 transferFrom. Same anti-snipe restriction applies.
Special case: If launched == false and the caller is the bondingCurve address, the allowance check is bypassed (unlimited internal allowance).
Parameters:
from: Token holder addressto: Recipient addressamount: Token amount in weiReturns: true on success
function approve(address spender, uint256 amount) public returns (bool)
Standard ERC20 approve. Sets the allowance for spender to amount.
Parameters:
spender: Address to approveamount: Allowance amount in wei. Use type(uint256).max for unlimited.Returns: true on success
function launch() public
Unlocks transfers to the PancakeSwap pair. Sets launched = true.
Access control: Can only be called by the associated bondingCurve address. Reverts with Forbidden() if called by anyone else.
When called: This is called internally by the BondingCurve's buy() function when ethReserve >= launchThreshold.
function balanceOf(address account) public view returns (uint256)
Returns the token balance of account.
function allowance(address owner, address spender) public view returns (uint256)
Returns the remaining allowance that spender is permitted to spend on behalf of owner.
| Error | Description |
|---|---|
Forbidden() |
Thrown when transfer/transferFrom targets the PancakeSwap pair before launch, or when launch() is called by an address other than the bonding curve |
const token = new ethers.Contract(tokenAddress, BCTokenABI, provider);
const name = await token.name();
const symbol = await token.symbol();
const totalSupply = await token.totalSupply();
const launched = await token.launched();
const userBalance = await token.balanceOf(userAddress);
console.log(`${name} (${symbol})`);
console.log(`Total Supply: ${ethers.utils.formatEther(totalSupply)}`);
console.log(`Launched: ${launched}`);
console.log(`User Balance: ${ethers.utils.formatEther(userBalance)}`);
// Before calling bondingCurve.sell(), approve it to spend your tokens
const amountToSell = ethers.utils.parseEther("500000"); // 500,000 tokens
await token.approve(bondingCurveAddress, amountToSell);
// Then call bondingCurve.sell(...)
// Poll or use events on the BondingCurve
const bondingCurve = new ethers.Contract(bcAddress, BondingCurveABI, provider);
bondingCurve.on("Launch", (tokenAmount, ethAmount) => {
console.log("Token has launched to PancakeSwap!");
console.log("LP Token Amount:", ethers.utils.formatEther(tokenAmount));
console.log("LP ETH Amount:", ethers.utils.formatEther(ethAmount));
});
Anti-Snipe Mechanism: Before launched = true, any transfer whose to address equals the PancakeSwap pair will revert with Forbidden(). The pair address is lazily resolved from the PancakeSwap factory inside the notRestricted modifier.
Init instead of Constructor: Because BCToken is deployed as an ERC1167 proxy, the init() function acts as the constructor. It can only be called once (guarded by require(bondingCurve == address(0), 'Inited')).
Minting: All tokens are minted in init() directly to the bondingCurve address. No additional minting is possible after initialization.
BondingCurve Unlimited Allowance: The BondingCurve contract has an implicit unlimited allowance on all holders' balances pre-launch, implemented inside transferFrom. This allows the curve to move tokens freely during the pre-launch trading phase without requiring separate approve calls.
Fixed Supply: Total supply is always 1,000,000,000 tokens (1 billion, 18 decimals). 800M are sold on the bonding curve; 200M go to the PancakeSwap LP at launch.