Launchpad factory Issue 01 Vol. 2026 Chain 4663 Factory live

LAUNCHPADS, DEPLOYED BY ANYONE.

Everyone else launches coins. You launch the venue they launch them from — your contracts, your fee, your board. Every trade on it pays you inside the same transaction.

01Name & curve
02One transaction
03Fees, per trade
Forge keeps
0.10%
Everything above it is yours. Fixed at deploy, never raised.
Forge fee
0.10%
Fixed at deployNever raised
Owner take
0.90%
On a 1.00% padPer trade
Deploy cost
0.1 ETH
Once, at forge timeNo renewals
Registry
2 / 3
Pads listedSlot open
Clock
TestnetEarly
Volume routed through pads the forge does not control
$1,240

Small, and real. Two test pads are live, three tokens have launched on them, and every dollar of that settled on contracts we cannot pause, edit or drain. The factory writes a pad once and then has no further say in it — which is the only reason a number like this means anything.

Paid to pad owners$11
Tokens launched3
Pads deployed2
Admin keys in existence0
01 / The deal

0.10% isthe whole arrangement.

The factory is immutable. It cannot change your fee, redirect your revenue or touch your reserves — and the cut it takes was written into your pad at deploy.

Make a pad
PadFactory 0xF0e6a41B7cC29d5480fAe2B1937Dd0Ca5518C921
Deploy cost0.1 ETH
Paid once, at forge time. It is the only thing you ever pay up front, and it buys the contract set plus a hosted board.
Forge fee0.10%
Taken inside the fee you set, so traders never pay extra for us. Hard-coded at deploy and impossible to raise afterwards.
Your trade fee0.5–3.0%
You pick it. Everything above our 0.10% lands in your wallet, transferred inside the same swap as the trade itself.
Claim functionNone
There is nothing to claim because nothing accumulates. A $100 buy at 1.00% pays you $0.90 and the forge $0.10, in that block.
Admin keys0
No proxy, no upgrade path, no pause switch, no owner that can reach a balance. Verify it against the interface in the API reference.
02 / Mechanism

A venue you deploy, own,and then walk away from.

01

Forge it

Name your pad, pick its curve, set the fee traders will pay. Sign one transaction — the factory writes those parameters into the contract and there is no function that can edit them afterwards.

Cost
0.1 ETH
02

Take the keys

You get a hosted board at yourslug.zpad.fun and an API key scoped to your pad alone. Keep the board as it ships, skin it, or point your own frontend at the API.

Issued
On confirm
03

They launch

Anyone can launch tokens on your pad. Curves fill, tokens graduate into DEX pools with the LP burned, and trading runs on your contracts — never through your keys, never through ours.

Custody
None
04

You earn

Your share of every trade is transferred inside the same swap. There is no claim button because there is nothing waiting to be claimed. The forge keeps its 0.10% and nothing else.

Your share
0.90%
03 / Pairs

Not just USDG.Pair against a tokenized stock.

25 tickers live on Robinhood Chain · fixed into your pad at deploy Optional add-on · +0.1 ETH
04 / API — one key, the whole pad

Your board workswithout writing any of this.

01

Quickstart

Everything is one REST API at https://api.zpad.fun/v1, plus a thin SDK that wraps it. Both speak to the same endpoints — use whichever fits.

terminalShell
npm i @zpad/sdk
pad.jsJS SDK
import { ZPAD } from '@zpad/sdk'

export const pad = new ZPAD({
  pad: 'yourpad',              // the slug you picked at deploy
  key: process.env.ZPAD_KEY,  // zp_live_… — server side only
})

// who am I talking to?
const info = await pad.get()
console.log(info.name, info.fees.tradeBps, info.stats.tokens)
// → MoonWorks 100 61
02

Authentication

Every request carries a bearer key. A key is scoped to exactly one pad — asking it about someone else's pad returns 403 pad_scope, so a leaked key can never reach beyond the pad it belongs to.

terminalShell
curl https://api.zpad.fun/v1/pads/yourpad \
  -H "Authorization: Bearer zp_live_8f2c19ad4b7e"
PrefixWhere it belongsCan do
zp_live_Your server only. Never in a browser bundle.Read + prepare transactions + manage webhooks
zp_pub_Safe to ship in frontend code and websocket URLs.Read only
zp_test_Testnet pads. Same surface, throwaway data.Everything, on testnet

Keys are shown once, at deploy. Rotating one from your board invalidates the old key after a five minute grace period, so you can roll a deployment without dropping requests.

03

Conventions

Amounts are strings of base units, never floats — "250000000" is 250 USDG at 6 decimals. Timestamps are RFC 3339 UTC. Fees are basis points, so 100 means 1.00%.

Lists are cursor-paginated. Pass the cursor you got back; never build one yourself.

terminalShell
curl "https://api.zpad.fun/v1/pads/yourpad/tokens?limit=25&cursor=eyJvIjoxNzU2NDgzMjAwfQ" \
  -H "Authorization: Bearer $ZPAD_KEY" -i

HTTP/1.1 200 OK
ZPAD-Version: 2026-08-01
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
X-RateLimit-Reset: 1756483260
responseJSON
{
  "data": [ /* … 25 items … */ ],
  "page": {
    "hasMore": true,
    "cursor": "eyJvIjoxNzU2NDc5NjAwfQ",
    "limit": 25
  }
}

Rate limit is 60 requests per second per key, burstable to 120. Over it you get 429 with a Retry-After header — the SDK backs off and retries for you.

04

Your pad

One call returns the whole configuration burned in at deploy, plus live stats. Nothing here can change except the stats — the fee split and curve are immutable.

GET /v1/pads/{slug}JSON
{
  "slug": "yourpad",
  "name": "MoonWorks",
  "address": "0x8f3C1b9a44Ee02c7B1d5aA90fE31c7742Bd41d17",
  "chainId": 4663,
  "owner": "0x7bF3c410aD9e2255Ef01a7cC94f0b8E4d31c9A21",
  "curve": "classic",
  "quote": {
    "symbol": "USDG",
    "address": "0x51aB6f8cB2e7419b0dA3E8Cc2FbD1904Ee7803F7",
    "decimals": 6
  },
  "fees": {
    "tradeBps": 100,        // what a trader pays: 1.00%
    "creatorBps": 90,       // → your wallet, inside the same tx
    "protocolBps": 10,      // → ZPAD. always 10. never rises.
    "launchFeeWei": "500000000000000"
  },
  "graduation": { "reserveUsd": 12000, "lpBurned": true },
  "stats": {
    "tokens": 61,
    "graduated": 9,
    "volumeUsd24h": 38210.44,
    "volumeUsdTotal": 412703.18,
    "creatorFeesUsd": 3714.32
  },
  "createdAt": "2026-08-09T11:24:03Z"
}
05

Tokens

The board listing. Filter by status (bonding, graduated, all) and sort by volume24h, marketCap, newest or progress.

GET /v1/pads/{slug}/tokens?sort=volume24h&status=bondingJSON
{
  "data": [
    {
      "address": "0x91b2e7Ac03d5F1882bB4fD0a7cE6109a54cB4cE1",
      "name": "Hood Cat",
      "ticker": "HOODCAT",
      "image": "ipfs://bafkreihq2s7x…",
      "creator": "0x4a1d90Ff23cB8e07A1502dEc7719b6Aa3C0f88b0",
      "status": "bonding",
      "curve": {
        "progress": 0.343,             // 34.3% of the way to graduation
        "reserveUsd": 4120.55,
        "priceUsd": 0.00000412,
        "supply": "1000000000000000000000000000"
      },
      "marketCapUsd": 8134.90,
      "volumeUsd24h": 3210.40,
      "holders": 212,
      "createdAt": "2026-08-28T09:02:11Z"
    }
  ],
  "page": { "hasMore": true, "cursor": "eyJvIjoxNzU2Mzc4…", "limit": 25 }
}
board.jsJS SDK
// walk the whole board without touching cursors yourself
for await (const token of pad.tokens.paginate({ status: 'bonding' })) {
  if (token.curve.progress > 0.9) {
    await alertTelegram(`${token.ticker} is about to graduate`)
  }
}
06

Launching a token

This is the part people get wrong, so it is worth being precise: the API never signs anything. You POST what the token should be, and you get back unsigned calldata plus a list of things that must happen first. Your user's wallet does the rest.

Send an Idempotency-Key — retrying with the same key returns the same prepared transaction instead of preparing a second one.

POST /v1/pads/{slug}/tokensRequest
{
  "name": "Hood Cat",
  "ticker": "HOODCAT",
  "description": "the cat that bought the dip",
  "image": "ipfs://bafkreihq2s7x…",
  "creator": "0x4a1d90Ff23cB8e07A1502dEc7719b6Aa3C0f88b0",
  "devBuy": { "amount": "100000000", "currency": "USDG" },
  "links": { "x": "https://x.com/hoodcat" }
}
responseJSON
{
  "token": {
    "predictedAddress": "0x91b2e7Ac03d5F1882bB4fD0a7cE6109a54cB4cE1",
    "ticker": "HOODCAT"
  },
  "transaction": {
    "chainId": 4663,
    "to": "0x8f3C1b9a44Ee02c7B1d5aA90fE31c7742Bd41d17",
    "data": "0x1f2a3b4c000000000000000000000000…",
    "value": "500000000000000",       // your launch fee, in wei
    "gasLimit": "2450000"
  },
  "requires": [
    {
      "type": "erc20-approval",
      "token": "0x51aB6f8cB2e7419b0dA3E8Cc2FbD1904Ee7803F7",
      "spender": "0x8f3C1b9a44Ee02c7B1d5aA90fE31c7742Bd41d17",
      "amount": "100000000"
    }
  ],
  "expiresAt": "2026-08-30T12:04:00Z"
}
launch.tsviem
import { createWalletClient, custom, erc20Abi } from 'viem'

const wallet = createWalletClient({ transport: custom(window.ethereum) })
const [account] = await wallet.getAddresses()

const res = await pad.tokens.create({ name: 'Hood Cat', ticker: 'HOODCAT' })

// 1. clear everything the API says must happen first
for (const req of res.requires) {
  if (req.type === 'erc20-approval') {
    await wallet.writeContract({
      account, abi: erc20Abi, address: req.token,
      functionName: 'approve',
      args: [req.spender, BigInt(req.amount)],
    })
  }
}

// 2. send exactly what you were handed. never rebuild the calldata —
//    the predicted address depends on it byte for byte.
const hash = await wallet.sendTransaction({
  account,
  chainId: res.transaction.chainId,
  to:      res.transaction.to,
  data:    res.transaction.data,
  value:   BigInt(res.transaction.value),
  gas:     BigInt(res.transaction.gasLimit),
})

// 3. the token is indexed the moment the block lands
const token = await pad.tokens.waitFor(hash)   // resolves in ~2s
07

Quotes & trades

Quote first, then trade. A quote is priced against the live curve and tells you exactly what the fee split will be — the same numbers the contract will enforce.

POST /v1/pads/{slug}/quoteRequest
{
  "token": "0x91b2e7Ac03d5F1882bB4fD0a7cE6109a54cB4cE1",
  "side": "buy",
  "amountIn": "250000000",     // 250 USDG
  "slippageBps": 100
}
responseJSON
{
  "side": "buy",
  "amountIn":  { "currency": "USDG", "raw": "250000000", "usd": 250.00 },
  "amountOut": {
    "ticker": "HOODCAT",
    "raw": "60728155000000000000000000",
    "min": "60120873450000000000000000"   // after your 1% slippage
  },
  "priceImpactBps": 84,
  "fees": {
    "totalUsd": 2.50,       // 1.00% of 250
    "creatorUsd": 2.25,     // 0.90% → the pad owner
    "protocolUsd": 0.25     // 0.10% → ZPAD
  },
  "validUntil": "2026-08-30T12:00:30Z"
}

POST /v1/pads/{slug}/trades takes the same body and returns a transaction object shaped exactly like the launch one. Trades above 2% of the curve's inventory are refused onchain, so the quote returns 422 insufficient_reserve rather than letting you build a transaction that reverts.

08

Live stream

Polling works, but the websocket is cheaper and instant. It takes a zp_pub_ key, so it is safe to open straight from a browser.

stream.jsWebSocket
const ws = new WebSocket(
  'wss://stream.zpad.fun/v1/pads/yourpad?key=zp_pub_3d81c4f0'
)

ws.addEventListener('open', () => {
  ws.send(JSON.stringify({
    subscribe: ['trade', 'token.created', 'token.graduated'],
  }))
})

ws.addEventListener('message', (e) => {
  const ev = JSON.parse(e.data)
  switch (ev.type) {
    case 'trade':           return paintTrade(ev.data)
    case 'token.created':   return prependCard(ev.data)
    case 'token.graduated': return confetti(ev.data)
  }
})

// the server pings every 30s. two missed pings means the socket is
// dead even though readyState still says OPEN — reconnect on that.
let lastPing = Date.now()
ws.addEventListener('ping', () => { lastPing = Date.now() })
setInterval(() => {
  if (Date.now() - lastPing > 70_000) ws.close()
}, 10_000)
09

Webhooks

For anything that must survive your process restarting. Every delivery is signed with HMAC-SHA256 over the raw body — verify it, or anyone who learns your URL can forge events.

POST https://yourserver.com/hooks/zpadHeaders
Content-Type: application/json
ZPAD-Event: token.graduated
ZPAD-Delivery: dlv_7c1e93f0a244
ZPAD-Signature: t=1756483200,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
bodyJSON
{
  "id": "evt_2f9a71c4b830",
  "type": "token.graduated",
  "created": 1756483200,
  "pad": "yourpad",
  "data": {
    "token": { "address": "0x91b2…4cE1", "ticker": "HOODCAT" },
    "reserveUsd": 12043.55,
    "pool": "0x77Aa02fD91c4e5B3a0817fEc39d5B2c4406C910F",
    "lpBurned": true,
    "creatorFeesUsd": 109.49
  }
}
hooks.jsNode
import express from 'express'
import { createHmac, timingSafeEqual } from 'node:crypto'

const app = express()

app.post('/hooks/zpad',
  // raw, not json: the signature covers the exact bytes we sent
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const header = req.get('ZPAD-Signature') ?? ''
    const part = Object.fromEntries(
      header.split(',').map(kv => kv.split('='))
    )

    // replay protection: nothing older than five minutes
    const age = Math.abs(Date.now() / 1000 - Number(part.t))
    if (!Number.isFinite(age) || age > 300) return res.sendStatus(400)

    const expected = createHmac('sha256', process.env.ZPAD_WEBHOOK_SECRET)
      .update(`${part.t}.${req.body}`)
      .digest()

    const given = Buffer.from(part.v1 ?? '', 'hex')
    if (given.length !== expected.length || !timingSafeEqual(given, expected)) {
      return res.sendStatus(401)
    }

    const event = JSON.parse(req.body)

    // ack inside 5s or we count it as failed — do the work after.
    // deliveries can repeat, so key your side on event.id.
    res.sendStatus(200)
    queue.add(event.id, event)
  })
EventFires when
token.createdA launch transaction lands on your pad
trade.executedAny buy or sell along a curve
token.graduatedA curve fills and migrates to its DEX pool
fees.paidCreator fees settle to your wallet
pad.milestoneVolume or token-count round numbers, for announcements

Non-2xx replies are retried 8 times with exponential backoff across 24 hours, then the delivery is parked and shown on your board so you can replay it by hand.

10

Errors

Every failure is JSON with a stable machine-readable code. Branch on the code, never on the message — messages get reworded.

422 Unprocessable EntityJSON
{
  "error": {
    "code": "insufficient_reserve",
    "message": "Trade exceeds 2% of the curve's inventory.",
    "param": "amountIn",
    "requestId": "req_9f21a0c4e7",
    "docs": "https://docs.zpad.fun/errors#insufficient_reserve"
  }
}
CodeHTTPWhat to do
invalid_request400A field is missing or malformed; param names it
bad_key401Key is wrong, revoked, or past its rotation grace period
pad_scope403That key belongs to a different pad
not_found404No such pad, token or delivery
idempotency_conflict409Same key, different body — pick a new key
insufficient_reserve422Trade too large for the curve; split it up
slippage_exceeded422Quote went stale; re-quote and retry
rate_limited429Back off for Retry-After seconds
chain_unavailable503Reads are stale; retry. Nothing was lost
11

Onchain

The API is a convenience. This is the thing that actually holds the money, and you can talk to it without us — the interface below is the whole surface of a pad's curve contract.

IPadCurve.solSolidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

interface IPadCurve {
    /// @notice Buy along the curve. Reverts if the price moved past `minOut`.
    /// @dev    Creator and protocol fees are transferred inside this call.
    function buy(uint256 amountIn, uint256 minOut, address to)
        external returns (uint256 amountOut);

    function sell(uint256 amountIn, uint256 minOut, address to)
        external returns (uint256 amountOut);

    /// @notice Fixed at deploy. protocolBps is always 10 and has no setter.
    function fees() external view returns (
        uint16 tradeBps, uint16 creatorBps, uint16 protocolBps
    );

    function reserve() external view returns (uint256);
    function quoteToken() external view returns (address);

    /// @notice Callable by anyone once reserve() clears the threshold.
    ///         Pairs the reserve into a DEX pool and burns the LP.
    function graduate() external;

    event Traded(
        address indexed trader,
        bool isBuy,
        uint256 amountIn,
        uint256 amountOut,
        uint256 creatorFee,
        uint256 protocolFee
    );

    event Graduated(address indexed pool, uint256 reserve, uint256 lpBurned);
}

Note what is missing: no mint, no pause, no setFees, no owner that can touch balances. Your creator fee is paid inside buy and sell — there is no claim function because there is nothing to claim.

ContractAddressRole
PadFactory0xF0e6…C921Deploys every pad. Immutable, no proxy.
PadRegistry0x3B77…A04DLists every pad ever forged.
FeeRouter0x59Dd…7E12Splits each trade fee at settlement.
USDG0x51aB…03F7Quote currency, 6 decimals.
05 / Pricing

Three numbers.There is no fourth one.

ItemWhen it is charged AmountGoes to
Deploy
Once, at forge time. Buys the contract set, the registry entry and a hosted board.
0.1 ETH
ZPAD
Stock pairs
Optional, once, at forge time. Adds 25 tokenized stock tickers your creators can pair against instead of USDG.
+0.1 ETH
ZPAD
Forge fee
Every trade on your pad. Taken inside the fee you set, so traders never pay extra for us.
0.10%
ZPAD
Trade fee
Every trade on your pad. You choose the number; the 0.10% above lives inside it.
0.5–3.0%
You, minus 0.10%
Token launch fee
Each token launched on your pad. Set it to zero to open the floodgates.
0–0.005 ETH
You, all of it
Set a 1.00% fee and route $1M through your pad — that is $9,000 to you and $1,000 to the forge. No tiers · no renewals · no revenue share
Registry slot 03 is open

YOUR PAD,ONE TRANSACTION AWAY.

Name it, tune the curve, set your fee. The factory writes it in and hands you the keys — then has no further say in anything.

Make a pad Read the API
Deploy 0.1 ETH Forge fee 0.10% Custody none Admin keys none