BondingCurveToken & CompanyTokenLauncher
The company token system has two phases: a Bancor bonding curve for price discovery, followed by automatic graduation to a Uniswap V4 pool when market cap reaches the threshold.
Contracts
| Contract | Address | Role |
|---|---|---|
| CompanyTokenLauncher | 0x725Bca4a1E4Db18C807C2aB8663e1Fd3cbff99e9 | Factory, bonding curve management, V4 graduation |
| BondingCurveToken | Per-company (EIP-1167) | ERC-20 token with factory-restricted mint/burn |
| BancorFormula | Library | Connector weight math for buy/sell price calculation |
Bonding Curve Constants
| Parameter | Value | Description |
|---|---|---|
| Connector Weight (CW) | 333,333 | Bancor weight numerator (1/3) |
| PPM | 1,000,000 | Parts per million denominator |
| Seed Supply | 1,000 tokens (1e21 wei) | Initial virtual liquidity |
| Seed Reserve | 100 USDC (1e8 wei) | Initial virtual reserve |
| Graduation Threshold | 10,000 USDC | Market cap trigger for V4 migration |
Token Lifecycle
Phase 1 — Bonding Curve
- A company is launched via
launchCompany(). This deploys a newBondingCurveTokeninstance and seeds the curve with 1,000 tokens and 100 USDC virtual reserve. - Anyone can buy tokens via
buyFromCurve()using USDC. The Bancor formula calculates the token amount. - Anyone can sell tokens back via
sellToCurve(). The formula calculates the USDC return. - Price rises with demand according to the Bancor power curve.
Phase 2 — V4 Graduation
- When market cap reaches
graduationThreshold(default $10K), the token auto-graduates. - The BondingCurveToken is marked as graduated (mint/burn disabled permanently).
- A Uniswap V4 pool is initialized with the accumulated reserve as liquidity.
- If a ScoutFundV2 is configured, it auto-invests in the new pool.
- Post-graduation, the token trades freely on Uniswap V4 with the CeosHook fee.
BondingCurveToken Interface
/// @notice The factory contract authorized to mint/burn (immutable, set in constructor)
function factory() external view returns (address);
/// @notice The ERC-8004 identity hash this token represents
function identityHash() external view returns (bytes32);
/// @notice Company ID hash (zero for legacy agent tokens)
function companyId() external view returns (bytes32);
/// @notice Market cap threshold for V4 graduation (USDC, 6 decimals)
function graduationThreshold() external view returns (uint256);
/// @notice Whether this token has graduated to a V4 pool
function isGraduated() external view returns (bool);
/// @notice Mint tokens (factory only, reverts if graduated)
function mint(address to, uint256 amount) external;
/// @notice Burn tokens from holder (factory only, no allowance needed, reverts if graduated)
function burnFrom(address from, uint256 amount) external;
/// @notice Initialize company-specific fields (factory only, once)
function initializeCompany(bytes32 _companyId, uint256 _graduationThreshold) external;
/// @notice Mark token as graduated (factory only)
function graduate() external;The burnFrom function bypasses ERC-20 allowance by design. Only the immutable factory (set in the constructor) can call it, and the factory address cannot be changed after deployment.
CompanyTokenLauncher Interface
Launch
/// @notice Deploy a company token on a bonding curve.
/// @param companyId Unique identifier (keccak256 of company slug)
/// @param name Token name (e.g., "ZeroToOne")
/// @param symbol Token symbol (e.g., "ZTO")
/// @return token The deployed BondingCurveToken address
function launchCompany(
bytes32 companyId,
string calldata name,
string calldata symbol
) external returns (address token);Requires LAUNCHER_ROLE. Duplicate companyId values are rejected.
Trading
/// @notice Buy company tokens from the bonding curve using USDC.
/// @param companyId Company identifier
/// @param usdcAmount Amount of USDC to spend (6 decimals)
/// @param minOut Minimum tokens to receive (slippage protection)
/// @return tokensOut Number of tokens received (18 decimals)
function buyFromCurve(
bytes32 companyId,
uint256 usdcAmount,
uint256 minOut
) external returns (uint256 tokensOut);
/// @notice Sell company tokens back to the bonding curve for USDC.
/// @param companyId Company identifier
/// @param tokenAmount Amount of tokens to sell (18 decimals)
/// @param minOut Minimum USDC to receive (slippage protection)
/// @return usdcOut Amount of USDC received (6 decimals)
function sellToCurve(
bytes32 companyId,
uint256 tokenAmount,
uint256 minOut
) external returns (uint256 usdcOut);Views
/// @notice Current token price on the bonding curve.
/// price = reserve * PPM / (supply * CW)
/// @return price USDC per token (6 decimals, scaled to 1e18 token)
function getCurrentPrice(bytes32 companyId) external view returns (uint256);
/// @notice Current market cap on the bonding curve.
/// marketCap = reserve * PPM / CW
/// @return marketCap In USDC (6 decimals)
function getCurrentMarketCap(bytes32 companyId) external view returns (uint256);
/// @notice Get bonding curve state for a company.
function getBondingState(bytes32 companyId) external view returns (BondingState memory);
/// @notice Get the V4 pool record for a graduated company.
function getCompanyPool(bytes32 companyId) external view returns (CompanyPool memory);
/// @notice Total number of companies that have launched tokens.
function totalCompanies() external view returns (uint256);Structs
struct BondingState {
address token; // BondingCurveToken address
uint256 reserve; // USDC reserve balance (6 decimals)
uint256 graduationThreshold; // Market cap threshold for V4 graduation
bool graduated; // Whether graduated to V4
uint256 launchedAt; // Timestamp
}
struct CompanyPool {
address token;
PoolId poolId;
uint256 initialSupply;
uint256 initialPriceUsdc;
uint256 launchedAt;
}Price Formula
The Bancor formula with connector weight 1/3:
Buy: tokensOut = supply * ((1 + usdcAmount / reserve) ^ (CW/PPM) - 1)
Sell: usdcOut = reserve * (1 - (1 - tokenAmount / supply) ^ (PPM/CW))
Spot price: price = reserve * PPM / (supply * CW)
Market cap: marketCap = reserve * PPM / CWThe connector weight of 1/3 means the price increases sub-linearly with supply — early buyers get better prices, creating natural demand incentives.
Graduation
When a buyFromCurve transaction pushes marketCap >= graduationThreshold, the internal _graduateToV4 function:
- Marks the BondingCurveToken as graduated (disables mint/burn permanently).
- Computes
sqrtPriceX96from the graduation price and currency sort order. - Initializes a Uniswap V4 pool via
poolManager.initialize(). - Stores the V4 pool record in
companyPools[companyId]. - If
scoutFundis configured, callsinvestInCompany()(non-blocking with try/catch).
Events
event BondingCurveDeployed(bytes32 indexed companyId, address indexed token, uint256 graduationThreshold);
event TokenBought(bytes32 indexed companyId, address indexed buyer, uint256 usdcIn, uint256 tokensOut);
event TokenSold(bytes32 indexed companyId, address indexed seller, uint256 tokensIn, uint256 usdcOut);
event CompanyGraduated(bytes32 indexed companyId, address token, PoolId poolId, uint256 marketCap);
event PoolInitialized(bytes32 indexed companyId, PoolId indexed poolId, address token, address usdc, uint160 sqrtPriceX96);
event ScoutInvestFailed(bytes32 indexed companyId, address token, bytes reason);Roles
| Role | Permission |
|---|---|
LAUNCHER_ROLE | Call launchCompany() |
DEFAULT_ADMIN_ROLE | Config setters (setPoolFee, setGraduationThreshold, setCeosHook, setScoutFund) |
Security
ReentrancyGuardonlaunchCompany,buyFromCurve,sellToCurveSafeERC20for all USDC operations- Duplicate
companyIdprevention via mapping check onlyFactorymodifier on BondingCurveToken prevents unauthorized mintingonlyNotGraduatedmodifier prevents post-graduation mint/burn- Non-blocking ScoutFund integration (try/catch) prevents graduation failure