Documentation
Quickstart
Launch a token with an opening buy, in one transaction, with viem. Every address below is Arc
mainnet (chain 5042); see Addresses.
The snippets are written against a QUOTE — any approved pair token — rather than USDC in
particular. Pick one from GET /v1/protocol → approvedPairTokens, and take its decimals from
the same record; see Decimals for the five and their scales.
const QUOTE = USDC; // or WETH, cirBTC, EURC, XAUM
const QUOTE_DECIMALS = 6; // 18 for WETH/XAUM, 8 for cirBTC1 · Check you are allowed to launch#
Launching is gated and disabled by default on a fresh deployment — on Arc mainnet it stays closed until the owner opens it. Always check first:
const allowed = await client.readContract({
address: FACTORY, abi: factoryAbi, functionName: "canLaunch", args: [account],
});
if (!allowed) throw new Error("launching is not open for this address");2 · Pin the economics#
expectedEconomics freezes the terms your user was shown. If the owner re-pegs the curve or
changes the launch fee between your quote and your signature, the launch reverts instead of
landing on terms nobody agreed to.
const expectedEconomics = await client.readContract({
address: FACTORY, abi: factoryAbi,
functionName: "previewLaunchEconomics", args: [0n, QUOTE],
});Fetch it in the same flow as the submit. Do not cache it. Passing bytes32(0) waives the
check, which means accepting whatever terms are live when the transaction lands.
3 · Approve the router#
The atomic path pulls the fee and the opening buy, and the spender is the router, not the
factory. They are two different assets unless the launch quotes in USDC: the fee is always
launchFee() of launchFeeToken() (5 USDC), the opening buy is quoteIn of QUOTE.
const fee = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "launchFee" });
const feeToken = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "launchFeeToken" });
const openingBuy = parseUnits("1", QUOTE_DECIMALS); // 1 unit of the quote, in ITS decimals
if (feeToken.toLowerCase() === QUOTE.toLowerCase()) {
// Same token: one approval for the sum covers both pulls.
await walletClient.writeContract({
address: QUOTE, abi: erc20Abi, functionName: "approve",
args: [LAUNCH_AND_BUY, fee + openingBuy],
});
} else {
// Different tokens: the fee in USDC, the buy in the quote — two approvals, same spender.
await walletClient.writeContract({
address: feeToken, abi: erc20Abi, functionName: "approve", args: [LAUNCH_AND_BUY, fee],
});
await walletClient.writeContract({
address: QUOTE, abi: erc20Abi, functionName: "approve", args: [LAUNCH_AND_BUY, openingBuy],
});
}4 · Launch#
const { result } = await client.simulateContract({
address: LAUNCH_AND_BUY, abi: launchAndBuyAbi, functionName: "launchAndBuy",
args: [
{
name: "My Token", symbol: "MINE", logo: "", description: "",
socials: { twitter: "", telegram: "", discord: "", website: "", farcaster: "" },
creatorFeeRecipient: account, // must NOT be zero on this path
creatorTaxBps: 0,
expectedEconomics,
salt: crypto.getRandomValues(new Uint8Array(32)),
},
0n, // launchConfigId
QUOTE, // pairToken
openingBuy, // quoteIn — must be non-zero here, in the quote's decimals
0n, // minTokensOut
account, // recipient of the bought tokens
],
account,
});
const [token, curve, tokensOut] = result;Then send it with an explicit gas limit — see the warning below.
5 · Launch without an opening buy#
launchAndBuy reverts on a zero quoteIn — it exists to buy and refuses to act as a plain
deployer. Use the factory directly, and note the approval target moves with it:
await walletClient.writeContract({
address: feeToken, abi: erc20Abi, functionName: "approve", // the fee token (USDC), whatever the quote
args: [FACTORY, fee], // the FACTORY, not the router
});
const { result } = await client.simulateContract({
address: FACTORY, abi: factoryAbi, functionName: "launchToken",
args: [params, 0n, QUOTE], account,
});A token launched this way starts with the creator holding none of its supply.
6 · Buy on the curve#
The curve has no fixed address — read it from the launch record.
const launch = await client.readContract({
address: FACTORY, abi: factoryAbi, functionName: "getLaunchedToken", args: [token],
});
await walletClient.writeContract({
address: launch.pairToken, abi: erc20Abi, functionName: "approve", // the launch's OWN quote
args: [launch.curve, amountIn], // approve the CURVE, amount in the quote's decimals
});
await walletClient.writeContract({
address: launch.curve, abi: curveAbi, functionName: "buy",
args: [amountIn, minTokensOut, account],
});