Integrate
Everything a trading bot, aggregator or data site needs to support launchtokens tokens, without asking us. Every launch goes through one contract per chain, the CurveFactory:
- Before graduation, a token trades only on its bonding curve, through
buyandsellon the CurveFactory. - After graduation, it trades only on its Uniswap v3 pool (1% fee tier, paired with WETH).
Every function and event signature on this page is generated from the contracts' ABIs at build time.
Contracts
Robinhood Chain (chain ID 4663)
| Contract | Address | Explorer |
|---|---|---|
| CurveFactory Launches, curve buys and sells, quotes, events | 0x51A8A9e0889dD7C2Dc5118828f2741F7F74FF843 | Blockscout Etherscan |
| LiquidityLocker Holds every graduated pool position forever | 0xa1E5aac0079e625C36164e0DECc2eC627D6f1BFE | Blockscout Etherscan |
| UniswapV3Graduator Creates the pool and adds the liquidity at graduation | 0xb1372b23E97c09125D8FD2e8C121deb9cb6BE9b7 | Blockscout Etherscan |
| UniswapV3Adapter Works out each token's pool address | 0x90A35D7104707B455f10FCBA021EF36de7cd035c | Blockscout Etherscan |
| WETH The pair token of every graduated pool | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 | Blockscout Etherscan |
Index from block 87628359 (the CurveFactory's deploy block). Public RPC: https://rpc.mainnet.chain.robinhood.com.
Robinhood Chain Testnet (chain ID 46630)
| Contract | Address | Explorer |
|---|---|---|
| CurveFactory Launches, curve buys and sells, quotes, events | 0x130C34f186BDB6719EfB1DDa7Bdf6b25F6CBE708 | Blockscout |
| LiquidityLocker Holds every graduated pool position forever | 0x3a37C41c610a5A26845aD635d84C52Ac5147587d | Blockscout |
| UniswapV3Graduator Creates the pool and adds the liquidity at graduation | 0xF1Db864195563d4e72515FB46FA78e90b707cde1 | Blockscout |
| UniswapV3Adapter Works out each token's pool address | 0x76Cc43B6F555eD86b5D30Bc4FA8b4f50D4E3AB0d | Blockscout |
| WETH The pair token of every graduated pool | 0x7943e237c7F95DA44E0301572D358911207852Fa | Blockscout |
Index from block 132559642. Public RPC: https://rpc.testnet.chain.robinhood.com. On testnet a curve graduates at 0.05 ETH instead of 5.
ABI and machine-readable addresses
- CurveFactory.json: CurveFactory ABI
- CurveToken.json: the token ABI (ERC-20 plus the launch views)
- contracts.json: addresses, start blocks and pool settings for every chain
All three can be fetched from any origin.
Is this a launchtokens token?
Call curve(token) on the CurveFactory. It reverts with UnknownToken(token) for anything we didn't launch. Our tokens also return the CurveFactory from factory(), but any contract can claim that; only curve(token) proves it.
Read a curve
function curve(address token) external view returns (CurveView v);
struct CurveView {
address creator;
uint256 raised;
uint256 target;
uint256 sold;
uint256 curveTokens;
bool graduated;
address pool;
uint256 positionId;
uint256 launchedAt;
uint256 price;
uint256 marketCap;
}| Field | Meaning |
|---|---|
creator | The wallet that launched the token |
raised | ETH in the curve, in wei |
target | ETH the curve must raise to graduate (5 ETH on Robinhood Chain) |
sold | Tokens sold from the curve, in token units (18 decimals) |
curveTokens | Tokens for sale on the curve: 800,000,000 × 10¹⁸ |
graduated | true once the token has moved to its Uniswap pool |
pool | The token's Uniswap v3 pool. Known from launch; it opens at graduation |
positionId | The locked Uniswap position; 0 until graduation |
launchedAt | Launch time, unix seconds |
price | Spot price, wei per whole token |
marketCap | Fully diluted market cap, wei (price × 1,000,000,000) |
Progress to graduation is raised / target.
Quotes
function getBuyQuote(address token, uint256 ethIn) external view returns (uint256 tokensOut, uint256 fee, uint256 ethUsed, uint256 refund, bool graduates);
function getSellQuote(address token, uint256 tokensIn) external view returns (uint256 ethOut, uint256 fee);getBuyQuote:ethInis everything you send, fee included.feeis the 1% fee. If the buy would graduate the token,graduatesistrue,tokensOutis the rest of the curve,ethUsedis what that costs, andrefundcomes back to the buyer in the same transaction.getSellQuote:ethOutis what the seller receives, after the 1% fee.- Both revert with
AlreadyGraduated(token)after graduation.getBuyQuoteignores the pause and the launch protection cap.
The fee is 1% of the trade, rounded up to the wei.
Buy
function buy(address token, uint256 minTokensOut, uint256 deadline) external payable returns (uint256 tokensOut);- Pay in native ETH (
msg.value). All of it is spent, except in the buy that graduates the token: that buy fills up to the curve's last token and refunds the excess in the same transaction. minTokensOutis your slippage guard;deadlineis a unix timestamp in seconds.- The buy that graduates the token also creates the pool and locks the liquidity. It needs up to
GRADUATION_EXTRA_GAS()(5,500,000) more gas than a normal buy. Estimate gas per transaction; don't hard-code a limit. buyreverts withLaunchesPaused()while the factory is paused.
Sell
function sell(address token, uint256 amountIn, uint256 minEthOut, uint256 deadline) external returns (uint256 ethOut);- First approve the CurveFactory to spend
amountInof the token (approve(factory, amountIn)on the token). minEthOutis your slippage guard (after the fee);deadlineis a unix timestamp in seconds.- Selling is never paused. The pause has no effect on
sell. - After graduation
sellreverts withAlreadyGraduated(token): sell on the pool instead.
Launch protection
For the first 60 seconds after launch no wallet may hold more than 2% of supply (20,000,000 tokens). A buy or transfer that would leave the receiver above the cap reverts inside the token with:
error HoldingCapExceeded(address account, uint256 balance);This error comes from the CurveToken, so decode reverts with both ABIs. To stay under it, read these on the token (or from the CurveLaunched event):
function protectionEndsAt() external view returns (uint256);
function maxWalletDuringProtection() external view returns (uint256);
function protectionActive() external view returns (bool);Sales are never capped. In the launch transaction itself, only the creator's own buy can take tokens off the curve.
Graduation and switching to Uniswap
When raised reaches target, the buy that reaches it graduates the token in the same transaction. The raised ETH and the 200M reserved tokens go into the token's Uniswap v3 pool, and the position is locked forever in the LiquidityLocker.
Detect it with any of:
- the
Graduatedevent, curve(token).graduated,graduated()on the token.
Then route trades to the Uniswap v3 pool at curve(token).pool (the same address as pool() on the token): fee tier 10000 (1%), paired with WETH (0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73). Any Uniswap v3 router or quoter works; there is nothing launchtokens-specific after graduation.
Before graduation, the pool address is fixed but closed: sending the token to it reverts with PoolClosedUntilGraduation(). Don't route pre-graduation trades to Uniswap.
Events
All on the CurveFactory.
event CurveLaunched(address indexed token, address indexed creator, address indexed pool, LaunchInfo info);
struct LaunchInfo {
uint256 supply;
uint256 curveTokens;
uint256 poolTokens;
uint256 targetEth;
uint256 protectionEndsAt;
uint256 maxWalletDuringProtection;
uint256 devBuyEth;
uint256 devBuyTokens;
uint256 launchIndex;
}
event CurveMetadata(address indexed token, string name, string symbol, string metadataURI, bytes32 metadataHash, bytes32 salt);
event Trade(address indexed token, address indexed trader, bool indexed isBuy, uint256 ethAmount, uint256 tokenAmount, uint256 fee, uint256 raised, uint256 sold);
event Graduated(address indexed token, address indexed pool, uint256 indexed positionId, uint256 ethToPool, uint256 tokensToPool, uint128 liquidity, uint160 sqrtPriceX96, bool corrected, uint256 leftoverEth, uint256 leftoverTokens);| Event | topic0 |
|---|---|
CurveLaunched | 0xdf1f4344a333daca2878035534a5faeb2e2a26c2257e590aab8f4e8bd984afd1 |
CurveMetadata | 0x4ae8c32686d7d3a4ab7221910bc092d614741c0cd5192ce94c6bce78c51f23c2 |
Trade | 0x2c76e7a47fd53e2854856ac3f0a5f3ee40d15cfaa82266357ea9779c486ab9c3 |
Graduated | 0x94349f59d0fb50785232955ac77cd1f37fd5b243a8be8c2171c1cbde8a52d587 |
CurveLaunched is the new-token signal: the token, its creator, its future pool and the launch numbers. devBuyEth and devBuyTokens are the creator's launch buy (its Trade event comes first in the same transaction). launchIndex counts launches on this factory from 1.
CurveMetadata comes in the same transaction. For launches made on launchtokens.io, metadataURI is an https URL of a JSON document { "description", "image", "links": { "website", "x", "telegram" } } (any link may be missing), and metadataHash is the keccak256 of its exact bytes, so you can check you got the right file. Anyone can call the contract directly, so treat the URI and its contents as untrusted input.
Trade is every curve buy and sell.
isBuyistruefor buys.- Buy:
ethAmountis what the buyer paid, fee included, refund excluded. Sell:ethAmountis what the seller received, after the fee. feeis the 1% fee in wei.raisedandsoldare the curve's state after the trade.- The spot price after the trade, in wei per whole token, follows from
raisedalone (T= target in wei):price = 5e18 × (T + 3 × raised)² / (T × 16e27), rounded down. It equalscurve(token).price.
Graduated marks the move to Uniswap: the pool, the locked position ID and what went into the pool (ethToPool, tokensToPool).
correctedistrueif someone had already created the pool at another price; the graduator moved the price to the curve's last price before adding the liquidity.leftoverEthandleftoverTokensare what the position didn't take: rounding dust, with the ETH sent to the treasury and the tokens burnt (sent to0x…dEaD).- In the rare case that someone's WETH bids already sat above the curve's price, the ETH goes in alone and the 200M tokens are burnt:
tokensToPoolis0andleftoverTokensis the 200M.
Errors
The ones a trader can hit:
error LaunchesPaused();
error Expired();
error UnknownToken(address token);
error AlreadyGraduated(address token);
error ZeroAmount();
error NothingBought();
error SlippageExceeded(uint256 amountOut, uint256 minAmountOut);
error ExceedsSold(uint256 amountIn, uint256 sold);
error HoldingCapExceeded(address account, uint256 balance);
error PoolClosedUntilGraduation();Example: quote and buy with viem
import { createPublicClient, createWalletClient, defineChain, http, parseAbi, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
const robinhood = defineChain({
id: 4663,
name: 'Robinhood Chain',
nativeCurrency: { name: 'Ether', symbol: 'ETH', decimals: 18 },
rpcUrls: { default: { http: ['https://rpc.mainnet.chain.robinhood.com'] } },
});
const FACTORY = '0x51A8A9e0889dD7C2Dc5118828f2741F7F74FF843';
const abi = parseAbi([
'function curve(address token) view returns ((address creator, uint256 raised, uint256 target, uint256 sold, uint256 curveTokens, bool graduated, address pool, uint256 positionId, uint256 launchedAt, uint256 price, uint256 marketCap) v)',
'function getBuyQuote(address token, uint256 ethIn) view returns (uint256 tokensOut, uint256 fee, uint256 ethUsed, uint256 refund, bool graduates)',
'function buy(address token, uint256 minTokensOut, uint256 deadline) payable returns (uint256 tokensOut)',
'event Trade(address indexed token, address indexed trader, bool indexed isBuy, uint256 ethAmount, uint256 tokenAmount, uint256 fee, uint256 raised, uint256 sold)',
]);
const client = createPublicClient({ chain: robinhood, transport: http() });
const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
const wallet = createWalletClient({ account, chain: robinhood, transport: http() });
const token = '0x…'; // the token to buy
// still on the curve?
const c = await client.readContract({ address: FACTORY, abi, functionName: 'curve', args: [token] });
if (c.graduated) throw new Error(`graduated: trade on the Uniswap v3 pool ${c.pool}`);
// quote 0.01 ETH and accept up to 2% slippage
const ethIn = parseEther('0.01');
const [tokensOut, , , refund, graduates] = await client.readContract({
address: FACTORY, abi, functionName: 'getBuyQuote', args: [token, ethIn],
});
const minTokensOut = (tokensOut * 98n) / 100n;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 60);
// simulate first: it surfaces HoldingCapExceeded, LaunchesPaused, SlippageExceeded… before you pay gas
const { request } = await client.simulateContract({
account, address: FACTORY, abi, functionName: 'buy', args: [token, minTokensOut, deadline], value: ethIn,
});
const hash = await wallet.writeContract(request);
await client.waitForTransactionReceipt({ hash });Example: read trades with viem
// past trades of one token (public RPCs limit block ranges: page through in chunks, e.g. 2,000 blocks)
const logs = await client.getContractEvents({
address: FACTORY, abi, eventName: 'Trade', args: { token },
fromBlock: 87628359n, toBlock: 87628359n + 2_000n,
});
for (const { args } of logs) {
console.log(args.isBuy ? 'buy' : 'sell', args.trader, args.ethAmount, args.tokenAmount, args.raised);
}
// live: every trade on every curve
const stop = client.watchContractEvent({
address: FACTORY, abi, eventName: 'Trade',
onLogs: (logs) => logs.forEach(({ args }) => console.log(args.token, args.isBuy, args.ethAmount)),
});Other useful reads
function paused() external view returns (bool);
function launchCount() external view returns (uint256);
function target() external view returns (uint256);
function launchFee() external view returns (uint256);
function FEE_BPS() external view returns (uint256);
function GRADUATION_EXTRA_GAS() external view returns (uint256);
function creator() external view returns (address);
function pool() external view returns (address);
function graduated() external view returns (bool);
function launchedAt() external view returns (uint256);Every token has 18 decimals and a fixed supply of 1,000,000,000. It has no owner, no mint, no pause, no blacklist and no transfer tax.
Older contracts on testnet
Some testnet tokens were launched on an earlier curve set (listed on Contracts & security) and still trade there. Same ABI and events; index both factories if you want every testnet token.
