Docs & API

Build whatever you like on top: scripts, bots, dashboards, trading tools. You don't need a key or an allowlist, because your code calls the same contracts this site calls, and the only limits are your own RPC provider's. Each launch puts its entire supply into a Uniswap v4 pool paired with USDC, and that position stays locked.

Bots and snipers are welcome. There's no anti-snipe window, max buy, max wallet, cooldown or blacklist in any launch or in the hook, and the first block of every launch is open to everyone.

Contracts on Arc

Addresses this site uses (chain id 5042)
RPC (public)https://rpc.mainnet.arc.io
Portal0x7F80b1198e6DAa56b0019Cb45020382358E385Fd
Hook0x10dE365Cc583bA953a9e6C36658A138082d9e8cc
PoolManager0x8366a39CC670B4001A1121B8F6A443A643e40951
UniversalRouter0x4fcA4a51Ab4F23A7447b3284fBd7D73289A89Fb1
Permit20x000000000022D473030F116dDEE9F6B43aC78BA3
USDC (every pool's pair)0x3600000000000000000000000000000000000000

Decimals: USDC has 6, launch tokens have 18. Supply is always 1,000,000,000. USDC doubles as Arc's gas coin, so one balance covers both trading and gas.

Public JSON API

This site serves the pad's data as JSON. No key, no sign-up, and any website or bot can call it (CORS is open). Responses are cached for a few seconds at the edge, so poll as often as you like.

GET https://pad.stabledoge.site/api/v1/config            # chain, RPC, every contract address, pool settings, live or not
GET https://pad.stabledoge.site/api/v1/launches?limit=50  # newest first; &before=<block> pages back
GET https://pad.stabledoge.site/api/v1/market?limit=30    # live price, market cap, change since launch
GET https://pad.stabledoge.site/api/v1/tokens/0xToken     # one launch: pool key, price, tax waiting to flush, details
curl https://pad.stabledoge.site/api/v1/market?limit=1
# { "live": true, "updatedAt": 1790400000, "tokens": [ { "address": "0x…", "symbol": "WOW",
#   "priceUsd": 0.0000021, "marketCapUsd": 2100, "changeSinceLaunchPct": 84.2,
#   "buyTaxBps": 200, "sellTaxBps": 400, "createdAt": 1790399000, "image": null } ] }

TypeScript SDK

pad-sdk wraps all of this: it spots new launches the block they happen, quotes, buys, sells, launches and claims, and sets up the Permit2 approvals for you. It's built on viem. Source and a ready-made sniper example: pad-sdk on GitHub.

import { createArcClients, createPadClient } from 'sdoge-pad-sdk';

const { publicClient, walletClient } = createArcClients(process.env.PRIVATE_KEY);
const pad = createPadClient({ publicClient, walletClient });

// Buy $5 of every new launch as soon as it's mined, 5% max slippage.
pad.watchLaunches(async (launch) => {
  const { hash } = await pad.buy({ token: launch.token, usdcIn: 5_000_000n, slippageBps: 500 });
  console.log('bought', launch.symbol, hash);
});

USDC is also the gas coin, so the SDK keeps $0.10 back on every buy. Every swap it builds carries a minimum output; it never sends one without.

Reading launches and prices

Pick a source:

Option A: straight from the chain

Each launch is one LaunchCreated log on the portal. For a live price, read the pool's slot0 with a single extsload call on PoolManager. There's nothing else to run.

Option B: the pad-indexer HTTP API

pad-indexer stores launches, trades, holders and candles in its own database, so you can skip scanning logs yourself. The base URL below is this site's indexer when one is set.

GET https://your-indexer.example.com/launches
GET https://your-indexer.example.com/stats/:token
GET https://your-indexer.example.com/trades/:token?n=30
GET https://your-indexer.example.com/holders/:token?n=20
GET https://your-indexer.example.com/candles/:token?n=96&interval=900   # OHLCV; interval in seconds, 900 by default
curl https://your-indexer.example.com/stats/0xTokenAddress
# { "priceUsd": 0.000042, "marketCapUsd": 42000, "volume24hUsd": 3150.5,
#   "change24hPct": -2.7, "holders": 88, "txns24h": 19, "liquidityUsd": 7400 }

Buying and selling

A trade is one v4 exact-input swap on the launch's pool, sent through Uniswap's UniversalRouter. Whatever you spend (USDC on a buy, the token on a sell) needs two approvals first: the ERC-20 approves Permit2, then Permit2 approves the router. Each one lasts until it is used up, expires or is revoked. This site approves exactly the amount of each trade, never an unlimited amount, and the router's allowance expires after a day.

// Step 1: let Permit2 move the amount you're spending
erc20.approve(PERMIT2, amount)

// Step 2: let the router spend it through Permit2 (this site sets a 1-day expiry)
permit2.approve(token, ROUTER, amount, expiration)

// Step 3: the swap
router.execute(commands, inputs, deadline)
// commands = 0x10 (V4_SWAP)
// actions  = SWAP_EXACT_IN_SINGLE (0x06), SETTLE_ALL (0x0c), TAKE_ALL (0x0f)
// Arc's UniversalRouter (v2.1.1) expects SIX fields in SWAP_EXACT_IN_SINGLE:
//   (PoolKey poolKey, bool zeroForOne, uint128 amountIn, uint128 amountOutMinimum,
//    uint256 minHopPriceX36 /* 0 turns it off */, bytes hookData)
// Encode only five and the call reverts without any error data.

// Getting a quote costs nothing and needs no balance. eth_call execute with two actions,
// SWAP_EXACT_IN_SINGLE then TAKE_ALL(currencyOut, type(uint256).max). That minimum can never
// be met, so the call reverts with V4TooLittleReceived(minAmountOutReceived, amountReceived):
// read the quote from amountReceived.

Pool order: whichever of the two addresses is smaller, compared as a number, is currency0. On a buy you pay USDC, so zeroForOne is true exactly when USDC is currency0; on a sell, when the launch token is.

Launching from code

A single transaction mints the token, opens its USDC pool and locks all 1B tokens in the pool position:

portal.createLaunch({
  name: "Much Wow Coin",
  symbol: "WOW",
  startingMarketCapQuote: 2_500_000000n, // $2,500 in USDC's 6 decimals ($100 at least)
  buyTaxBps: 200,  // 2%, up to 1000 (10%)
  sellTaxBps: 400, // 4%
})
// emits LaunchCreated(token, creator, locker, splitter, poolId, quoteAsset,
//                     tokenIsToken0, buyTaxBps, sellTaxBps, tickLower, tickUpper,
//                     initSqrtPriceX96, name, symbol)

Every launch gets a splitter contract of its own, and the creator can claim from it whenever they like (see Fees).

Fees and claims

A trade pays two things: the pool's 1% LP fee, and the creator's tax for that side, anywhere from 0 to 10%, picked at launch and fixed from then on. The tax is always collected in USDC. The tax, plus whatever LP fees are earned in USDC, is shared 90% to the creator and 10% to the platform.

hook.flush(poolKey)            // anyone: pushes a pool's waiting tax into its splitter
locker.harvestFees()           // anyone: collects LP fees; USDC fees go to the splitter, token fees are burned
splitter.claim(to, USDC)       // creator only: pays out the creator's 90%
splitter.claimPlatform(USDC)   // anyone: pays the platform's 10% to the treasury

Swaps never pay anyone out directly, so if a payout address gets stuck or blocklisted, only its own claim fails and trading carries on. A creator can pass the role to another wallet in two steps: transferCreator, then acceptCreator from the new wallet.

Events

event LaunchCreated(
  address indexed token, address indexed creator, address locker, address splitter,
  bytes32 poolId, address quoteAsset, bool tokenIsToken0,
  uint16 buyTaxBps, uint16 sellTaxBps, int24 tickLower, int24 tickUpper,
  uint160 initSqrtPriceX96, string name, string symbol
);

event Swap( // emitted by PoolManager for every pool: filter on id
  bytes32 indexed id, address indexed sender, int128 amount0, int128 amount1,
  uint160 sqrtPriceX96, uint128 liquidity, int24 tick, uint24 fee
);

event TaxCollected(bytes32 indexed poolId, address indexed quoteAsset, bool isBuy, uint256 amount);