TypeScript
Launch a token
One call creates the token and its curve. A second form does that and takes the first position in the same transaction, which is the one you want if you are buying your own launch.
A launch is one call to the factory with the coin's metadata, the launch config to use, and the launch fee attached. It deploys the token, deploys its curve, mints the whole supply onto it and opens trading — all in that transaction.
The parameters#
Everything the launch needs, in one object. Most of it is metadata; three fields are decisions.
import { toHex } from 'viem'
import { account } from './size.js'
/** CREATE2 salt. It only has to be unused by this account — any random 32 bytes. */
const salt = toHex(crypto.getRandomValues(new Uint8Array(32)))
export const params = {
name: 'Example Coin',
symbol: 'EXAMPLE',
// A URL, not the image itself. Host the picture and put its link here —
// uploading through size.fun gives you one, pinned to IPFS.
logo: 'https://size.fun/api/img/0000000000000000000000000000000000000000000000000000000000000000',
description: 'A coin that exists.',
socials: {
twitter: 'https://x.com/example',
telegram: '',
discord: '',
website: '',
farcaster: '',
},
// Where this launch's fees are paid. Changeable later, by this address only.
creatorFeeRecipient: account.address,
// 0 to 1000, charged on every trade on top of the 1% base fee and paid to you
// in full. Frozen at launch: there is no setter for it afterwards.
creatorTaxBps: 0,
// Puts half of your share of the base fee into buying the coin back, on a
// five-year vest. Can be turned on or off later.
buybackEnabled: false,
// 32 zero bytes waives the check on the launch terms. See "Pinning the terms".
expectedEconomics: `0x${'00'.repeat(32)}` as `0x${string}`,
salt,
} as const
| Field | What it does |
|---|---|
name, symbol |
Standard ERC-20 metadata. Permanent. |
logo |
A URL. The contract stores the link, not the image, so host it somewhere that will outlive the launch. |
description, socials |
Shown on the coin's page. All five social fields exist; pass empty strings for the ones you are not using. |
creatorFeeRecipient |
Where this launch's fees are paid. Can be transferred later, by that address. |
creatorTaxBps |
Permanent. 0–1000 (0–10%), charged on every trade on top of the base fee and paid to you in full. |
buybackEnabled |
Puts half your share of the base fee into buying the coin back on a five-year vest. Changeable later. |
expectedEconomics |
Pins the launch terms. 32 zero bytes waives it — see below. |
salt |
Feeds CREATE2, so it must be one this account has not used. Any random 32 bytes. Mining it is how you get a vanity address. |
Launching#
import { parseEventLogs } from 'viem'
import {
publicClient, walletClient, account,
size, factoryAbi, OKB, LAUNCH_CONFIG_ID,
} from './size.js'
import { params } from './launch-params.js'
const launchFee = await publicClient.readContract({
address: size.factory,
abi: factoryAbi,
functionName: 'launchFee',
})
// Simulating first does two things: it reverts here rather than on chain if
// anything about the parameters is wrong, and it hands back the token and curve
// addresses before the transaction has been sent.
const { request, result } = await publicClient.simulateContract({
address: size.factory,
abi: factoryAbi,
functionName: 'launchToken',
args: [params, LAUNCH_CONFIG_ID, OKB],
value: launchFee,
account,
})
const [token, curve] = result
console.log({ token, curve })
const hash = await walletClient.writeContract(request)
const receipt = await publicClient.waitForTransactionReceipt({ hash })
// The same two addresses, read back off the receipt.
const [launched] = parseEventLogs({
abi: factoryAbi,
eventName: 'TokenLaunched',
logs: receipt.logs,
})
console.log(launched.args)
Simulating before writing is worth the extra round trip on this call in particular: the factory validates the whole parameter set, and a simulation tells you which constraint you missed with the actual error, rather than costing you a reverted transaction to find out.
It also hands back the token and curve addresses before anything is sent, because both are CREATE2-derived and therefore knowable in advance.
Launching and buying in one transaction#
The factory cannot fold a dev buy into launchToken, so doing it in two
transactions leaves a gap between the coin opening and your buy landing. That gap is
measured in blocks for a bot and in seconds for a human holding a wallet prompt, and it
is long enough to lose the whole allocation — a launch on this factory was bought out by
twenty-two addresses in the two blocks after it opened.
LaunchAndBuy closes it outright. Both legs settle in one transaction, so
there is no intermediate state to trade against, and a failed buy takes the launch down
with it rather than leaving you holding a coin you did not want on its own.
import { parseEther } from 'viem'
import {
publicClient, walletClient, account,
size, factoryAbi, launchAndBuyAbi, OKB, LAUNCH_CONFIG_ID,
} from './size.js'
import { params } from './launch-params.js'
/** What you want to spend on your own coin, in OKB. */
const quoteIn = parseEther('0.5')
const launchFee = await publicClient.readContract({
address: size.factory,
abi: factoryAbi,
functionName: 'launchFee',
})
const { request, result } = await publicClient.simulateContract({
address: size.launchAndBuy,
abi: launchAndBuyAbi,
functionName: 'launchAndBuy',
args: [
params,
LAUNCH_CONFIG_ID,
OKB,
quoteIn,
// No slippage bound is needed. The curve does not exist until this call
// creates it, so there is no price for anyone else to move first.
0n,
// Who receives the tokens. Exempted from the snipe tax automatically.
account.address,
// Extra wallets to exempt, if a team is bundling its opening buys. Up to 31.
[],
],
// For a native launch the call carries both legs: the fee and the buy.
value: launchFee + quoteIn,
account,
})
const [token, curve, tokensOut] = result
console.log({ token, curve, tokensOut })
const hash = await walletClient.writeContract(request)
await publicClient.waitForTransactionReceipt({ hash })
Use this one
If you intend to hold any of your own coin, launch through launchAndBuy.
The plain launchToken path is for launches with no dev buy at all.
Bundling several wallets#
Buys in the first 15 seconds pay the snipe
tax. The recipient of the dev buy is exempted automatically; a team opening across
several addresses passes them in snipeTaxExemptions, up to 31 more. They
are declared inside the launch transaction, so they are fixed before anyone else can see
the coin exists.
args: [
params,
LAUNCH_CONFIG_ID,
OKB,
quoteIn,
0n,
account.address,
['0xabc…', '0xdef…'], // exempt as well; recipient is added for you
],
Pinning the terms#
The launch terms — supply, base fee, phantom reserve, threshold, the graduated pool's fee tier and the fee split — are protocol settings. They move rarely, but if one moved between your reading it and your launch mining, your coin would quietly get terms you did not agree to.
expectedEconomics makes that revert instead. Read the digest, pass it,
and a launch under changed terms fails rather than repricing.
import { publicClient, size, factoryAbi, OKB, LAUNCH_CONFIG_ID } from './size.js'
/**
* The launch terms — supply, fee, phantom reserve, threshold, the pool's fee
* tier, the fee split — are protocol settings. They change rarely, but a change
* landing between the moment you read them and the moment your launch mines
* would silently give you different terms than the ones you agreed to.
*
* Passing this digest as `expectedEconomics` makes that case revert instead.
* Passing 32 zero bytes waives the check, which is what the site does.
*/
export const expectedEconomics = await publicClient.readContract({
address: size.factory,
abi: factoryAbi,
functionName: 'previewLaunchEconomics',
args: [LAUNCH_CONFIG_ID, OKB],
})
Passing 32 zero bytes waives the check, which is what the site does. For a scripted launch where nobody is watching the prompt, pinning is the better default.
After it lands#
Trading is already open — there is no second step to enable it. From here:
- The coin's page is
https://size.fun/#/token/<token>. - Fees start accruing on the curve from the first trade. Collecting them is two calls.
getLaunchedToken(token)on the factory returns the curve address and the terms the launch is running under, at any time.
Errors worth recognising
CreatorTaxTooHigh — above the 10% cap.
LaunchFeeNotPaid — the value sent does not match
launchFee().
LaunchEconomicsMismatch — the terms moved under a pinned launch.
A repeated salt reverts too, because the pair already exists at that
address.