size.fun Open app

TypeScript

Setup

Install viem, define X Layer, point a client at the launchpad. Every other page in this section starts from this file.

Everything here uses viem and nothing else. There's no SDK to install — the launchpad is a handful of contracts and viem calls all of them.

bash
npm install viem

You'll want TypeScript 5.0+ and Node 20+ if you're running this outside a browser. The samples use top-level await, so run them as ESM: "type": "module" in your package.json, or a .mts file.

The shared module#

Every other page imports from this file: the chain, the clients, the addresses, the ABIs. Save it once and the rest is a few lines each time.

ts size.ts
import { createPublicClient, createWalletClient, defineChain, http, parseAbi } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import type { Address } from 'viem'

/** X Layer. Chain 196, native OKB, one-second blocks. */
export const xLayer = defineChain({
  id: 196,
  name: 'X Layer',
  nativeCurrency: { name: 'OKB', symbol: 'OKB', decimals: 18 },
  rpcUrls: { default: { http: ['https://xlayerrpc.okx.com'] } },
  blockExplorers: { default: { name: 'OKLink', url: 'https://www.oklink.com/xlayer' } },
})

export const publicClient = createPublicClient({ chain: xLayer, transport: http() })

/** A server-side signer. In a browser use custom(provider) instead — see below. */
export const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`)
export const walletClient = createWalletClient({ account, chain: xLayer, transport: http() })

export const size = {
  factory: '0xfdfCaaF9e744D2C43365a710628469bCAf365491',
  launchAndBuy: '0x24274B859C2B99358A7c9ed1da33EbAb832CadB4',
  feeEscrow: '0x401100747741364AD16a96D6Ba5724Bf817Cc25F',
} as const satisfies Record<string, Address>

/** Coins quote against native OKB, which is addressed as the zero address. */
export const OKB = '0x0000000000000000000000000000000000000000' as const

/**
 * The tokenised Hong Kong equities a curve launch may quote against instead.
 *
 * Each is the ERC-4626 *wrapper*, not the xStock itself: the raw token rebases
 * when a dividend or split moves its multiplier, and a curve reads its own
 * quote balance as the reserve backing every seller. Pass one of these as the
 * `pairToken` argument — see /pairs.
 *
 * The factory is the authority on what is approved. Read
 * `approvedPairTokens(address)` before you rely on any of them.
 */
export const STOCKS = {
  TENCENT: '0x41333Df9E7639188BBfca5522dC4844398Af9f9E',
  SHEIN: '0xff637d2d435D6745Df3faf61272B1216e7e8b727',
  HKEX: '0xDFc060b4dE44Fb88998b695aA59169643DED10ea',
  KUAISHOU: '0x096369B9b197bCbF669a3bC827Aa119d80d891E4',
  MEITUAN: '0xad1b65C8556957cf23d1B5e9accdc449b415fA97',
  MIXUE: '0x1Bac76a12d8EccA6dBF4B0A146Da8870d50F04c9',
} as const satisfies Record<string, Address>

/** The only launch config on the factory today: 1B supply, 1% fee, 30/75 OKB. */
export const LAUNCH_CONFIG_ID = 0n

export const factoryAbi = parseAbi([
  'struct Socials { string twitter; string telegram; string discord; string website; string farcaster; }',
  'struct TokenParams { string name; string symbol; string logo; string description; Socials socials; address creatorFeeRecipient; uint16 creatorTaxBps; bool buybackEnabled; bytes32 expectedEconomics; bytes32 salt; }',
  'struct LaunchedToken { address token; address curve; address deployer; address creatorFeeRecipient; address pairToken; uint256 graduationThreshold; uint24 poolFee; int24 tickSpacing; uint16 creatorTaxBps; bool buybackEnabled; uint8 phase; uint256 sweptQuote; uint256 sweptTokens; uint256 sweptAt; bool exists; }',
  'function launchFee() view returns (uint256)',
  'function launchEnabled() view returns (bool)',
  'function maxCreatorTaxBps() view returns (uint256)',
  'function previewLaunchEconomics(uint256 launchConfigId, address pairToken) view returns (bytes32)',
  'function approvedPairTokens(address pairToken) view returns (bool)',
  'function pairTokenEconomics(address pairToken) view returns (uint256 phantomQuote, uint256 graduationThreshold, uint8 decimals)',
  'function launchToken(TokenParams params, uint256 launchConfigId, address pairToken) payable returns (address token, address curve)',
  'function getLaunchedToken(address token) view returns (LaunchedToken)',
  'function createGraduatedPool(address token) returns (uint256 positionId)',
  'event TokenLaunched(address indexed token, address indexed curve, address indexed deployer, address pairToken, uint256 launchConfigId, uint256 graduationThreshold)',
  'event PoolGraduated(address indexed token, uint256 positionId, uint256 tokenAmount, uint256 pairTokenAmount)',
])

export const launchAndBuyAbi = parseAbi([
  'struct Socials { string twitter; string telegram; string discord; string website; string farcaster; }',
  'struct TokenParams { string name; string symbol; string logo; string description; Socials socials; address creatorFeeRecipient; uint16 creatorTaxBps; bool buybackEnabled; bytes32 expectedEconomics; bytes32 salt; }',
  'function launchAndBuy(TokenParams params, uint256 launchConfigId, address pairToken, uint256 quoteIn, uint256 minTokensOut, address recipient, address[] snipeTaxExemptions) payable returns (address token, address curve, uint256 tokensOut)',
])

export const curveAbi = parseAbi([
  'function buy(uint256 quoteIn, uint256 minTokensOut, address recipient) payable returns (uint256 tokensOut)',
  'function sell(uint256 tokensIn, uint256 minQuoteOut, address recipient) returns (uint256 quoteOut)',
  'function getReserves() view returns (uint256 quoteReserve, uint256 tokenReserve)',
  'function realQuoteReserve() view returns (uint256)',
  'function sellableTokens() view returns (uint256)',
  'function graduationThreshold() view returns (uint256)',
  'function currentSnipeTaxBps(address recipient) view returns (uint256)',
  'function graduated() view returns (bool)',
  'function readyToGraduate() view returns (bool)',
  'function token() view returns (address)',
  'function feeBps() view returns (uint256)',
  'function creatorTaxBps() view returns (uint256)',
  'function quoteFeeBalance() view returns (uint256)',
  'function sweepFees(uint256 minBuybackTokensOut)',
  'event CurveBuy(address indexed buyer, address indexed recipient, uint256 quoteIn, uint256 tokensOut, uint256 fee, uint256 tax)',
  'event CurveSell(address indexed seller, address indexed recipient, uint256 tokensIn, uint256 quoteOut, uint256 fee, uint256 tax)',
])

export const escrowAbi = parseAbi([
  'function claim() returns (uint256)',
  'function balanceOf(address recipient) view returns (uint256)',
])

// ---------------------------------------------------------------------------
// The seeded launcher. A different factory, a different token contract, and no
// curve at any point — the supply goes into a Uniswap v3 pool in the launch
// transaction. Trading one is a plain v3 swap, so the router and quoter below
// are Uniswap's own canonical deployments rather than anything of ours.
// ---------------------------------------------------------------------------

export const seeded = {
  factory: '0x3038604B92fb5ef8FfbdDDae5A5730CA4CAdAf62',
  locker: '0x1CA8bfe2E20E17cD14518501a203A001a21ecd3c',
  router: '0x4f0C28f5926AFDA16bf2506D5D9e57Ea190f9bcA',
  quoter: '0xD1b797D92d87B688193A2B976eFc8D577D204343',
  /** A v3 pool cannot hold native OKB, so an OKB-quoted launch is a WOKB pool.
   *  The router wraps and unwraps inside the swap, so you still spend OKB. */
  wokb: '0xe538905cf8410324e03A5A23C1c177a474D59b2b',
} as const satisfies Record<string, Address>

/** The tier every seeded pool opens on: 1%, tick spacing 200. */
export const POOL_FEE = 10_000

/** One launch config per quote asset, positional. 0 is OKB; read the rest with
 *  getLaunchConfig(id) rather than assuming an order that can change. */
export const SEEDED_CONFIG_ID = 0n

export const seededFactoryAbi = parseAbi([
  'struct Socials { string twitter; string telegram; string discord; string website; string farcaster; }',
  'struct TokenParams { string name; string symbol; string logo; string description; Socials socials; address feeWallet; }',
  'struct LaunchConfig { address pairToken; uint256 graduationThreshold; int24 initialTick; uint256 supply; uint16 maxWalletBps; uint16 maxTxBps; uint32 restrictionBlocks; uint24 reservedFee; bool enabled; bool routerRequiresDeadline; }',
  'struct LaunchedToken { address token; address deployer; address pairedToken; address positionManager; uint256 positionId; uint256 dexId; uint256 launchConfigId; uint256 restrictionsEndBlock; uint256 supply; bool isToken0; uint24 poolFee; bool exists; uint256 initialBuyAmount; }',
  'function launchFee() view returns (uint256)',
  'function launchEnabled() view returns (bool)',
  'function launchConfigCount() view returns (uint256)',
  'function getLaunchConfig(uint256 id) view returns (LaunchConfig)',
  'function getLaunchedToken(address token) view returns (LaunchedToken)',
  'function graduationStatus(address token) view returns (uint256 pairedPrincipal, uint256 threshold, bool graduated)',
  'function predictTokenAddress(TokenParams params, uint256 launchConfigId, uint256 dexId, bytes32 salt, address tokenDeployer) view returns (address)',
  'function launchToken(TokenParams params, uint256 launchConfigId, uint256 dexId, bytes32 salt) payable returns (address token)',
  'event TokenLaunched(address indexed token, address indexed deployer, address indexed pool, address pairToken, uint256 dexId, uint256 launchConfigId, uint256 positionId, uint256 supply, uint256 restrictionsEndBlock, uint256 initialBuyAmount)',
])

/** Permissionless. Splits the pool's accrued fees 30/70 and credits both sides
 *  to the same escrow a curve launch pays into. */
export const seededLockerAbi = parseAbi([
  'function collectFees(address token) returns (uint256 amount0, uint256 amount1)',
  'function feeRecipientOf(address token) view returns (address)',
  'function lockedPositions(address token) view returns (uint256)',
])

export const routerAbi = parseAbi([
  'struct ExactInputSingleParams { address tokenIn; address tokenOut; uint24 fee; address recipient; uint256 amountIn; uint256 amountOutMinimum; uint160 sqrtPriceLimitX96; }',
  'function exactInputSingle(ExactInputSingleParams params) payable returns (uint256 amountOut)',
  'function unwrapWETH9(uint256 amountMinimum, address recipient) payable',
  'function multicall(uint256 deadline, bytes[] data) payable returns (bytes[] results)',
])

export const quoterAbi = parseAbi([
  'struct QuoteExactInputSingleParams { address tokenIn; address tokenOut; uint256 amountIn; uint24 fee; uint160 sqrtPriceLimitX96; }',
  'function quoteExactInputSingle(QuoteExactInputSingleParams params) returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',
])

/** SwapRouter02's "pay this contract, not the caller" recipient sentinel. It is
 *  what lets a sell end in native OKB: the swap pays the router, then
 *  unwrapWETH9 in the same multicall forwards it. */
export const ADDRESS_THIS = '0x0000000000000000000000000000000000000002' as const

The ABIs are human-readable signatures rather than pasted JSON. viem parses them into the same thing and they stay legible, which matters here — these signatures are the reference for what you can call.

Only the parts you need

Trimmed to what these guides use. The contracts are verified on OKLink, so the full ABI of any of them is one click away.

Which RPC#

The public endpoint above works and is free. Two things to know:

  • It caps eth_getLogs at 100 blocks per call. Fine for watching, painful for walking history — page it, or use a provider without the cap.
  • It's rate limited. If you're polling reads for more than a few coins, get your own endpoint. This is all standard JSON-RPC, so any X Layer provider works.

In a browser#

Only the signer changes. Reads still go through a public client; writes go through the user's wallet instead of a private key.

ts browser.ts
import { createWalletClient, custom } from 'viem'
import { xLayer } from './size.js'

/**
 * In a browser the signer is the user's wallet, so the private key never
 * exists in your code. Everything else on these pages is unchanged: the same
 * ABIs, the same simulate-then-write pattern, the same public client for reads.
 */
export async function connect() {
  const provider = (window as any).ethereum
  if (!provider) throw new Error('No wallet found')

  const [address] = await provider.request({ method: 'eth_requestAccounts' })

  // Offer X Layer if the wallet has never been there. 0xc4 is 196.
  await provider.request({
    method: 'wallet_addEthereumChain',
    params: [{
      chainId: '0xc4',
      chainName: 'X Layer',
      nativeCurrency: { name: 'OKB', symbol: 'OKB', decimals: 18 },
      rpcUrls: ['https://xlayerrpc.okx.com'],
      blockExplorerUrls: ['https://www.oklink.com/xlayer'],
    }],
  }).catch(() => {})

  const walletClient = createWalletClient({
    account: address,
    chain: xLayer,
    transport: custom(provider),
  })

  return { address: address as `0x${string}`, walletClient }
}

Keys

The server-side client in size.ts reads a private key from the environment. Never ship that to a browser and never commit it. In a UI the wallet is the signer and your code never sees a key.

What you can call#

You want toCallOn
Launch a coinlaunchTokenLaunchFactory
Launch and take the first positionlaunchAndBuyLaunchAndBuy
Buy on the curvebuythe coin's curve
Sell on the curvesellthe coin's curve
Read price and progressgetReservesthe coin's curve
Find a coin's curvegetLaunchedTokenLaunchFactory
Settle a stalled graduationcreateGraduatedPoolLaunchFactory
Move fees to the escrowsweepFeesthe coin's curve
Collect your feesclaimFeeEscrow

Curves are per coin. Given a token address, getLaunchedToken on the factory returns its curve and the terms it launched under.