Before graduation, tokens trade exclusively through the Bonding Curve, with execution handled by IgnixManager rather than a DEX.
The curve uses the constant-product model x · y = k, but its fee calculation differs from a standard AMM.
There is no onchain quote function. minTokensOut and minQuoteOut must be calculated by the caller.
IgnixManager(manager).tokens(token)
Remaining amount available:
sellable - sold
Fees are deducted from the input amount before the remaining amount enters the curve:
feeBps = buyFeeBps + taxBuyBps + snipeBpsNow(token)
net = quoteIn − quoteIn × feeBps / 10000
out = vToken − ceil(vQuote × vToken / (vQuote + net))
snipeBpsNow(token) returns the current Anti-Snipe Tax and becomes 0 once the protection window ends.
// Native Quote Token
IgnixManager(manager).buy{value: amountIn}(
token, amountIn, minTokensOut
)
// ERC20 Quote Token; approve first
IgnixManager(manager).buy(
token, amountIn, minTokensOut
)
For native Quote Tokens, msg.value must equal amountIn.
The final buy may trigger a refund. If out exceeds the remaining available amount, only the remaining tokens are purchased. The unused input is refunded and no fee is charged on that portion. Use balance changes or emitted events to determine the actual amount spent.
Sell-side fees are deducted from the output amount:
gross = vQuote − ceil(vQuote × vToken / (vToken + tokenIn))
net = gross − gross × (sellFeeBps + taxSellBps) / 10000
IgnixManager(manager).sell(
token, tokenIn, minQuoteOut
)
minQuoteOut corresponds to net, not gross.
Buy fees are deducted from the input, while sell fees are deducted from the output. The two sides use different calculations.
Aggregators, trading bots and other contracts can route through the curve, but the generic "swap into your own contract, then forward to the user" pattern does not work here.
Before graduation a launch token can only move to or from IgnixManager. Any other transfer
reverts with CurveOnly() (0x9dabc49b). Graduation lifts the restriction permanently, after
which the token is an ordinary ERC20 in a standard Uniswap pool.
The restriction exists so nobody can buy on the curve, spin up their own pool and sell at an arbitrary price into a market disconnected from the curve.
The restriction applies to the launch token only. Quote Tokens (native OKB, wrapped xStocks,
…) are unrestricted and can be routed through your contract freely — so multi-hop such as
OKB → wNVDAx → launch token is possible. Only the hop that produces the launch token must
deliver straight to the user.
buyToCallable by any address — no allowlist.
function buyTo(
address token,
uint256 amountIn,
uint256 minTokensOut,
address recipient // the end user, not your own contract
) external payable;
The Manager pulls the Quote Token from msg.sender and sends the launch token and any
near-graduation refund straight to recipient. Your contract never holds the launch token, so
CurveOnly() cannot trigger.
| Quote Token | Prerequisite |
|---|---|
| ERC20 | quote.approve(manager, amountIn) first; msg.value must be 0 |
Native (quote == address(0)) | msg.value == amountIn; no approval needed |
Approve the Manager, not your own contract — it pulls via safeTransferFrom(msg.sender, …).
sellFromCallable by any address — no allowlist — but it requires an EIP-712 authorization from the seller, and the signature is bound to the calling contract.
function sellFrom(
address token,
address payer, // the seller
address recipient, // Quote Token recipient; may be your own contract
uint256 tokenIn,
uint256 minQuoteOut,
uint256 deadline,
bytes calldata authorization
) external;
The launch token moves straight from payer to the Manager and the Quote Token straight to
recipient. The seller must approve the Manager for the launch token, not your contract.
EIP-712 domain:
{ "name": "IgnixManager", "version": "1", "chainId": 196,
"verifyingContract": "<IgnixManager address>" }
SellFrom(address adapter,address token,address payer,address recipient,uint256 tokenIn,uint256 minQuoteOut,uint256 nonce,uint256 deadline)
adapter is the contract that calls sellFrom; nonce is the current sellNonces(payer) and
increments on every call, so each authorization is single-use. Signatures are checked with
SignatureChecker, so both EOAs and ERC-1271 smart accounts work.
function DOMAIN_SEPARATOR() external view returns (bytes32);
function SELL_FROM_TYPEHASH() external pure returns (bytes32);
function sellNonces(address payer) external view returns (uint256);
function cancelSellAuthorizations() external; // seller-invoked; voids all pending authorizations
Because the sell output is a Quote Token, routing it onward is unrestricted: set recipient to
your own contract, swap, then pay the user in whatever asset you like.
sellFromOriginA signature-free sell entry point also exists, but it is restricted to addresses the platform has
explicitly allowlisted; every other caller reverts with NotSellAdapter() (0x9d65dd00).
Third-party integrations should use sellFrom.
| Selector | Error | Meaning |
|---|---|---|
0x9dabc49b | CurveOnly | A launch token was sent to something other than the Manager — you are still forwarding through your own contract |
0x5cd5d233 | BadSignature | Authorization failed: check adapter, nonce, and that every field matches the call |
0x0819bdcd | SignatureExpired | Past deadline |
0x0bba69fb | BadValue | Zero/Manager recipient or payer; msg.value present for an ERC20 quote; msg.value != amountIn for a native quote |
0x2c353d89 | FounderOnly | The founder round is still open; public buys are closed |
0x7dd37f70 | Slippage | Output below minTokensOut / minQuoteOut |
0x52df9fe5 | SoldOut | The curve is exhausted |
0x9e87fac8 | Paused | Platform pause gate is active |
CurveOnly is declared on the token contract, not the Manager, so it will not decode against the
Manager ABI — match it by selector.
All divisions in the formulas above must be rounded up.
Rounding down may result in quoted output being slightly higher than the actual onchain output, causing minTokensOut or minQuoteOut to fail.
When sold == sellable, the token graduates within the same transaction and liquidity is deployed to the DEX.
After graduation:
IgnixManager.buy() → revert
IgnixManager.sell() → revert
Subsequent trading automatically moves to Uniswap. See Token Types for graduation detection.
Holder dividends can be queried and claimed through the token's DividendTracker:
address tracker = IgnixToken(token).tracker();
IDividendTracker(tracker).allAssets();
IDividendTracker(tracker).withdrawableOf(holder, asset);
IDividendTracker(tracker).claimAll();
Dividends stream linearly over 24 hours, so withdrawableOf changes continuously and should not be cached for long periods.