Launch a token

Call launchDrop with viem, with two pools and a dev buy.

This guide launches an ERC-20 on Base with two pools:

  • The main pool has 80% of the supply against native ETH, at a start market cap of 10 ETH.
  • The second pool has 20% against USDC, at a start market cap of about $33,000.

It also buys tokens from the main pool with 0.1 ETH, in the same transaction.

Set up the client

Copy abi.ts and addresses.ts into your project. Then create the clients:

client.ts
import { createPublicClient, createWalletClient, http } from "viem";
import { privateKeyToAccount } from "viem/accounts";
import { base } from "viem/chains";

export const account = privateKeyToAccount(
  process.env.PRIVATE_KEY as `0x${string}`,
);
export const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});
export const walletClient = createWalletClient({
  account,
  chain: base,
  transport: http(),
});

Calculate the start ticks

initialTick is the start price, as a tick of raw quote per raw token. This function converts a start market cap to a tick. See Set the start price.

start-tick.ts
const TICK_SPACING = 200;
const TOTAL_SUPPLY = 1_000_000_000;

/**
 * Start tick for a starting market cap, in whole units of the quote.
 */
export function startTick({
  marketCap,
  quoteDecimals,
}: {
  marketCap: number; // In whole quote units
  quoteDecimals: number;
}) {
  // Whole quote per whole token, then raw quote per raw token
  const price = marketCap / TOTAL_SUPPLY;
  const rawPrice = price * 10 ** (quoteDecimals - 18);
  const tick = Math.log(rawPrice) / Math.log(1.0001);

  return Math.round(tick / TICK_SPACING) * TICK_SPACING;
}

Build the launch parameters

launch.ts
import { keccak256, parseEther, toHex, zeroAddress } from "viem";

import { launchpadAbi } from "./abi";
import { ERC20_DEPLOYER, LAUNCHPAD, USDC } from "./addresses";
import { account, publicClient, walletClient } from "./client";
import { startTick } from "./start-tick";

// The partner of the launch. See "Register a partner".
const PARTNER_ID = Number(process.env.PARTNER_ID);

// 10 ETH and $33,000 start market caps. Give all pools the same start price.
const ethTick = startTick({ marketCap: 10, quoteDecimals: 18 }); // -184200
const usdcTick = startTick({ marketCap: 33_000, quoteDecimals: 6 }); // -379600

const params = {
  name: "Example",
  symbol: "EXMPL",
  contractURI: "ipfs://bafkrei...",
  salt: keccak256(toHex("example-1")),
  partnerId: PARTNER_ID,
  tokenDeployer: ERC20_DEPLOYER,
  creatorFeeRecipient: account.address,
  pools: [
    // Main pool: 80% against native ETH
    { quote: zeroAddress, supplyBps: 80_00, initialTick: ethTick },
    // 20% against USDC
    { quote: USDC, supplyBps: 20_00, initialTick: usdcTick },
  ],
  // Buys from the main pool with 0.1 ETH.
  devBuy: { amountIn: parseEther("0.1"), recipient: account.address },
} as const;
  • salt is any 32 bytes. The launchpad combines it with your address, so nobody else can take your token address.
  • partnerId sets the LP fee and the partner's share of fees. Check the terms with partners(partnerId) first. See Partners and Register a partner.
  • The pools must follow the pool rules. Put the main pool first.
  • Use a creatorFeeRecipient of 0x…dEaD for burn mode.

Send the launch

The main pool's quote is native ETH, so send the dev buy amount as value.

launch.ts
const { result: token, request } = await publicClient.simulateContract({
  account,
  address: LAUNCHPAD,
  abi: launchpadAbi,
  functionName: "launchDrop",
  args: [params],
  value: params.devBuy.amountIn,
});

const hash = await walletClient.writeContract(request);
await publicClient.waitForTransactionReceipt({ hash });

console.log(`Launched ${token}`);

The simulation returns the token address and shows a revert before you send. See Errors for the revert reasons.

Use an ERC-20 for the dev buy

If the main pool's quote is an ERC-20, approve the launchpad for the dev buy amount before the launch. Then send no value.

import { erc20Abi } from "viem";

const amountIn = 1_000n * 10n ** 6n; // 1,000 USDC

await walletClient.writeContract({
  address: USDC,
  abi: erc20Abi,
  functionName: "approve",
  args: [LAUNCHPAD, amountIn],
});

Launch a B-20 on Base

Set tokenDeployer to B20_DEPLOYER. Everything else is the same. A B-20 address does not depend on the name, so use a new salt for each B-20 launch from the same account.

What the launch emits

The launchpad emits these events, in order:

  1. DropCreated with the creator, token, partner, token deployer, and creator fee recipient.
  2. DropPoolCreated for each pool, with its pool ID, share, and start price.
  3. DropDevBuy, if the launch has a dev buy.

DropHook also emits DropSwap for the dev buy. See Events.

On this page