Create
Documentation
Get started

Quickstart

Launch a token with an opening buy, in one transaction, with viem. Every address below is Arc testnet; see Addresses.

1 · Check you are allowed to launch#

Launching is gated and disabled by default on a fresh deployment. Always check first:

ts
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.

ts
const expectedEconomics = await client.readContract({
  address: FACTORY, abi: factoryAbi,
  functionName: "previewLaunchEconomics", args: [0n, USDC],
});

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, so one approval covers both — and the spender is the router, not the factory.

ts
const fee = await client.readContract({ address: FACTORY, abi: factoryAbi, functionName: "launchFee" });
const openingBuy = 1_000_000n;                       // 1 USDC — 6 decimals

await walletClient.writeContract({
  address: USDC, abi: erc20Abi, functionName: "approve",
  args: [LAUNCH_AND_BUY, fee + openingBuy],
});

4 · Launch#

ts
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
    USDC,          // pairToken
    openingBuy,    // quoteIn — must be non-zero here
    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.

Warning
If the opening buy is large enough to cross the graduation threshold, the curve tries to seed the Uniswap pool inside this same transaction. eth_estimateGas cannot size that: the seed is a best-effort try/catch, so a simulation in which it fails still succeeds overall and returns a limit too small for it to work — every time, deterministically. Send something generous (8,000,000 is ample). EIP-1559 charges for gas used, not the limit, so over-providing costs nothing.

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:

ts
await walletClient.writeContract({
  address: USDC, abi: erc20Abi, functionName: "approve",
  args: [FACTORY, fee],                              // the FACTORY, not the router
});

const { result } = await client.simulateContract({
  address: FACTORY, abi: factoryAbi, functionName: "launchToken",
  args: [params, 0n, USDC], 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.

ts
const launch = await client.readContract({
  address: FACTORY, abi: factoryAbi, functionName: "getLaunchedToken", args: [token],
});

await walletClient.writeContract({
  address: USDC, abi: erc20Abi, functionName: "approve",
  args: [launch.curve, amountIn],                    // approve the CURVE
});

await walletClient.writeContract({
  address: launch.curve, abi: curveAbi, functionName: "buy",
  args: [amountIn, minTokensOut, account],
});