Build on Outlaw
Everything the protocol does on-chain, the contracts it touches on Robinhood Chain, and a public REST API so terminals, bots, and aggregators can list Outlaw pairs without asking permission.
Introduction
Outlaw is a token launchpad on Robinhood Chain. One transaction deploys a token and opens a live market — no presale, no allowlist, no owner keys. Every launch is constrained the same way, by construction:
| Guarantee | How |
|---|---|
| Fixed supply | 1,000,000,000 tokens, minted once at deploy. No mint function exists afterwards. |
| No owner | Governance is noOp — no admin, no upgrades, no taxes, no blacklist. Renounced from block one. |
| Fair distribution | 90% of supply is sold on a public bonding curve. The deployer buys on the same curve as everyone else. |
| Locked liquidity | On graduation the curve proceeds seed a Uniswap pool and the LP is locked. There is no withdraw path. |
Everything is non-custodial: launches and trades are transactions signed by your wallet, settled on Robinhood Chain. The Outlaw app is a viewer and a transaction builder — it never holds funds.
How it works
Launches run on Doppler static auctions — audited launch infrastructure deployed on Robinhood Chain mainnet. A token's life has three phases, which map 1:1 to the columns on the /pairs board:
| Phase | Status | What happens |
|---|---|---|
| 1. Curve | new | Deploy mints 1B tokens and puts 900M on a Uniswap v3 bonding curve priced in WETH, starting around a $10K market cap. |
| 2. Completing | completing | Buys walk the price up the curve. Progress is deterministic — the % filled is readable on-chain at any time. |
| 3. Migrated | migrated | At ~$250K market cap the curve graduates: proceeds + remaining supply seed a Uniswap v2 pool and the LP is locked permanently. Trading continues on the open DEX. |
Network
Robinhood Chain is an Arbitrum Orbit L2. Gas is paid in ETH and blocks arrive roughly every 100ms.
| Mainnet | Testnet | |
|---|---|---|
| Chain ID | 4663 | 46630 |
| RPC | https://rpc.mainnet.chain.robinhood.com | https://rpc.testnet.chain.robinhood.com |
| Explorer | https://robinhoodchain.blockscout.com | https://explorer.testnet.chain.robinhood.com |
| Gas token | ETH | ETH |
Contracts
Core infrastructure on Robinhood Chain mainnet (4663):
| Contract | Address |
|---|---|
| WETH9 | 0x0bd7d308f8e1639fab988df18a8011f41eacad73 |
| UniswapV3Factory | 0x1f7d7550b1b028f7571e69a784071f0205fd2efa |
| SwapRouter02 | 0xcaf681a66d020601342297493863e78c959e5cb2 |
| NonfungiblePositionManager | 0x73991a25c818bf1f1128deaab1492d45638de0d3 |
| QuoterV2 | 0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7 |
The Doppler launch contracts (airlock, factories, migrators) ship with the SDK — resolve the current set at runtime instead of hard-coding:
import { getAddresses } from "@whetstone-research/doppler-sdk/evm";
const addrs = getAddresses(4663); // airlock, tokenFactory, v3Initializer, ...Launching
The simplest path is the launch form: image, name, ticker, socials — one wallet signature. To launch programmatically, do exactly what the app does.
1 · Connect to Robinhood Chain
import { createPublicClient, createWalletClient, custom, http, defineChain } from "viem";
export const robinhood = defineChain({
id: 4663,
name: "Robinhood Chain",
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
rpcUrls: { default: { http: ["https://rpc.mainnet.chain.robinhood.com"] } },
blockExplorers: {
default: { name: "Blockscout", url: "https://robinhoodchain.blockscout.com" },
},
});
const publicClient = createPublicClient({ chain: robinhood, transport: http() });
const walletClient = createWalletClient({ chain: robinhood, transport: custom(window.ethereum) });2 · Store token metadata
Post the off-chain metadata first — the response uri becomes the token's on-chain tokenURI, so wallets and explorers resolve the name, image, and links. Images are data URLs, downscaled client-side to ≤512px.
curl -X POST https://outlaw.markets/api/launch/metadata \
-H "content-type: application/json" \
-d '{
"mint": "my-token-launch",
"name": "My Token",
"symbol": "MYT",
"description": "Launched outside the system.",
"image": "data:image/png;base64,..."
}'
# → { "uri": "https://outlaw.markets/api/metadata/my-token-launch" }3 · Deploy the static auction
One call deploys the token and its bonding curve. The parameters below are the Outlaw standard — same supply, same curve band, same migration for every launch.
import { DopplerSDK, getAddresses } from "@whetstone-research/doppler-sdk/evm";
import { parseEther } from "viem";
const sdk = new DopplerSDK({ publicClient, walletClient, chainId: 4663 });
const params = sdk
.buildStaticAuction()
.tokenConfig({ name: "My Token", symbol: "MYT", tokenURI })
.saleConfig({
initialSupply: parseEther("1000000000"), // fixed 1B — every Outlaw token
numTokensToSell: parseEther("900000000"), // 90% sold on the curve
numeraire: getAddresses(4663).weth, // priced in WETH
})
.withMarketCapRange({
marketCap: { start: 10_000, end: 250_000 }, // USD — curve fills $10K → $250K
numerairePrice: 3000, // ETH/USD reference
})
.withMigration({ type: "uniswapV2" }) // graduation: LP seeded + locked
.withGovernance({ type: "noOp" }) // no owner, no upgrades, no taxes
.withUserAddress(deployer)
.build();
const { tokenAddress, poolAddress, transactionHash } =
await sdk.factory.createStaticAuction(params);tokenAddress is your ERC-20, poolAddress is the live curve. The token page and the API pick both up immediately — there is no registration step.Trading
While a token is on the curve it trades in its Uniswap v3 pool against WETH; after graduation it trades in the migrated Uniswap v2 pool. Either way it's standard Uniswap — every router, aggregator, and bot that speaks Uniswap works unchanged, with standard pool fees.
Quoting
Quote through QuoterV2 (a simulate call, not a transaction), then swap through SwapRouter02 with your slippage bound:
import { parseAbi } from "viem";
const QUOTER_V2 = "0x33e885ed0ec9bf04ecfb19341582aadcb4c8a9e7";
const quoterAbi = parseAbi([
"function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96)) returns (uint256 amountOut, uint160, uint32, uint256)",
]);
const { result } = await publicClient.simulateContract({
address: QUOTER_V2,
abi: quoterAbi,
functionName: "quoteExactInputSingle",
args: [{ tokenIn: weth, tokenOut: token, amountIn, fee, sqrtPriceLimitX96: 0n }],
});
const minAmountOut = (result[0] * 99n) / 100n; // 1% slippage guardFor display prices without an RPC round-trip, use the REST API below.
REST API
A public read API for anyone integrating Outlaw pairs into an external terminal, screener, or bot. It serves the same data that powers the /pairs board and the token pages.
# the live board — three columns in one call
curl "https://outlaw.markets/api/v1/tokens?status=new&limit=10"
# one token, by launch id or 0x contract address
curl "https://outlaw.markets/api/v1/tokens/kbm"
# chart data
curl "https://outlaw.markets/api/v1/tokens/kbm/candles?interval=5m"Conventions
| Base URL | https://outlaw.markets/api/v1 |
| Auth | None — all endpoints are public reads |
| CORS | Open (Access-Control-Allow-Origin: *) — call it straight from a browser terminal |
| Format | JSON. Prices in USD, timestamps in unix seconds, percentages as numbers (12.4 = +12.4%) |
| Errors | Non-200 with { "error": "message" } — 400 for bad params, 404 for unknown tokens |
| Caching | Responses set short cache-control max-age; respect it instead of hammering |
| Token ids | Every {id} accepts the launch id (slug) or the token's 0x contract address |
List tokens
The board. Filter by lifecycle status to reproduce the three columns, or search across names and tickers.
| Param | Type | Description |
|---|---|---|
| status | string | Filter: new · completing · migrated |
| q | string | Search names and tickers |
| sort | string | createdAt (default) · marketCap · volume24h · change24h · bondingProgress |
| order | string | desc (default) · asc |
| limit | number | 1–100, default 50 |
| offset | number | Pagination offset |
{
"tokens": [
{
"id": "kbm",
"name": "Keyboard Monkey",
"symbol": "KBM",
"status": "new",
"priceUsd": 0.0000084,
"marketCapUsd": 8400,
"liquidityUsd": 5100,
"volume24hUsd": 6200,
"change24hPct": 12.4,
"txns24h": 31,
"holders": 14,
"bondingProgressPct": 3,
"createdAt": 1783987158
}
],
"pagination": { "total": 8, "limit": 10, "offset": 0 }
}Token detail
Everything the board row has, plus addresses, supply, safety facts, and links to the sub-resources.
{
"token": {
"id": "kbm",
"name": "Keyboard Monkey",
"symbol": "KBM",
"status": "new",
"priceUsd": 0.0000084,
"marketCapUsd": 8400,
"liquidityUsd": 5100,
"volume24hUsd": 6200,
"change24hPct": 12.4,
"txns24h": 31,
"holders": 14,
"bondingProgressPct": 3,
"createdAt": 1783987158,
"description": "Fixed 1B supply, mint authority revoked at deploy...",
"tokenAddress": "0x7c61...",
"deployerAddress": "0x94b0...",
"totalSupply": 1000000000,
"chainId": 4663,
"graduationMarketCapUsd": 250000,
"ownershipRenounced": true,
"liquidityLocked": false,
"links": {
"self": "/api/v1/tokens/kbm",
"candles": "/api/v1/tokens/kbm/candles",
"trades": "/api/v1/tokens/kbm/trades",
"holders": "/api/v1/tokens/kbm/holders",
"page": "/token/kbm"
}
}
}Candles
OHLCV bars for charting — drop them straight into a TradingView/lightweight-charts series. Base resolution is 1 minute; higher intervals are aggregated server-side.
| Param | Type | Description |
|---|---|---|
| interval | string | 1m (default) · 5m · 15m · 1h |
| limit | number | 1–1000, default 500 (most recent bars) |
{
"id": "kbm",
"interval": "5m",
"candles": [
{
"time": 1783985700,
"open": 0.0000081,
"high": 0.0000086,
"low": 0.0000079,
"close": 0.0000084,
"volume": 41.2
}
]
}Trades
Most recent fills, newest first. limit 1–100, default 50.
{
"id": "kbm",
"trades": [
{
"side": "buy",
"amountToken": 402519,
"amountUsd": 3381.16,
"priceUsd": 0.0000084,
"wallet": "0x3f9a...",
"timestamp": 1783987121
}
]
}Holders
Top holders by supply share. Special positions carry a tag: lp (the locked pool) and deployer.
{
"id": "kbm",
"totalHolders": 14,
"holders": [
{ "wallet": "0xa1b2...", "pctSupply": 6.4, "tag": "lp" },
{ "wallet": "0x94b0...", "pctSupply": 2.1, "tag": "deployer" },
{ "wallet": "0x88c3...", "pctSupply": 1.9 }
]
}Protocol stats
Protocol-wide totals for dashboards and tickers.
{
"chainId": 4663,
"launchesTotal": 18920,
"migratedTotal": 1284,
"volume24hUsd": 23400000,
"liquidityRoutedUsd": 9640000,
"board": { "new": 8, "completing": 6, "migrated": 14 }
}Gotchas
| Testnet ≠ mainnet | Chain 46630 has no Uniswap or Doppler deployment. Test against mainnet with small size, or fork it locally. |
| block.number is L1-paced | Robinhood Chain is an Arbitrum Orbit rollup: the block.number opcode tracks the parent chain (~10s+ cadence) while RPC blocks land every ~100ms. Never use it for fine-grained timing — use block.timestamp. |
| Metadata before launch | Post /api/launch/metadata first and pass the returned uri as tokenURI — a launch with a dead URI renders blank in wallets forever (the token is immutable). |
| Address casing | The API returns lowercase 0x addresses and accepts either casing; contract calls via viem want checksummed or lowercase, not mixed. |
| Immutable means immutable | No function exists to change a token's name, image, taxes, or supply after deploy. Triple-check the ticker before signing. |
| Quote before every swap | Curve prices move fast near graduation. Re-quote via QuoterV2 right before sending, and always set amountOutMinimum. |
