# Quickstart

Launch a token with an opening buy, in one transaction, with viem. Every address below is Arc
mainnet (chain `5042`); see [Addresses](/documentation/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](/documentation/decimals) for the five and their scales.

```ts
const QUOTE = USDC;                                  // or WETH, cirBTC, EURC, XAUM
const QUOTE_DECIMALS = 6;                            // 18 for WETH/XAUM, 8 for cirBTC
```

## 1 · 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:

```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, 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`.

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

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

> [!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: 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.

```ts
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],
});
```