# Pons Trading API
> Trade and launch tokens on Pons, on Robinhood Chain
---
# Robinhood Chain Trading API - Pons, Flap, Bags, o1 and every launchpad
> Build bots that trade tokens from every launchpad on Robinhood Chain - Pons, Flap, Bags, o1, Pools.trade, PAIR, Long, letscash - and any Uniswap pool, through one free, keyless API. Buy, sell, stream every launch live, create tokens, claim creator fees.
# Robinhood Chain Trading API
The simplest way to trade tokens from every launchpad on Robinhood Chain through one API. Pons, Flap, Bags, o1, Pools.trade, PAIR, Long and letscash - on their bonding curves and in the Uniswap pools they graduate into - plus any other token with a Uniswap pool on the chain. Launch new tokens on Pons v2.
shrine.trade is one keyless API for executing buys and sells on any of them, streaming every launch and graduation the moment it happens, creating tokens with a dev buy, reading curve and pool state, and claiming creator fees.
[Buy & Sell](/local-trade) · [Live Feed](/live-launches) · [Create Token](/create-token) · [Fees](/fees)
## One API, every launchpad
| Launchpad | Where its tokens trade | `protocol` |
|---|---|---|
| **Pons** v2 / v1 | bonding curve, then Uniswap v4 / Uniswap V3 from launch | `PONS_V2` / `PONS_V1` |
| **Flap** | Flap's curve, then its Uniswap v2 pair - the Portal fills both | `FLAP` |
| **Bags** | bonding curve, then a Uniswap v4 pool with the Bags hook | `BAGS` |
| **o1** | Uniswap v4 pool with the o1 hook from launch, priced in a tokenised stock | `O1` |
| **Pools.trade** | plain Uniswap v4 pool from launch | `POOLS_TRADE` |
| **letscash** | Uniswap v4 pool with the letscash hook | `LETSCASH` |
| **PAIR** | up to five Uniswap v4 pools, each priced in a tokenised stock | `PAIR` |
| **Long** | Uniswap v4 pool with the Doppler hook, priced in a tokenised stock | `LONG` |
| **Varo**, **Virtuals** | launches stream on the live feed; trading is coming | `VARO`, `VIRTUALS` |
| anything else on **Uniswap** | v4 or V3 pool - NVDA, TSLA and the other tokenised stocks included | `UNISWAP` |
Pass a token address and the API works out which launchpad and which venue. Tokens priced in USDG, DJT, NVDA and the other tokenised stocks are bought with plain ETH; the conversion is part of the transaction.
## Why shrine.trade
- **Keyless.** No account, no API key, no sign-up. Call it.
- **Your key stays yours.** Local only. Every endpoint returns unsigned transactions. You sign them with your own wallet. Your private key never leaves your machine.
- **Everything in ETH.** Buy any token with ETH, whatever it is priced in. Sell back to ETH with one flag.
- **FREE live feed.** Every launch, graduation and new pool over WebSocket, within a second of the block.
- **Sub-second quotes.** Independent chain reads run in parallel; a quote comes back in a few hundred milliseconds.
- **Checked before you sign.** Funds, balances, approvals and slippage are verified up front, so what you get back lands.
- **Front-run protected.**
- **Copy-paste scripts.** Every page has a plain JavaScript and Python script: change three lines, run it.
- **Built for bots.** Stable error codes, consistent nonces, ETH-denominated amounts, and the [sniper tutorial](/tutorials/snipe-pons-launches) to start from.
```
Base URL: https://api.shrine.trade/rh
```
## Endpoints
| Endpoint | What it does |
|---|---|
| `POST /api/local-trade` | Buy or sell a token from any launchpad on the chain → [Buy & Sell](/local-trade) |
| `POST /api/send` | Broadcast a transaction you signed - no RPC of your own needed → [Sending](/local-trade#sending) |
| `WS /api/launches/ws` | Live feed of launches on every launchpad, graduations and new pools → [Live Launches](/live-launches) |
| `WS /api/stream/ws` | Every buy, sell and liquidity change for the tokens you name, curve and Uniswap. Free key → [Advanced Data Stream](/advanced-stream) |
| `POST /api/create-token` | Launch a token on Pons v2, with an optional dev buy → [Create Token](/create-token) |
| `GET /api/token/{address}` | Which launchpad, which venue, curve or pool state, snipe tax, unswept creator fees → [Token Info](/token-info) |
| `GET /api/fees/{wallet}` · `POST /api/claim-fees` | What your launches earned, and the transactions that pay it out → [Claim Creator Fees](/creator-fees) |
## Questions?
Read the [FAQ](/faq) for supported platforms, fees, amounts, slippage, private-key safety and the live feed. Or follow a [tutorial](/tutorials) - sniping launches, trading stock-priced tokens, and what gas costs on Robinhood Chain.
## Stay updated
Follow [@shrinetrade on X](https://x.com/shrinetrade) for releases, and join the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) for support from the team.
AI coding tools can read the full documentation as a single text file: [llms-full.txt](/llms-full.txt).
*shrine.trade is an independent trading API for Robinhood Chain developers and traders. It is not affiliated with Pons, Flap, Bags, o1, Pools.trade, PAIR, Long, letscash, Varo, Virtuals, Uniswap or Robinhood Markets, Inc.; each launchpad's contracts and fees belong to it. Built by traders, for traders.*
---
# Pons Trade Stream: Every Buy, Sell and Liquidity Change on Robinhood Chain
> WebSocket stream of every buy, sell, liquidity add and liquidity removal for the Pons tokens you choose, on the bonding curve, in Uniswap v4 after graduation, and in Uniswap V3. No key needed, live.
# Advanced Data Stream
Every trade and every liquidity change for the Pons tokens you name, pushed as they land. One socket covers the whole life of a token:
- **On the bonding curve** - each buy and sell on the Pons v2 curve, with the curve's fee and any creator or opening-snipe tax broken out.
- **In Uniswap v4 after graduation** - each swap in the token's pool, and each liquidity position opened or closed.
- **In Uniswap V3** - the same for Pons v1 tokens, which launch straight into a V3 pool.
Use it for a strategy, a wallet tracker, volume and holder analytics, or a bot that reacts to what others do instead of only to launches. The [launch feed](/live-launches) tells you a token exists; this tells you what happens to it.
> **No key needed.** Connect and go: one connection per IP address. For more connections, or events a few hundred milliseconds earlier straight from the sequencer, see the [⚡ Lightspeed stream](/lightspeed/stream).
```
WS wss://api.shrine.trade/rh/api/stream/ws?tokens=0x…,0x…
WS wss://api.shrine.trade/rh/api/stream/ws?wallets=0x…,0x…
```
Follow **tokens** to get everything that happens to them, **wallets** to get everything they do on any Pons token, or both on one socket.
## Copy this
**JavaScript**
```js
const TOKENS = ["0x94FDe7626988Ae15E406E89C2277cd1a820dfbD0"]; // any Pons tokens
const ws = new WebSocket(`wss://api.shrine.trade/rh/api/stream/ws?tokens=${TOKENS.join(",")}`);
ws.onmessage = (e) => {
const m = JSON.parse(e.data);
if (m.type === "websocket_active") { console.log("following", m.tokens.length, "tokens from block", m.block); return; }
if (m.type === "trade") console.log(`${m.side.toUpperCase()} ${m.tokenAmountFormatted} ${m.symbol} for ${m.quoteAmountFormatted} ${m.quoteSymbol} on ${m.venue} by ${m.origin}`);
if (m.type === "liquidity") console.log(`liquidity ${m.action} ${m.symbol} on ${m.venue}: ${m.liquidityDelta}`);
if (m.type === "lagged") console.warn("read too slowly, missed", m.dropped, "events");
};
// Change what you follow without reconnecting.
// ws.send(JSON.stringify({ subscribe: ["0x…"], unsubscribe: ["0x…"] }));
```
Node 22+ has `WebSocket` built in; older Node needs `npm install ws`.
**Python**
```python
import json, websocket # pip install websocket-client
TOKENS = ["0x94FDe7626988Ae15E406E89C2277cd1a820dfbD0"] # any Pons tokens
def on_message(ws, raw):
m = json.loads(raw)
if m["type"] == "websocket_active":
print("following", len(m["tokens"]), "tokens from block", m["block"])
elif m["type"] == "trade":
print(f'{m["side"].upper()} {m["tokenAmountFormatted"]} {m["symbol"]} for {m["quoteAmountFormatted"]} {m["quoteSymbol"]} on {m["venue"]} by {m.get("origin")}')
elif m["type"] == "liquidity":
print(f'liquidity {m["action"]} {m["symbol"]} on {m["venue"]}: {m["liquidityDelta"]}')
elif m["type"] == "lagged":
print("read too slowly, missed", m["dropped"], "events")
url = f"wss://api.shrine.trade/rh/api/stream/ws?tokens={','.join(TOKENS)}"
websocket.WebSocketApp(url, on_message=on_message).run_forever()
```
## What you get
On connect, one `websocket_active` frame confirming what is being followed:
```json
{
"type": "websocket_active",
"stream": "advanced",
"tokens": ["0x94FDe7626988Ae15E406E89C2277cd1a820dfbD0"],
"wallets": [],
"unknown": [],
"maxTokens": 50,
"maxWallets": 50,
"block": 57705543,
"upstream": "open"
}
```
`unknown` lists anything you asked for that the stream can't follow, with a reason: an address that isn't a Pons token, or not an address at all. The rest of the connection is events.
`upstream` is `open` when logs are flowing from the node and `connecting` while the subscription is being set up, which the first client to connect triggers; it takes a second or two. If a token you follow stays silent, check this before assuming the token is quiet.
### Trades
```json
{
"type": "trade",
"venue": "pons_curve",
"side": "buy",
"token": "0x94FDe7626988Ae15E406E89C2277cd1a820dfbD0",
"symbol": "LAPTOP",
"protocol": "PONS_V2",
"quoteToken": "0x…",
"quoteSymbol": "PLTR",
"origin": "0xf0816aa238BaA4774D41B88556292413A6059B75",
"caller": "0xEc20E594D28a17511264dc73a84cd4AA957B0ABc",
"tokenAmount": "885663926228359252587",
"tokenAmountFormatted": "885.663926228359252587",
"quoteAmount": "21060197598119",
"quoteAmountFormatted": "0.000021060197598119",
"price": "0.000000023779",
"curveFee": "210601975981",
"tax": "0",
"priceAfter": "0.000000023779",
"marketCap": "23.779",
"tokenReserve": "807536513474090402491482633",
"tokenReserveFormatted": "807536513.474090402491482633",
"quoteReserve": "2080401284608788604",
"quoteReserveFormatted": "2.080401284608788604",
"reservesSource": "curve",
"block": 55978479,
"timestamp": 1788550107,
"tx": "0xcf9c…",
"logIndex": 8
}
```
| Field | Meaning |
|---|---|
| `venue` | `pons_curve`, `uniswap_v4` (a graduated v2 token's pool) or `uniswap_v3` (a v1 token's pool). |
| `side` | `buy` - the quote asset went in and tokens came out. `sell` - the reverse. |
| `quoteToken` / `quoteSymbol` | What the token is priced in: ETH (address zero), USDG, a tokenised stock. Amounts are in this. |
| `origin` | The wallet that sent the transaction. Most trades go through a router - ours, Uniswap's, Pons's own site - so `caller` is a contract and `origin` is the person. Watch wallets by this. Missing on the rare event where the node didn't return the transaction in time. |
| `caller` | Whoever called the venue: a router, or the wallet itself on a direct trade. |
| `tokenAmount` / `quoteAmount` | Base units, plus a `…Formatted` decimal for each. On the curve these are what the curve received and paid out. |
| `price` | Quote per token, from this trade's own two amounts. |
| `curveFee` / `tax` | Curve trades only: the curve's fee and any creator or opening-snipe tax taken from the quote leg, base units. |
| `priceAfter` | Quote per token once this trade is done: the pool's price for pool trades, this trade's own price on the curve. |
| `marketCap` | `priceAfter` times the token's total supply, in the quote asset. Convert with the quote's own price if you want dollars. |
| `tokenReserve` / `quoteReserve` | What the venue holds after the trade, base units plus `…Formatted`. On the curve, its real reserves. In a pool, the active range's virtual reserves derived from the swap's liquidity and price - what the next trade is priced against. `reservesSource` says which: `curve` or `pool_virtual`. |
| `sqrtPriceX96` / `tick` | Pool trades only: the pool's price after the swap, in Uniswap's own units. |
| `block` / `timestamp` / `tx` / `logIndex` | Where it happened. `block` and `logIndex` together order events exactly. |
### Liquidity
```json
{
"type": "liquidity",
"venue": "uniswap_v4",
"action": "add",
"token": "0x…",
"symbol": "CONTRA",
"protocol": "PONS_V2",
"quoteToken": "0x0000000000000000000000000000000000000000",
"quoteSymbol": "ETH",
"origin": "0x…",
"caller": "0x…",
"liquidityDelta": "184467440737095516",
"tickLower": -887220,
"tickUpper": 887220,
"block": 57708240,
"timestamp": 1788779311,
"tx": "0x…",
"logIndex": 3
}
```
| Field | Meaning |
|---|---|
| `action` | `add` or `remove`. |
| `liquidityDelta` | Liquidity units added (positive) or removed (negative), in Uniswap's own measure. |
| `tokenAmount` / `quoteAmount` | V3 pools report how much of each asset moved; v4 pools report only the liquidity delta, so these are absent there. |
| `tickLower` / `tickUpper` | The position's price range. A full-range position on a Pons pool is the one Pons itself created at graduation. |
### Following wallets
Pass `wallets=` in the URL, or send `subscribeWallets`, and every trade or liquidity change those wallets **send** comes through, whatever token it's on, with the token attributed. The match is on `origin` (the transaction sender) or `caller`, so a wallet trading through a router, ours or Pons's site, is still caught. Use it for copy-trading, whale watching, or following a creator's own wallet.
One limit: a wallet's swap in a Uniswap v4 pool can only be attributed if the stream has seen that pool since it started or someone follows the token. Curve trades are always attributed. If you care about one graduated token, follow the token as well.
### Control messages
Send JSON text frames to change what you follow; the reply is a `subscribed` message with the full current lists:
```json
{ "subscribe": ["0x…", "0x…"], "unsubscribe": ["0x…"] }
{ "subscribeWallets": ["0x…"], "unsubscribeWallets": ["0x…"] }
{ "ping": true }
```
Add `&pretty=1` to the URL to have every frame indented and newline-terminated, handy for watching a tape with curl.
`{"type":"lagged","dropped":n}` means your client read too slowly and `n` events were skipped rather than queued without bound. Process faster or follow fewer tokens.
## Notes
- **Filtering is required.** The chain sees over a million trades a day. You name up to 50 tokens and 50 wallets per connection, and only their events come through. There is no "everything" mode.
- **Live.** Events are pushed from a node subscription and arrive within about a second of the block, same as the [launch feed](/live-launches).
- **Pons and Uniswap tokens supported.** v2 (curve, then Uniswap v4) and v1 (Uniswap V3). A token that isn't a Pons launch is reported in `unknown`.
- **Limits.** 50 tokens and 50 wallets per connection, one connection per IP. Follow more on the one socket rather than opening a second; ask in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) if you need more.
---
# Create a Token on Pons (Robinhood Chain)
> Launch a token on the Pons v2 bonding curve on Robinhood Chain with one signed transaction - metadata, image, creator tax, dev buy and any quote asset from ETH to NVDA.
# Create Token
```
POST https://api.shrine.trade/rh/api/create-token
```
Builds the Pons launch transaction for you: metadata, launch config, the pinned economics hash, and — if you want one — a dev buy in the same transaction. You sign and send it; the token and its bonding curve exist as soon as it confirms.
shrine.trade takes **no fee** on creation. The transaction value covers Pons's own launch fee (plus your dev buy, if any).
Launching is **Pons v2 only**: the bonding curve, graduating to Uniswap v4 on its own once it sells out. Pons v1 launches are not offered (v1 is superseded), though v1 tokens still [buy and sell](/local-trade) here.
> **About the private key field.** **The key never leaves your machine.** It signs locally; only the signed transaction is handed to `/api/send` to broadcast. The key itself is never sent to shrine.trade.
Uploading happens first: **Build waits for the image to finish pinning to IPFS**, so the token's `logo` is set the moment it launches rather than being patched in afterwards.
## Example
**JavaScript**
```js
const { readFileSync } = require("node:fs");
const { Wallet } = require("ethers");
// ─── your token ───────────────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const NAME = "Grene";
const SYMBOL = "GRENE";
const IMAGE = "./logo.png"; // image file, next to this script
const DESCRIPTION = "the greenest coin on Robinhood Chain";
// ─── links — all optional, "" to leave one out ────────
const TWITTER = "https://x.com/grene";
const TELEGRAM = "";
const DISCORD = "";
const WEBSITE = "https://grene.example";
const FARCASTER = "";
// ─── economics ────────────────────────────────────────
const PAIR_TOKEN = "ETH"; // what buyers pay with: ETH, USDG, NVDA, MSTR, …
const DEV_BUY = ""; // your own opening buy, in ETH. "" = none
const CREATOR_TAX = 0; // % of every trade you earn, 0–10. Fixed at launch
const FEE_RECIPIENT = ""; // where your fees go. "" = the launching wallet
const EXEMPTIONS = []; // wallets that skip the opening snipe tax.
// Max 32, or 31 when you take a dev buy
const BUYBACK = false; // route creator fees into buybacks
// ──────────────────────────────────────────────────────
async function main() {
const wallet = new Wallet(PRIVATE_KEY); // signs only; no node needed
// 1. Upload the image. We pin it to IPFS and hand back a hosted URL.
const form = new FormData();
form.append("file", new Blob([readFileSync(IMAGE)], { type: "image/png" }), "logo.png");
form.append("name", NAME);
form.append("symbol", SYMBOL);
form.append("description", DESCRIPTION);
const up = await fetch("https://api.shrine.trade/rh/api/upload-image", {
method: "POST",
body: form,
});
const uploaded = await up.json();
if (uploaded.error) throw new Error(`${uploaded.error}: ${uploaded.message}`);
console.log("image pinned:", uploaded.image);
// 2. Ask shrine.trade to build the launch transaction.
const res = await fetch("https://api.shrine.trade/rh/api/create-token", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
name: NAME,
symbol: SYMBOL,
description: DESCRIPTION,
logo: uploaded.image,
socials: { twitter: TWITTER, telegram: TELEGRAM, discord: DISCORD,
website: WEBSITE, farcaster: FARCASTER },
pairToken: PAIR_TOKEN,
creatorTaxBps: CREATOR_TAX * 100,
buybackEnabled: BUYBACK,
// Left out entirely when empty - the API picks sane defaults.
...(DEV_BUY ? { initialBuy: DEV_BUY } : {}),
...(EXEMPTIONS.length ? { snipeTaxExemptions: EXEMPTIONS } : {}),
...(FEE_RECIPIENT ? { creatorFeeRecipient: FEE_RECIPIENT } : {}),
from: wallet.address,
}),
});
const body = await res.json();
if (body.error) throw new Error(`${body.error}: ${body.message}`);
console.log("launch fee", body.launchFeeFormatted, "ETH");
// 3. Sign locally - your private key never leaves this script.
// 4. Hand each signed transaction to the API to broadcast, in order (a dev
// buy in an ERC-20 needs an approval first). No RPC of your own needed.
let launched;
for (const tx of body.txs) {
const signed = await wallet.signTransaction({
to: tx.to, data: tx.data, value: BigInt(tx.value), gasLimit: BigInt(tx.gas),
maxFeePerGas: BigInt(tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas),
nonce: tx.nonce, chainId: tx.chainId, type: 2,
});
const sent = await (await fetch("https://api.shrine.trade/rh/api/send", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ signedTx: signed }),
})).json();
if (sent.error) throw new Error(`${sent.error}: ${sent.message}`);
if (sent.status !== "landed") throw new Error(`${tx.description} ${sent.status}: ${sent.explorer}`);
if (sent.token) launched = sent;
}
// 5. The relay reads the new addresses out of the TokenLaunched event for you.
console.log("token launched:", launched.token);
console.log("curve:", launched.curve);
console.log("tx:", launched.explorer);
}
main();
```
Save it as `launch.js`, put your image next to it as `logo.png`, then:
```bash
npm install ethers
node launch.js
```
**Python**
```python
import requests
from eth_account import Account
# ─── your token ───────────────────────────────────────
PRIVATE_KEY = "0xYOUR_PRIVATE_KEY"
NAME = "Grene"
SYMBOL = "GRENE"
IMAGE = "./logo.png" # image file, next to this script
DESCRIPTION = "the greenest coin on Robinhood Chain"
# ─── links - all optional, "" to leave one out ────────
TWITTER = "https://x.com/grene"
TELEGRAM = ""
DISCORD = ""
WEBSITE = "https://grene.example"
FARCASTER = ""
# ─── economics ────────────────────────────────────────
PAIR_TOKEN = "ETH" # what buyers pay with: ETH, USDG, NVDA, MSTR, …
DEV_BUY = "" # your own opening buy, in ETH. "" = none
CREATOR_TAX = 0 # % of every trade you earn, 0-10. Fixed at launch
FEE_RECIPIENT = "" # where your fees go. "" = the launching wallet
EXEMPTIONS = [] # wallets that skip the opening snipe tax.
# Max 32, or 31 when you take a dev buy
BUYBACK = False # route creator fees into buybacks
# ──────────────────────────────────────────────────────
account = Account.from_key(PRIVATE_KEY) # signs only; no node needed
# 1. Upload the image. We pin it to IPFS and hand back a hosted URL.
with open(IMAGE, "rb") as fh:
up = requests.post(
"https://api.shrine.trade/rh/api/upload-image",
files={"file": ("logo.png", fh, "image/png")},
data={"name": NAME, "symbol": SYMBOL, "description": DESCRIPTION},
).json()
if "error" in up:
raise SystemExit(f"{up['error']}: {up['message']}")
print("image pinned:", up["image"])
# 2. Ask shrine.trade to build the launch transaction.
res = requests.post(
"https://api.shrine.trade/rh/api/create-token",
json={
"name": NAME,
"symbol": SYMBOL,
"description": DESCRIPTION,
"logo": up["image"],
"socials": {"twitter": TWITTER, "telegram": TELEGRAM, "discord": DISCORD,
"website": WEBSITE, "farcaster": FARCASTER},
"pairToken": PAIR_TOKEN,
"creatorTaxBps": CREATOR_TAX * 100,
"buybackEnabled": BUYBACK,
# Left out entirely when empty - the API picks sane defaults.
**({"initialBuy": DEV_BUY} if DEV_BUY else {}),
**({"snipeTaxExemptions": EXEMPTIONS} if EXEMPTIONS else {}),
**({"creatorFeeRecipient": FEE_RECIPIENT} if FEE_RECIPIENT else {}),
"from": account.address,
},
)
body = res.json()
if "error" in body:
raise SystemExit(f"{body['error']}: {body['message']}")
print("launch fee", body["launchFeeFormatted"], "ETH")
# 3. Sign locally - your private key never leaves this script.
# 4. Hand each signed transaction to the API to broadcast. A dev buy in an ERC-20 needs an approval
# first, so send whatever comes back, in order.
for tx in body["txs"]:
signed = Account.sign_transaction(
{
"to": tx["to"],
"data": tx["data"],
"value": int(tx["value"]), # launch fee (+ dev buy) in wei
"gas": tx["gas"],
"maxFeePerGas": int(tx["maxFeePerGas"]),
"maxPriorityFeePerGas": int(tx["maxPriorityFeePerGas"]),
"nonce": tx["nonce"],
"chainId": tx["chainId"],
"type": tx["type"],
},
account.key,
)
sent = requests.post("https://api.shrine.trade/rh/api/send", json={"signedTx": "0x" + signed.raw_transaction.hex().removeprefix("0x")}).json()
if "error" in sent:
raise SystemExit(f"{sent['error']}: {sent['message']}")
if sent["status"] != "landed":
raise SystemExit(f"{tx['description']} {sent['status']}: {sent['explorer']}")
if sent.get("token"):
launched = sent
# 5. The relay reads the new addresses out of the TokenLaunched event for you.
print("token launched:", launched["token"])
print("curve:", launched["curve"])
print("tx:", launched["explorer"])
```
Save it as `launch.py`, put your image next to it as `logo.png`, then:
```bash
pip install eth-account requests
python launch.py
```
## Supported quote assets
A Pons token can be priced in ETH or in one of Robinhood Chain's tokenised
assets. Pass the **ticker** as `pairToken` - `"MSTR"`, `"USDG"`, `"NVDA"` - or
the address if you prefer; both work, and tickers are case-insensitive.
Whatever you pick is fixed at launch and is what buyers pay with. The launch
fee itself is always ETH.
| Ticker | Decimals | Address |
|---|---|---|
| `ETH` | 18 | native ETH — no address |
| `USDG` | 6 | `0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168` |
| `SPCX` | 18 | `0x4a0E65A3EcceC6dBe60AE065F2e7bb85Fae35eEa` |
| `NVDA` | 18 | `0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC` |
| `RDDT` | 18 | `0x05b37Fb53A299a1b874A619e1c4C404D52C36F4C` |
| `AMZN` | 18 | `0x12f190a9F9d7D37a250758b26824B97CE941bF54` |
| `DJT` | 18 | `0x1D11f0496982706C5e14A514D4E79F2e6BdE4516` |
| `TTWO` | 18 | `0x5e81213613b6B86EaB4c6c50d718d34359459786` |
| `GME` | 18 | `0x1b0E319c6A659F002271B69dB8A7df2F911c153E` |
| `SPY` | 18 | `0x117cc2133c37B721F49dE2A7a74833232B3B4C0C` |
| `AAPL` | 18 | `0xaF3D76f1834A1d425780943C99Ea8A608f8a93f9` |
| `MSFT` | 18 | `0xe93237C50D904957Cf27E7B1133b510C669c2e74` |
| `COIN` | 18 | `0x6330D8C3178a418788dF01a47479c0ce7CCF450b` |
| `TSLA` | 18 | `0x322F0929c4625eD5bAd873c95208D54E1c003b2d` |
| `QQQ` | 18 | `0xD5f3879160bc7c32ebb4dC785F8a4F505888de68` |
| `PLTR` | 18 | `0x894E1EC2D74FFE5AEF8Dc8A9e84686acCB964F2A` |
| `cbBTC` | 8 | `0xCEC185eB182c47d1bA1EFc84e6959e18cd620Be4` |
| `GOOGL` | 18 | `0x2e0847E8910a9732eB3fb1bb4b70a580ADAD4FE3` |
| `GLD` | 18 | `0xC9a981FEE1F9DEc688bb123ccDeCc63D0deBFC4e` |
| `META` | 18 | `0xc0D6457C16Cc70d6790Dd43521C899C87ce02f35` |
| `CRCL` | 18 | `0xdF0992E440dD0be65BD8439b609d6D4366bf1CB5` |
| `COST` | 18 | `0x4EA005168D7F09a7A0Ba9D1DEf21a479950E44C2` |
| `MSTR` | 18 | `0xec262a75e413fAfD0dF80480274532C79D42da09` |
| `AMD` | 18 | `0x86923f96303D656E4aa86D9d42D1e57ad2023fdC` |
| `SNDK` | 18 | `0xB90A19fF0Af67f7779afF50A882A9CfF42446400` |
| `BB` | 18 | `0x48E39E56aCdbA37b09020C0b734A613C9a2f100A` |
| `MU` | 18 | `0xfF080c8ce2E5feadaCa0Da81314Ae59D232d4afD` |
| `HIMS` | 18 | `0xCceE82fE024c36fA15E1005edE3E9e4787e23D09` |
Pons scales each curve's economics into its quote asset, so the graduation
threshold differs per asset - 4.2 ETH, 8090 USDG, 41.6 NVDA. `create-token`
returns the one that applies as `graduationThreshold`.
## Request
| Field | Type | Description |
|---|---|---|
| `name` | string | Token name. Not unique — always identify tokens by address. |
| `symbol` | string | Ticker. |
| `description` | string | Project description. |
| `socials` | object | `{ twitter, telegram, discord, website, farcaster }` — any may be `""`. |
| `logo` | string, optional | Hosted image URL. The scripts above pin your image file to IPFS and fill this in for you. |
| `creatorFeeRecipient` | address, optional | Where creator fees are paid. Defaults to `from`. |
| `snipeTaxExemptions` | string[], optional | Up to **32** wallets that skip the opening snipe tax (**31** with an `initialBuy` - the dev-buy wallet takes one slot on-chain). In the form above, separate them with commas (spaces and new lines work too). Your own sniper, a partner, whoever. Fixed at launch and never changeable. |
| `creatorTaxBps` | number, optional | Extra creator tax on every trade, in basis points. Capped by Pons at 10%; immutable after launch. Default `0`. |
| `buybackEnabled` | boolean, optional | Route the creator's fee share into token buybacks (vested). Default `false`. |
| `launchConfigId` | number, optional | Which Pons launch config to use. Only config `0` exists today and it is the default, so leave it out. |
| `pairToken` | string, optional | What the curve is priced in. `"ETH"` (default) or the address of an approved quote asset — USDG, NVDA, SPCX, GME, cbBTC and the rest of Robinhood Chain's tokenised assets. An asset Pons hasn't approved returns `pair_token_not_approved`. |
| `initialBuy` | decimal string, optional | Your own opening buy, in the same transaction, **in ETH** whatever the token is priced in. On a USDG or NVDA launch the API works out how much of that asset the ETH buys (`initialBuy` in the response, `initialBuyEth` echoes your figure); your wallet's own holding of the asset is used first and only the shortfall is swapped, with a `buy_quote_with_eth` transaction in front. Your wallet is exempt from the snipe tax on this buy, and there is no slippage to set on the buy itself: it runs inside the transaction that creates the curve, so nobody can trade ahead of it. |
| `salt` | 32-byte hex, optional | CREATE2 salt. Lets you know the token address before launching. Random if omitted. |
| `from` | address | Your wallet (deployer). |
### What a launch costs
Two numbers matter, and they are not the same:
| | plain launch | + 0.001 dev buy |
|---|---|---|
| **ETH you must hold** | ~0.0026 | ~0.0036 |
| **ETH actually spent** | ~0.0019 | ~0.0029 |
The gap is not a fee. A node reserves `gasLimit x maxFeePerGas` for the whole
transaction before it runs, and neither figure is what gets used: the limit is
padded 20% above the estimate, and the price is a ceiling set at 1.25x the
current base fee, not the rate. Unused gas is never taken - you get the
difference back in the same block.
So **budget around 0.003 ETH on Robinhood Chain** for a plain launch, plus your
dev buy. A wallet holding exactly the launch fee plus the expected gas will be
rejected, which is not a bug. The ceiling is deliberately tight: if the base fee
climbs more than 25% between quoting and sending, the node rejects the
transaction and you request a fresh one.
Of what is spent, `0.0005` is Pons's launch fee and the rest is gas. shrine.trade
takes nothing on creation.
## Response
A real one, for a launch with a 0.001 ETH dev buy:
```json
{
"txs": [
{
"to": "0xe33E9E479dF8802cb0866d5d05258bEc4cF62948",
"data": "0x\u2026",
"value": "1500000000000000",
"gas": 4600821,
"maxFeePerGas": "581672501",
"maxPriorityFeePerGas": "1",
"nonce": 5,
"chainId": 4663,
"type": 2,
"description": "launch_and_buy"
}
],
"launchFee": "500000000000000",
"launchFeeFormatted": "0.0005",
"expectedEconomics": "0xa9fc75d4203a33fe660e8fa32c74c3aa41c1fda4bf23d3a39b6bc22a1f8b1ca7",
"launchConfigId": 0,
"pairToken": "ETH",
"pairTokenSymbol": "ETH",
"pairTokenDecimals": 18,
"graduationThreshold": "4200000000000000000",
"graduationThresholdFormatted": "4.2",
"salt": "0x\u2026",
"creatorFeeRecipient": "0x7b444D22f099Fd238210161791dE26d16c3cEdf2",
"creatorTaxBps": 100,
"snipeTaxExemptions": [],
"initialBuy": "1000000000000000",
"expectedTokensOut": "582993253935204464062630",
"minTokensOut": "553843591238444240859498"
}
```
- `txs` - sign and send in order. Usually one: `launch` (plain) or `launch_and_buy` (with a dev buy). A dev buy in an ERC-20 pair token can put `buy_quote_with_eth` (if the wallet is short of it) and `approve_quote` in front.
- `launchFee` - Pons's fee, always ETH, read from the factory at request time. The transaction's `value` is this plus an ETH dev buy.
- `pairToken`, `pairTokenSymbol`, `pairTokenDecimals` - what the curve is priced in. `graduationThreshold` is how much of it the curve must take in before it graduates; Pons scales it per asset.
- `expectedEconomics` - the launch config's economics hash, pinned into the transaction so the terms can't change between quote and landing.
- `salt` - yours, or the random one we picked. `creatorFeeRecipient`, `creatorTaxBps`, `snipeTaxExemptions` echo what the launch will use.
- `initialBuy`, `expectedTokensOut`, `minTokensOut` - the dev buy in the pair token's base units and what it returns. Only with an `initialBuy`. On an ERC-20-priced launch `initialBuyEth` is the ETH you gave and `initialBuy` what it became.
- `quoteSwap` - only when an ERC-20 dev buy needed a swap in front: what it buys, the ETH quoted and carried, and the pools.
---
# Claim Pons Creator Fees
> See what your Pons launches have earned on Robinhood Chain and claim it to your wallet in two calls.
# Claim Creator Fees
If you launched a token, every trade on it earns you a share: what's left of the trading fee after Pons's 30%, plus all of your creator tax. It isn't sent to you automatically - you claim it. Two endpoints, free and keyless.
## What's waiting
```
GET https://api.shrine.trade/rh/api/fees/{wallet}?token={yourToken}
```
```json
{
"wallet": "0xfc1FFd8b43631Ba04a347FAF930fA1Dc547BFcb2",
"escrow": "0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e",
"asset": "USDG",
"decimals": 6,
"claimable": "39388954",
"claimableFormatted": "39.388954",
"unswept": "1935146",
"unsweptFormatted": "1.935146",
"total": "41324100",
"totalFormatted": "41.3241",
"hasClaimable": true
}
```
`total` is what a claim pays out right now, in the launch's quote asset (`asset`). `claimable` is already in Pons's escrow; `unswept` is still on the token's curve and gets collected as part of the claim. `hasClaimable: false` means don't bother - you'd only pay gas.
Leave `token` out to see just the ETH escrow balance.
## Claim it
```
POST https://api.shrine.trade/rh/api/claim-fees
```
Send `{ "from": "0x…", "token": "0x…" }` - your wallet and your launched token - and you get back the transactions that pay everything out to your wallet: one or two, send them in order.
**JavaScript**
```js
const { Wallet } = require("ethers");
// ─── change these ─────────────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const TOKEN = "0xYOUR_LAUNCHED_TOKEN";
// ──────────────────────────────────────────────────────
async function main() {
const wallet = new Wallet(PRIVATE_KEY); // signs only; no node needed
// 1. Ask shrine.trade to build the claim.
const res = await fetch("https://api.shrine.trade/rh/api/claim-fees", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ from: wallet.address, token: TOKEN }),
});
const data = await res.json();
if (data.error) throw new Error(`${data.error}: ${data.message}`);
console.log("claiming", data.claimingFormatted, data.asset);
// 2. Sign locally and send, in order - the key never leaves this script.
for (const tx of data.txs) {
const signed = await wallet.signTransaction({
to: tx.to, data: tx.data, value: BigInt(tx.value), gasLimit: BigInt(tx.gas),
maxFeePerGas: BigInt(tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas),
nonce: tx.nonce, chainId: tx.chainId, type: 2,
});
const sent = await (await fetch("https://api.shrine.trade/rh/api/send", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ signedTx: signed }),
})).json();
if (sent.error) throw new Error(`${sent.error}: ${sent.message}`);
if (sent.status !== "landed") throw new Error(`${tx.description} ${sent.status}: ${sent.explorer}`);
console.log(tx.description + ": " + sent.explorer);
}
}
main();
```
Save it as `claim.js`, then:
```bash
npm install ethers
node claim.js
```
**Python**
```python
import requests
from eth_account import Account
# ─── change these ─────────────────────────────────────
PRIVATE_KEY = "0xYOUR_PRIVATE_KEY"
TOKEN = "0xYOUR_LAUNCHED_TOKEN"
# ──────────────────────────────────────────────────────
account = Account.from_key(PRIVATE_KEY) # signs only; no node needed
# 1. Ask shrine.trade to build the claim.
data = requests.post(
"https://api.shrine.trade/rh/api/claim-fees",
json={"from": account.address, "token": TOKEN},
).json()
if "error" in data:
raise SystemExit(f"{data['error']}: {data['message']}")
print("claiming", data["claimingFormatted"], data["asset"])
# 2. Sign locally and send, in order - the key never leaves this script.
for tx in data["txs"]:
signed = Account.sign_transaction(
{
"to": tx["to"],
"data": tx["data"],
"value": int(tx["value"]),
"gas": tx["gas"],
"maxFeePerGas": int(tx["maxFeePerGas"]),
"maxPriorityFeePerGas": int(tx["maxPriorityFeePerGas"]),
"nonce": tx["nonce"],
"chainId": tx["chainId"],
},
account.key,
)
sent = requests.post("https://api.shrine.trade/rh/api/send", json={"signedTx": "0x" + signed.raw_transaction.hex().removeprefix("0x")}).json()
if "error" in sent:
raise SystemExit(f"{sent['error']}: {sent['message']}")
if sent["status"] != "landed":
raise SystemExit(f"{tx['description']} {sent['status']}: {sent['explorer']}")
print(tx["description"] + ":", sent["explorer"])
```
Save it as `claim.py`, then:
```bash
pip install eth-account requests
python claim.py
```
## Notes
- **Only the `creatorFeeRecipient` can claim** - your wallet unless you set another at launch. `/api/token/{address}` shows which.
- **Launches priced in an ERC-20** (USDG, NVDA, …) pay out in that asset. Pass the launched token and the API works out the rest.
- **Launched with buybacks on?** Then Pons collects the curve's fees for you on its own schedule, and the claim covers what has arrived in the escrow so far. `nothing_to_claim` tells you when the rest is still on the curve.
- These are Pons's fees to you as a creator. Our 0.5% is separate and never touches this escrow.
---
# API Errors
> Every error code the Pons Trading API returns, grouped by endpoint, with what to do about each.
# Errors
Errors are JSON with a stable `error` code and a plain-language `message`. Check the code in your script and show the message to a human.
```json
{ "error": "insufficient_balance", "message": "wallet holds 0 tokens, tried to sell 1000" }
```
## Your request
| Code | HTTP | Meaning |
|---|---|---|
| `invalid_request` | 400 | A required field is missing or malformed. |
| `invalid_address` | 400 | `token`, `from` or another address field is not a valid address. |
| `invalid_amount` | 400 | `amount` is zero, negative, not a number, or a percentage on a buy. |
| `invalid_action` | 400 | `action` is not `"buy"` or `"sell"`. |
| `token_not_found` | 404 | The address is not a Pons token on Robinhood Chain, v2 or v1. Check you copied the token, not a pool or a curve. |
## Trading
| Code | HTTP | Meaning |
|---|---|---|
| `insufficient_funds` | 400 | Not enough ETH in `from` for the trade plus gas. The message says what the wallet holds and what it needs. It has to be **ETH on Robinhood Chain** - ETH on Ethereum mainnet or another L2 must be bridged first, and holding USDG, NVDA or another asset does not pay for gas. |
| `insufficient_balance` | 400 | Selling more tokens than the wallet holds. `"100%"` sells everything. |
| `slippage_exceeded` | 400 | The price moved between the quote and now by more than `slippage`. Retry, or raise `slippage` on a fast-moving token. |
| `no_route` | 400 | The token is priced in an asset your wallet doesn't hold enough of, and no Uniswap pool can buy that asset with ETH. Acquire it yourself, then retry. With `toEth`: no pool can turn the proceeds into ETH - sell without it and keep the asset. |
| `amount_too_small` | 400 | So little that the swap would return nothing. Increase `amount`. |
| `graduating` | 409 | The token is moving from its curve to its Uniswap pool right now. Retry in a minute; it then trades on Uniswap automatically. |
| `graduated` | 400 | The curve closed while your trade was being built. Retry; the trade now routes to Uniswap. |
| `curve_closed` | 409 | The curve has sold out and is about to graduate; sells reopen once the Uniswap pool exists. |
| `deadline_passed` | 400 | A Uniswap transaction is only valid for 10 minutes after it is built. Request a fresh one. |
| `insufficient_allowance` | 400 | An approval the trade needs is missing. Retry; the response will include it. |
## Sending
| Code | HTTP | Meaning |
|---|---|---|
| `not_ours` | 400 | `/api/send` only broadcasts transactions this API built: Pons and Uniswap trades, launches, approvals and claims. Anything else is refused before it reaches the chain. |
| `wrong_chain` | 400 | The signed transaction is for another chain id; this API relays Robinhood Chain (4663). |
## Creating a token
| Code | HTTP | Meaning |
|---|---|---|
| `invalid_pair_token` | 400 | `pairToken` is neither an address nor a ticker we know. The message lists the tickers. |
| `pair_token_not_approved` | 400 | Pons doesn't accept that asset as a quote. Use ETH or one of the [approved assets](/create-token#supported-quote-assets). |
| `too_many_exemptions` | 400 | More than 32 `snipeTaxExemptions` (31 with an `initialBuy`). |
| `creator_tax_too_high` | 400 | `creatorTaxBps` is above the maximum Pons allows (10%). |
| `invalid_salt` | 400 | `salt` is not 32 bytes of hex. |
| `launch_config_disabled` | 400 | The requested `launchConfigId` is not available. Leave it out. |
| `launcher_not_permitted` | 403 | Pons currently only lets allow-listed wallets launch, and `from` isn't one. |
| `launch_disabled` | 503 | Pons has paused launching. Try later. |
| `missing_file` | 400 | The image upload had no `file` field. |
| `not_an_image` | 400 | The uploaded file is not an image. |
| `file_too_large` | 400 | The image is over 5 MB. |
| `invalid_upload` | 400 | The upload could not be read. |
## Creator fees
| Code | HTTP | Meaning |
|---|---|---|
| `nothing_to_claim` | 400 | Nothing is waiting for this wallet. If the message names an amount, that much has been earned but is still being collected by Pons; check back later. |
| `sweep_requires_operator` | 400 | This launch's fees are collected by Pons on its own schedule, not by you. Nothing to do - claim again once they arrive. |
## Limits
| Code | HTTP | Meaning |
|---|---|---|
| `rate_limited` | 429 | More than 3 requests per second from your IP to one endpoint. Wait a second and retry (`Retry-After: 1`). |
| `too_many_connections` | 429 | Your IP already holds a socket of this kind: one launch feed and one data stream per address. Close the other one first. |
## Everything else
| Code | HTTP | Meaning |
|---|---|---|
| `revert` | 400 | The transaction would fail on-chain for a reason not listed above; `message` says what the contract reported. |
| `rpc_error` | 502 | Robinhood Chain could not be reached. Retry. |
A transaction can still fail after you send it if the price moves past your slippage. `minOut` is your protection; a failed trade costs only gas.
---
# Frequently Asked Questions
> Answers about supported launchpads, fees, private-key safety, amounts, slippage, the live feed and rate limits for the Robinhood Chain Trading API.
# Frequently asked questions
### Which platforms are supported?
Tokens from every launchpad on Robinhood Chain: **Pons** (v2 on its curve and in its Uniswap v4 pool after graduation, v1 in the Uniswap V3 pool it launched into), **Flap** (its curve, then its Uniswap v2 pair), **Bags** (its curve, then its Uniswap v4 pool), and **o1**, **Pools.trade**, **letscash**, **PAIR** and **Long** in the Uniswap v4 pools they launch into - plus any other token with a Uniswap pool here, such as the tokenised stocks (NVDA, TSLA, USDG …) themselves. One endpoint, the API works out where the token lives. **Varo** and **Virtuals** launches are on the live feed; trading them is coming. Launching is Pons v2 only.
### Do I need an API key or an account?
No. Every endpoint is open. The only limits are 3 requests per second per IP per endpoint, and one open socket per stream per IP: one launch feed and one data stream.
### Is my private key safe?
It never leaves your machine. The API returns unsigned transactions; your script signs them with your own key and posts only the signed bytes to `/api/send`, which broadcasts them and can do nothing else with them. shrine.trade never sees the key, never holds funds, and cannot move anything on your behalf.
### What does it cost?
0.5% of each buy and sell on Pons and on every Uniswap pool, taken in the asset the trade settles in; trades on the Flap and Bags bonding curves carry no shrine fee for now. Reads, the live feed and launching are free. Each launchpad's own curve fee, hook fee, creator tax or opening snipe tax applies on top, and gas is a few cents. See [Fees](/fees).
### Which currency is `amount` in?
On a buy, always ETH, whatever the token is priced in - the API converts into USDG, NVDA or whichever asset the token uses. On a sell, tokens, or a percentage of your balance like `"50%"`. Add `toEth: true` to a sell to receive ETH instead of the quote asset.
### Why did my buy come back as three transactions?
The token is priced in a tokenised asset you didn't hold. The first transaction buys exactly the missing amount with ETH, the second is a one-time approval, the third is the buy. Send them in order; the script does. Next time it is one.
### Are trades front-run protected?
Yes, in three ways. Robinhood Chain is an Arbitrum-based L2 with a single sequencer and no public mempool: your transaction is not visible to anyone before it is ordered, so there is nothing for a bot to jump ahead of. Every buy and sell carries a minimum (`minOut`, from your slippage) that the contract enforces, so a trade that would fill worse than quoted fails instead. And a dev buy on a launch runs inside the transaction that creates the curve, so it cannot be sniped. What remains is ordinary price movement between your quote and your transaction landing, which the slippage setting covers.
### What is slippage and what should I set?
How far the price may move against you between the quote and the transaction landing, in percent. Default 5. Raise it on fast-moving tokens if you see `slippage_exceeded`; lower it on quiet ones. It is enforced on-chain, so a trade that would exceed it fails instead of filling badly.
### Why is the wallet rejected when it holds enough for the trade?
Because the node reserves gas at the limit price before running the transaction. The API tells you the exact amount to hold. Keep 0.01 ETH extra and this never comes up. Details in [gas and fees](/tutorials/robinhood-chain-gas-and-fees).
### When can I buy a new launch?
Wait about five seconds. Pons taxes opening buys from near 99% down to zero. `GET /api/token/{address}?recipient=YOUR_WALLET` returns `snipeTaxBps`; buy when it is `0`. The [sniper tutorial](/tutorials/snipe-pons-launches) does exactly that.
### Can I get every trade, not just launches?
Yes. The [Advanced Data Stream](/advanced-stream) pushes every buy, sell, liquidity add and liquidity removal for the tokens you name, on the curve and in Uniswap after graduation. It needs a free key: join the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) and ask. It is live and keeps no history.
### Is the live feed really free?
Yes. `wss://api.shrine.trade/rh/api/launches/ws` streams every Pons launch, graduation and new Uniswap v4 pool. No key, one connection per IP. There is no history endpoint; it is a live feed.
### Where are my creator fees?
They pile up on your token's curve until swept into Pons's escrow, then you claim them. `/api/fees/{wallet}?token=YOUR_TOKEN` shows both parts and `/api/claim-fees` builds the transactions. See [Claim Creator Fees](/creator-fees).
### Can AI coding tools read these docs?
Yes. [llms.txt](/llms.txt) is the index and [llms-full.txt](/llms-full.txt) is every page as one text file, both regenerated on every release.
### Where do I get help?
The [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) for support; releases are announced on [X](https://x.com/shrinetrade).
---
# Fees
> shrine.trade charges 0.5% per trade on Pons and Uniswap tokens on Robinhood Chain and nothing on the Flap and Bags curves for now; launching and reading are free. Each launchpad's own fees apply on top.
# Fees
## shrine.trade
| Action | Fee |
|---|---|
| Buy | **0.5%** |
| Sell | **0.5%** |
| Create token | **Free** (Pons's launch fee applies — see below) |
| [⚡ Lightspeed](/lightspeed/overview) buy or sell | **0.75%** |
| Token info | Free |
The 0.5% is taken in the asset the trade settles in - ETH on an ETH-priced token, USDG or DJT on a stock-priced one, and so on - on the Pons curve and in every Uniswap pool alike, whichever launchpad opened the pool (Bags, o1, Pools.trade, letscash, PAIR, Long). Trades on the **Flap** and **Bags bonding curves** carry no shrine fee for now. The fee excludes each launchpad's own charges - Pons's trading fee, creator tax and opening snipe tax, Flap's curve fee and token taxes, Bags's 2% curve fee, a hook's fee (o1's opening anti-snipe fee, letscash's 3%) - and Uniswap pool fees. The response's `curveFeeBps` and `creatorTaxBps` report the venue's own cut on that trade.
[Lightspeed](/lightspeed/overview) trades pay 0.75% instead: the trade is built, signed and sent from our box next to Robinhood, from a wallet only your API key can open. On a Pons curve the fee is taken by the Lightspeed router, in a Uniswap pool by the Universal Router's fee step, always in the asset the trade settles in. Withdrawing from a Lightspeed wallet is free.
## Network
Robinhood Chain gas is paid in ETH and is small (100 ms blocks, cheap L2 execution). The API fills `gas` and the EIP-1559 fee fields for you.
## Lower fees
**The 0.5% is negotiable.** Trading real size, building a bot or app on top of
this, or need an endpoint that isn't here yet? Message me on
[Telegram](https://t.me/+nEqAowTK8BZhZjFk) and we'll sort out a rate that works.
---
# ⚡ Lightspeed Account: Hosted Wallet, Balance and Withdraw
> Create your Lightspeed wallet on Robinhood Chain, fund it with ETH, read its balance and withdraw. The private key is encrypted under your API key: without it nobody, including us, can open the wallet.
Advanced
Fastest-path route for snipers, from a wallet only your API key can open. To sign on your own machine instead, use the Buy & Sell API.
# Account
Your Lightspeed wallet is generated on our server, and only you can open it. The private key is encrypted under a key derived from your API key; we keep the ciphertext and a hash of the API key for lookup, never the API key itself. Without the API key the wallet cannot be decrypted, by us or by anyone who took a copy of our database. It is decrypted for the milliseconds a request needs it, from the API key that request carries. You hold custody.
## From code
```bash
curl -X POST https://rh-lightspeed.shrine.trade/api/lightspeed/account
```
```json
{ "address": "0x9985…a92f", "api_key": "ls_…", "private_key": "0x…" }
```
Shown once. Fund `address` with ETH on Robinhood Chain: that pays for the buys, for gas (tiny, a few millionths of an ETH per trade) and for the 0.75% fee. A token priced in USDG, NVDA or another asset is still bought with ETH; the swap into the asset is part of the trade.
Up to 20 accounts per hour per IP address.
:::caution Your wallet, your key
The API key is the key to the wallet. Whoever holds it, or the private key, controls the funds; we cannot recover a lost API key, so keep the private key as your backup. Keep only what you trade with, withdraw profits, and treat both like a password. If either leaks, create a new account and move the balance.
:::
## Balance
The key goes in the `x-api-key` header. Add `?token=0x…` to read one token's balance as well.
```bash
curl "https://rh-lightspeed.shrine.trade/api/lightspeed/account?token=0x94FD…" -H "x-api-key: ls_…"
```
```json
{
"address": "0x9985…a92f",
"eth": "0.004812", "wei": "4812000000000000",
"nonce": 7,
"token": { "address": "0x94FD…", "balance": "1250000.0", "raw": "1250000000000000000000000", "decimals": 18 }
}
```
`nonce` is the next nonce the box will sign with. It moves with every trade sent from here and with every transaction of the wallet Robinhood's feed shows, and is checked against the chain only after a receipt, never before a trade.
## Withdraw
Move ETH out, no fee. `amount` is ETH, or `"100%"` for everything above the network fee. The answer waits up to 15 s for the receipt.
```bash
curl -X POST https://rh-lightspeed.shrine.trade/api/lightspeed/withdraw \
-H "content-type: application/json" -H "x-api-key: ls_…" \
-d '{ "to": "0xYOUR_WALLET", "amount": "100%" }'
```
```json
{ "hash": "0x…", "explorer": "https://robinscan.io/tx/0x…", "status": "landed", "eth": "0.004791", "wei": "4791000000000000", "txs": [ … ] }
```
Tokens stay where they are: sell them first (a sell of an asset-priced token comes back as ETH by default), or import the private key into any wallet and move them from there.
---
# ⚡ Lightspeed: Fastest Pons Sniper API on Robinhood Chain
> One request buys or sells a Pons token from a box next to Robinhood: nothing is asked of the chain on the way, the transaction goes to every one of Robinhood's addresses at once, and the answer comes back on the first acceptance. Your wallet is encrypted under your own API key.
Advanced
Fastest-path route for snipers, from a wallet only your API key can open. To sign on your own machine instead, use the Buy & Sell API.
# ⚡ Lightspeed
One request. We build the trade, sign it with your Lightspeed wallet, which lives on our server, and hand it to every one of Robinhood's addresses at the same instant. No unsigned transaction comes back to you, no second call to send it, no RPC on your side, and none on ours either: the whole path from request to Robinhood runs on what the box already knows.
`POST https://rh-lightspeed.shrine.trade/api/lightspeed`
## Why it lands first
- **No round-trip.** The [Buy & Sell API](/) needs three hops: build, sign on your machine, send. Here it is one process on a box that peers with the chain, and building and signing take about a millisecond.
- **Nothing asked of the chain on the way.** Every Pons launch, every curve's reserves, every graduated pool's price and liquidity live in memory, kept current from Robinhood's feed and the chain's logs. The quote, the minimum out, the nonce and the gas price are all local. A trade does zero reads.
- **Every Robinhood address at once.** The signed transaction goes to every one of Robinhood's addresses in parallel; the first acceptance answers your request and the others finish in the background. One transaction, one nonce, one fill: there is nothing to race against yourself.
- **Asset-priced tokens in one transaction.** A token priced in USDG, NVDA, ORBIO or any other quote asset is bought with ETH and sold back to ETH in a single transaction through the Lightspeed router, with the ETH leg routed over the best of Uniswap v4 and v3.
- **The tape, as Robinhood orders it.** The [stream](/lightspeed/stream) fans out every Pons launch and trade from the same box the moment Robinhood orders it, before any node serves the block, with the launch's token and curve address predicted from the launch call.
## What it covers
Pons v2 tokens: on the bonding curve and in the Uniswap v4 pool after graduation, priced in ETH or in any of the quote assets Pons supports. Anything else on Robinhood Chain (Pons v1, Flap, Bags, a plain Uniswap token) is built by the [Buy & Sell API](/) next door and signed and sent the same way; the response says so with `"engine": "builder"`.
Next: [create an account](/lightspeed/account), then [trade](/lightspeed/trade).
---
# ⚡ Lightspeed Advanced Data Stream: Pons Launches and Trades From the Chain's Own Feed
> A WebSocket of every Pons launch and trade on Robinhood Chain, fanned out from Robinhood Chain's own feed about 170 ms before a node-based stream: predicted token and curve addresses, metadata, the dev buy, every buy and sell with its estimated fill. Key by request.
Advanced
Fastest-path route for snipers, from a wallet only your API key can open. To sign on your own machine instead, use the Buy & Sell API.
# Advanced Data Stream
`wss://rh-lightspeed.shrine.trade/api/lightspeed/stream?key=…`
Every Pons launch, trade and liquidity change, from the box that trades, and you pick what you want: launches, trades and liquidity changes, for the tokens and wallets you name or the whole tape. Launches and trades come from Robinhood's own feed the moment Robinhood has ordered them, before any node has the block, so a launch arrives with its token and curve address predicted from the launch call and a [Lightspeed buy](/lightspeed/trade) can go out at once. The [advanced data stream](/advanced-stream) waits for a node to execute the block first.
Measured on 2026-10-06 from one machine, both sockets open side by side, 119 trades matched by hash: the Lightspeed stream had each one first, by 86 ms at the tenth percentile, 170 ms at the median and 277 ms at the ninetieth. On this chain that is one to three blocks.
## Key
By request: ask in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk) for a stream key. A [Lightspeed account](/lightspeed/account) key works too, and then `connected` names the account. Pass it as `?key=` or in the `x-api-key` header.
## Where to run
The stream leaves Chicago. A bot in Chicago reads it about a millisecond later; one in Europe, a hundred milliseconds later, which on this chain is a block or two. The [copy trade tutorial](/tutorials/copy-trade-lightspeed) has the setup.
```js
const ws = new WebSocket("wss://rh-lightspeed.shrine.trade/api/lightspeed/stream?key=…&launches=1");
ws.onmessage = (m) => {
const e = JSON.parse(m.data);
if (e.type === "launch" && e.token) {
// e.token is predicted from the launch call; buy it now
ws.send(JSON.stringify({ subscribe: [e.token] })); // and follow its trades
}
if (e.type === "trade") console.log(e.side, e.symbol, e.amountIn.formatted, e.trader);
};
```
## Subscriptions
Say what you want as query parameters at connect time, or as messages afterwards; `subscribed` confirms each change. Trades and liquidity changes are sent for the tokens and wallets you follow; launches are not tied to a token yet, so they are simply on or off.
| Query | Message | |
|---|---|---|
| `tokens=0x…,0x…` | `{"subscribe": [...]}`, `{"unsubscribe": [...]}` | Follow these tokens (up to 500). |
| `wallets=0x…,0x…` | `{"subscribeWallets": [...]}`, `{"unsubscribeWallets": [...]}` | Follow these wallets: everything they do on any Pons token. |
| `launches=1` | `{"launches": true}` | Every new launch. Off by default. |
| `trades=0` | `{"trades": false}` | Trades of what you follow. On by default. |
| `liquidity=0` | `{"liquidity": false}` | Liquidity changes of what you follow. On by default. |
| `all=1` | `{"all": true}` | The whole Pons tape: every launch, trade and liquidity change. |
| | `{"ping": true}` | Answered with `{"type": "pong"}`. |
## Events
`connected` comes once, with the feed position (`feedSeq`), the subscriptions in force and, for an account key, your `account`.
### `launch`
```json
{
"type": "launch", "source": "feed",
"seq": 81687334, "block": 81687334, "timestamp": 1791325200, "hash": "0x…",
"via": "factory",
"token": "0x…", "curve": "0x…",
"deployer": "0x…", "creator": "0x…",
"name": "…", "symbol": "…", "logo": "…", "description": "…",
"socials": { "twitter": "…", "telegram": "…", "discord": "…", "website": "…", "farcaster": "…" },
"creatorTaxBps": 0,
"quoteAsset": { "address": "ETH", "symbol": "ETH", "decimals": 18 },
"devBuy": { "quoteIn": { "raw": "…", "formatted": "0.05" }, "minTokensOut": { "raw": "…", "formatted": "…" } }
}
```
`token` and `curve` are predicted from the launch call (the same CREATE2 the factory will run) and are what you pass to [trade](/lightspeed/trade). `via` is the call that made it: `factory` or `launch_and_buy`. A launch made through a contract we cannot read arrives from the factory's log a moment later instead, with `"source": "log"` and only `token`, `curve`, `quoteAsset`, `block` and `timestamp`; `token` is then `null` on the earlier feed event, if one was sent.
### `trade`
```json
{
"type": "trade", "side": "buy", "venue": "pons_curve", "via": "shrine_router",
"seq": 81687340, "block": 81687340, "timestamp": 1791325201, "hash": "0x…",
"trader": "0x…", "recipient": "0x…",
"token": "0x…", "name": "…", "symbol": "…",
"quoteAsset": { "address": "ETH", "symbol": "ETH", "decimals": 18 },
"currencyIn": "ETH",
"amountIn": { "raw": "10000000000000000", "formatted": "0.01" },
"minOut": { "raw": "…", "formatted": "…" },
"estimatedOut": { "raw": "…", "formatted": "…" }
}
```
A buy or sell the chain has ordered, on the curve (`pons_curve`) or in the graduated pool (`uniswap_v4`): what went in, the minimum the trader asked for, and our estimate of the fill from the curve's state with the trades ahead of it counted in (`estimatedOut`, curve trades only). `via` is the call: `curve` (the curve directly), `shrine_router`, `lightspeed_router` or `universal_router`. It is ordered, not yet executed: it can still revert.
### `liquidity`
```json
{
"type": "liquidity", "source": "log",
"action": "add", "venue": "uniswap_v4",
"block": 81687350, "logIndex": 4, "hash": "0x…",
"token": "0x…", "name": "…", "symbol": "…",
"quoteAsset": { "address": "ETH", "symbol": "ETH", "decimals": 18 },
"provider": "0x…",
"liquidityDelta": "184467440737095516",
"tickLower": -887200, "tickUpper": 887200
}
```
Liquidity added (`add`, a positive `liquidityDelta`) or removed (`remove`, negative) in a token's pool, Uniswap v4 for a graduated Pons v2 token or v3 for a Pons v1 token, by `provider`. These come from the chain's logs once the block has executed, so they trail launches and trades by a block or so.
### Connection messages
`{"type": "lagged", "dropped": n}` means your client read too slowly and `n` events were skipped; `{"type": "error", "message": …}` answers a bad message. Reconnect with the subscriptions in the query string to pick up where you were.
---
# POST /api/lightspeed: Build, Sign and Send One Pons Trade From Next to Robinhood
> The Lightspeed request and response on Robinhood Chain: fields, what the quote carries, what Robinhood answered, fees, and errors.
Advanced
Fastest-path route for snipers, from a wallet only your API key can open. To sign on your own machine instead, use the Buy & Sell API.
# Trade
`POST https://rh-lightspeed.shrine.trade/api/lightspeed`, with the API key from your [account](/lightspeed/account).
**JavaScript**
```js
const res = await fetch("https://rh-lightspeed.shrine.trade/api/lightspeed", {
method: "POST",
headers: { "content-type": "application/json", "x-api-key": process.env.LS_KEY },
body: JSON.stringify({
action: "buy", // "buy" or "sell"
token: "0xTHE_TOKEN",
amount: 0.01, // ETH on a buy; tokens or "100%" on a sell
slippage: 10, // percent; a fresh launch wants room
}),
});
const r = await res.json();
console.log(r.success, r.hash, r.quote?.expectedOutFormatted, r.sendMs);
```
**Python**
```python
import os, requests
r = requests.post(
"https://rh-lightspeed.shrine.trade/api/lightspeed",
headers={"x-api-key": os.environ["LS_KEY"]},
json={"action": "buy", "token": "0xTHE_TOKEN", "amount": 0.01, "slippage": 10},
).json()
print(r["success"], r["hash"], r.get("quote", {}).get("expectedOutFormatted"), r["sendMs"])
```
**curl**
```bash
curl -X POST https://rh-lightspeed.shrine.trade/api/lightspeed \
-H "content-type: application/json" -H "x-api-key: ls_…" \
-d '{ "action": "buy", "token": "0xTHE_TOKEN", "amount": 0.01, "slippage": 10 }'
```
### Request
| Field | | |
|---|---|---|
| `apiKey` | string | Or the `x-api-key` header. |
| `action` | `"buy"` \| `"sell"` | |
| `token` | string | Token address. |
| `amount` | number \| string | ETH on a buy, whatever the token is priced in. Tokens, or `"100%"` / `"50%"` of the balance, on a sell. |
| `slippage` | number | Percent. Default 5. |
| `toEth` | boolean | Sells of a token priced in an asset (USDG, NVDA, ORBIO …): take the proceeds in ETH (default `true`) or keep the asset (`false`). |
| `wait` | boolean | Default `false`: the answer comes as soon as Robinhood accepted. `true` waits up to 20 s for the receipt and adds `status`, `blockNumber` and `gasUsed`. |
### Response
```json
{
"success": true,
"hash": "0x…",
"explorer": "https://robinscan.io/tx/0x…",
"wallet": "0x9985…a92f",
"engine": "local",
"buildMs": 0.9,
"sendMs": 11.4,
"txs": [
{ "description": "buy", "hash": "0x…", "nonce": 7, "signUs": 180,
"submissions": [ { "address": "203.0.113.7", "accepted": true, "ms": 11.2 } ] }
],
"protocol": "PONS_V2", "venue": "pons_curve", "phase": "NotGraduated",
"token": "0x…", "name": "…", "symbol": "…", "curve": "0x…",
"quoteAsset": { "address": "ETH", "symbol": "ETH", "decimals": 18, "isNative": true },
"action": "buy",
"ethIn": "10000000000000000", "ethInFormatted": "0.01",
"amountIn": "9925000000000000", "amountInFormatted": "0.009925",
"quote": {
"expectedOut": "…", "expectedOutFormatted": "1250000.0", "minOut": "…",
"curveFeeBps": 100, "creatorTaxBps": 0, "snipeTaxBps": 0,
"lightspeedFeeBps": 75, "slippagePct": 10
}
}
```
`success` means Robinhood accepted every transaction of the trade; `hash` is the last one (a sell may be preceded by an approval, listed in `txs`). `sendMs` is from the request's arrival on our box to Robinhood's acceptance; `signUs` is the signing time in microseconds. `amountIn` is what reaches the venue after the Lightspeed fee, in the token's quote asset; on an asset-priced token `route` lists the hops the ETH took to get there. The quote is computed here from the curve's or pool's own math, and `minOut` is what the transaction enforces.
An accepted transaction can still revert on chain: the curve sold out in the same block, or the price moved past your slippage. Pass `wait: true` to learn that in the same request, or watch `hash`.
### Venues
| `venue` | | Fee |
|---|---|---|
| `pons_curve` | the bonding curve, through the Lightspeed router | 0.75% in the quote asset |
| `uniswap_v4` | the Pons pool after graduation, through the Universal Router | 0.75% in the asset the trade settles in |
An asset-priced curve token (`quoteAsset` not ETH) is bought with ETH in one transaction: the router swaps the ETH into the asset over Uniswap and buys the curve with it. A sell goes the other way and comes back as ETH unless `toEth` is `false`. A token outside Pons v2 is built by the [Buy & Sell API](/) and sent from here (`"engine": "builder"`, with `builderReason`); it may take more than one transaction.
### Errors
| Status | `error` | |
|---|---|---|
| 401 | `unauthorized` | Missing or unknown API key. |
| 400 | `invalid_action`, `invalid_token`, `invalid_amount`, `invalid_slippage` | |
| 400 | `insufficient_balance` | The wallet holds fewer tokens than the sell asks for. |
| 400 | `insufficient_funds` | Robinhood refused: not enough ETH for the value plus gas. |
| 400 | `unquotable` | The amount is outside what the curve or pool can fill. |
| 409 | `curve_closed` | The curve sold out and is graduating; buys reopen in the pool. |
| 503 | `warming_up`, `rpc` | The box has no gas price yet, or a cold read failed. |
| 502 | other | Robinhood refused with `message`; `txs` shows each attempt. |
## Fees
0.75% of the trade, taken inside the transaction, in the asset the trade settles in. Gas on Robinhood Chain is a few millionths of an ETH. See [Fees](/fees).
## Speed checklist
- Keep the wallet funded so no request waits on a deposit.
- Take `token` from the [stream](/lightspeed/stream): a launch event carries the predicted token address before the block exists, and the curve is quotable from its launch config.
- Run your bot near our box (Chicago) or accept the network hop; our side adds about a millisecond.
- Give a fresh launch slippage room: the dev buy and the first snipes land in the same blocks as yours.
---
# Live Launches on Every Robinhood Chain Launchpad
> WebSocket feed of every new launch on Pons, Flap, Bags, o1, Pools.trade, PAIR, Long, letscash, Varo and Virtuals, every graduation and every new Uniswap v4 pool on Robinhood Chain, the moment it happens. Free and keyless.
# Live Launches & Graduations
The whole token lifecycle on Robinhood Chain, pushed the moment it happens. Three things arrive on this one socket:
- **Launches** - a token appears on any launchpad: Pons, Flap, Bags, o1, Pools.trade, PAIR, Long, letscash, Varo or Virtuals.
- **Graduations** - a Pons or Flap curve sells out and the token moves to its permanent pool.
- **New Uniswap v4 pools** - including tokens launched straight on Uniswap, without a curve.
Free, no key, no account.
```
wss://api.shrine.trade/rh/api/launches/ws
```
## Copy this
Click the launchpads you want - the URL updates.
**JavaScript** (Node 22+, no packages):
```js
// Add ?protocols=PONS for launches only, or ?protocols=UNISWAP_V4 for
// graduations and new pools only. No query = everything.
const ws = new WebSocket("wss://api.shrine.trade/rh/api/launches/ws");
ws.onmessage = (e) => {
const t = JSON.parse(e.data);
// t.type: new_launch, graduated or new_pool
console.log(JSON.stringify(t, null, 2));
};
```
**Python** (`pip install websockets`):
```python
import json
from websockets.sync.client import connect
with connect("wss://api.shrine.trade/rh/api/launches/ws") as ws:
for raw in ws:
t = json.loads(raw)
# t["type"]: new_launch, graduated or new_pool
print(json.dumps(t, indent=2))
```
## What you get
On connect you're sent the **single most recent event** so the screen isn't blank, then everything new as it happens.
Each message has a `type` and a `protocol`:
| `type` | `protocol` | Means |
|---|---|---|
| `new_launch` | `PONS` | A token launched on the Pons curve - tradeable immediately. |
| `new_launch` | `FLAP`, `BAGS`, `O1`, `POOLS_TRADE`, `LETSCASH`, `PAIR`, `LONG`, `VARO`, `VIRTUALS` | A token launched on that launchpad. Where the launch opened a Uniswap v4 pool, the pool key rides along (`poolId`, `currency0`, `currency1`, `fee`, `tickSpacing`, `hooks`); a Bags launch carries its `curve`. |
| `graduated` | `UNISWAP_V4` | A Pons curve sold out; the token moved to its Uniswap v4 pool. |
| `graduated` | `FLAP` | A Flap curve sold out; the token moved to its Uniswap v2 pair. |
| `new_pool` | `UNISWAP_V4` | A Uniswap v4 pool was created. Includes graduations **and** tokens launched directly on Uniswap. When the pool carries a launchpad's hook, `launchpad` names it (`BAGS`, say). |
Filter with `?protocols=PONS,FLAP,BAGS` - any comma-separated set of the names above (omit it for everything), or use the picker above.
Fields that don't apply to an event are simply absent, so check `type` first.
```json
{
"type": "new_launch",
"protocol": "PONS",
"token": "0x84BD8478c0…",
"curve": "0x2ffCc8d7…",
"deployer": "0x1a2b3c…",
"name": "Tollbooth",
"symbol": "TOLLBOOTH",
"pairToken": "ETH",
"launchConfigId": 0,
"graduationThreshold": "4200000000000000000",
"uri": "ipfs://bafy…",
"description": "the greenest coin on Robinhood Chain",
"socials": {
"twitter": "https://x.com/grene",
"telegram": "", "discord": "", "website": "", "farcaster": ""
},
"blockNumber": 50952986,
"txHash": "0x…",
"timestamp": 1756654321
}
```
**`uri`, `description` and `socials` come off the token contract itself** - Pons stores them on-chain, so the creator's image URI and links arrive with the event and there's nothing extra to fetch.
A `new_pool` event carries `poolId`, `currency0`, `currency1`, `fee`, `tickSpacing` and `hooks` instead. A `hooks` of `0x0000…0000` means a plain Uniswap pool with no Pons hook - i.e. someone launched directly on Uniswap rather than through a curve.
> **Don't buy the instant you see a launch.** Pons has sniper protection and taxes buys for about **3 seconds** after a launch, starting near **99%** and decaying to zero. Buying the moment a launch appears in this feed hands over almost your entire order as tax.
## Notes
- **Reconnect on drop.** Long-lived sockets die; reconnect and you're re-seeded with the recent launches, so you won't miss much.
- **One subscription per IP.** A second launch-feed connection from the same address is refused with `too_many_connections`. One socket with no filter carries everything, so you need no more; the [data stream](/advanced-stream) is a separate kind and can be open alongside it.
- **Nothing is stored.** This is a live feed, not an archive - there's no history endpoint and no backfill.
---
# Buy & Sell Tokens from Any Launchpad on Robinhood Chain
> Buy or sell tokens from Pons, Flap, Bags, o1, Pools.trade, PAIR, Long and letscash on Robinhood Chain with one script, on the bonding curve or in the Uniswap pool. Pay in ETH; tokens priced in USDG, DJT, NVDA or TSLA are converted for you.
# Buy & Sell
Copy the script, change three lines, run it. We build the transaction, your script signs and sends it - your private key never leaves your machine.
One endpoint for a token from any launchpad on Robinhood Chain, wherever it lives right now:
- **Pons v2** - on its bonding curve, then in its Uniswap v4 pool after graduation. **Pons v1** - in the Uniswap V3 pool it launched into.
- **Flap** - on Flap's curve, then in its Uniswap v2 pair; Flap's Portal fills both.
- **Bags** - on its bonding curve, then in its Uniswap v4 pool.
- **o1**, **Pools.trade**, **letscash**, **PAIR**, **Long** - in the Uniswap v4 pool they launched into, hook fees included in the quote.
- **Any Uniswap token** on Robinhood Chain - the tokenised stocks (NVDA, TSLA, USDG …) and tokens launched straight into a pool.
Pass the token address and we work out which. The response says so in `protocol` (`PONS_V2`, `PONS_V1`, `FLAP`, `BAGS`, `O1`, `POOLS_TRADE`, `LETSCASH`, `PAIR`, `LONG` or `UNISWAP`) and `venue` (`pons_curve`, `flap_curve`, `bags_curve`, `uniswap_v4`, `uniswap_v3`, `uniswap_v2`).
Varo and Virtuals tokens are not tradable through the API yet; their launches are on the [live feed](/live-launches).
## What you need
- **Node.js** or **Python**.
- A wallet private key with some **ETH on Robinhood Chain**.
- A token address. Take one from any launchpad's site or the [live launches feed](/live-launches).
## Example
**JavaScript**
```js
const { Wallet } = require("ethers");
// ─── change these three ───────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const TOKEN = "0xTHE_TOKEN_ADDRESS";
const AMOUNT = "0.01"; // to buy: ETH to spend. To sell: "1000" tokens, or "100%"
const ACTION = "buy"; // "buy" or "sell"
const TO_ETH = true; // selling a token priced in USDG/NVDA/…: also
// convert the proceeds to ETH
const SLIPPAGE = 5; // %, how far the price may move against you
// ──────────────────────────────────────────────────────
async function main() {
const wallet = new Wallet(PRIVATE_KEY); // signs only; no node needed
const res = await fetch("https://api.shrine.trade/rh/api/local-trade", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
action: ACTION,
token: TOKEN,
amount: AMOUNT,
toEth: TO_ETH,
slippage: SLIPPAGE,
from: wallet.address,
}),
});
const data = await res.json();
if (data.error) throw new Error(`${data.error}: ${data.message}`);
console.log("you should get about", data.quote.expectedOutFormatted,
ACTION === "buy" ? "tokens" : data.quoteAsset.symbol);
if (data.quoteSwap) {
console.log("buying", data.quoteSwap.quoteOutFormatted, data.quoteAsset.symbol,
"with", data.quoteSwap.ethInMaxFormatted, "ETH first");
}
// Sign locally and hand the signed bytes to the API to broadcast - the key
// never leaves this script, and you need no RPC of your own.
for (const tx of data.txs) {
const signed = await wallet.signTransaction({
to: tx.to, data: tx.data, value: BigInt(tx.value), gasLimit: BigInt(tx.gas),
maxFeePerGas: BigInt(tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas),
nonce: tx.nonce, chainId: tx.chainId, type: 2,
});
const sent = await (await fetch("https://api.shrine.trade/rh/api/send", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ signedTx: signed }),
})).json();
if (sent.error) throw new Error(`${sent.error}: ${sent.message}`);
if (sent.status !== "landed") throw new Error(`${tx.description} ${sent.status}: ${sent.explorer}`);
console.log(tx.description + ": " + sent.explorer);
}
if (ACTION === "buy") {
console.log(`bought ${data.name} (${data.symbol}) ${data.token} for ${data.ethInFormatted ?? data.amountInFormatted} ETH`);
} else if (data.toEth) {
console.log(`sold ${data.amountInFormatted} ${data.name} (${data.symbol}) ${data.token} for ${data.toEth.ethOutFormatted} ETH`);
} else {
console.log(`sold ${data.amountInFormatted} ${data.name} (${data.symbol}) ${data.token} for ${data.quote.expectedOutFormatted} ${data.quoteAsset.symbol}`);
}
}
main();
```
Save it as `trade.js`, then:
```bash
npm install ethers
node trade.js
```
**Python**
```python
import requests
from eth_account import Account
# ─── change these three ───────────────────────────────
PRIVATE_KEY = "0xYOUR_PRIVATE_KEY"
TOKEN = "0xTHE_TOKEN_ADDRESS"
AMOUNT = "0.01" # to buy: ETH to spend. To sell: "1000" tokens, or "100%"
ACTION = "buy" # "buy" or "sell"
TO_ETH = True # selling a token priced in USDG/NVDA/…: also convert the proceeds to ETH
SLIPPAGE = 5 # %, how far the price may move against you
# ──────────────────────────────────────────────────────
account = Account.from_key(PRIVATE_KEY) # signs only; no node needed
res = requests.post(
"https://api.shrine.trade/rh/api/local-trade",
json={"action": ACTION, "token": TOKEN, "amount": AMOUNT, "toEth": TO_ETH, "slippage": SLIPPAGE, "from": account.address},
)
data = res.json()
if "error" in data:
raise SystemExit(f"{data['error']}: {data['message']}")
unit = "tokens" if ACTION == "buy" else data["quoteAsset"]["symbol"]
print("you should get about", data["quote"]["expectedOutFormatted"], unit)
for tx in data["txs"]:
signed = Account.sign_transaction(
{
"to": tx["to"],
"data": tx["data"],
"value": int(tx["value"]),
"gas": tx["gas"],
"maxFeePerGas": int(tx["maxFeePerGas"]),
"maxPriorityFeePerGas": int(tx["maxPriorityFeePerGas"]),
"nonce": tx["nonce"],
"chainId": tx["chainId"],
},
account.key,
)
sent = requests.post("https://api.shrine.trade/rh/api/send", json={"signedTx": "0x" + signed.raw_transaction.hex().removeprefix("0x")}).json()
if "error" in sent:
raise SystemExit(f"{sent['error']}: {sent['message']}")
if sent["status"] != "landed":
raise SystemExit(f"{tx['description']} {sent['status']}: {sent['explorer']}")
print(tx["description"] + ":", sent["explorer"])
if ACTION == "buy":
print(f"bought {data['name']} ({data['symbol']}) {data['token']} for {data.get('ethInFormatted', data['amountInFormatted'])} ETH")
elif data.get("toEth"):
print(f"sold {data['amountInFormatted']} {data['name']} ({data['symbol']}) {data['token']} for {data['toEth']['ethOutFormatted']} ETH")
else:
print(f"sold {data['amountInFormatted']} {data['name']} ({data['symbol']}) {data['token']} for {data['quote']['expectedOutFormatted']} {data['quoteAsset']['symbol']}")
```
Save it as `trade.py`, then:
```bash
pip install eth-account requests
python trade.py
```
The script sends whatever comes back, in order. Usually that is one transaction; a first sell, or a buy on a token priced in USDG, NVDA or another asset, may need a one-time approval in front of it.
Trades are front-run protected: Robinhood Chain has no public mempool, so nobody sees your transaction before the sequencer orders it, and the on-chain `minOut` guarantees the fill is never worse than your slippage allows.
> **Don't buy a launch in its first seconds.** Pons has sniper protection and taxes buys for about 3 seconds after a launch.
## Selling
Set `ACTION = "sell"`. `AMOUNT` is a number of tokens or a share of what you hold:
| `AMOUNT` | Sells |
|---|---|
| `"1000"` | 1000 tokens |
| `"50%"` | half your balance |
| `"100%"` | everything |
## Tokens priced in USDG, NVDA, …
A lot of Pons launches are priced in one of Robinhood Chain's tokenised assets instead of ETH. Nothing changes in the script: `AMOUNT` on a buy is always ETH. For a token priced in USDG, `"0.001"` means "spend 0.001 ETH", and the API works out how much USDG that is (`amountIn`, in `quoteAsset`) and hands the curve or pool that. If your wallet already holds that much USDG it is used as is; otherwise the transactions start with a swap that buys the missing amount with ETH, and `quoteSwap` says what that costs. `ethIn` in the response is the ETH you asked to spend.
Selling pays out in that asset. With `toEth: true` (the scripts' `TO_ETH`) the proceeds are converted to ETH as well: on a graduated token it happens inside the same transaction, on a curve token it is a follow-up swap of the guaranteed minimum. `toEth` in the response says how much ETH you end up with and through which pools.
The full list is on [Create Token](/create-token#supported-quote-assets).
## If it doesn't work
| Error | What to do |
|---|---|
| `insufficient_funds` | Add ETH to the wallet - the amount plus a little for gas. |
| `insufficient_balance` | You're selling more than you hold. Try `"100%"`. |
| `token_not_found` | Not a Pons token. Check you copied the token, not the pool. |
| `slippage_exceeded` | The price moved. Raise `SLIPPAGE` (percent, default `5`) and retry. |
| `graduating` | The token is moving to its Uniswap pool. Wait a minute and retry. |
Full list on [Errors](/errors).
## Sending
`POST https://api.shrine.trade/rh/api/send` takes one signed transaction and broadcasts it through our Robinhood Chain endpoint, so the scripts need no RPC at all. It only forwards transactions this API built - Pons and Uniswap trades, launches, approvals and claims - and refuses anything else with `not_ours`.
| Field | | |
|---|---|---|
| `signedTx` | 0x-hex | The signed transaction, as `wallet.signTransaction` returns it. |
| `wait` | boolean, optional | Default `true`: wait for the receipt. `false` returns the hash as soon as the node accepts it. |
```json
{ "hash": "0x…", "status": "landed", "blockNumber": 55020118, "gasUsed": 119746, "explorer": "https://robinscan.io/tx/0x…" }
```
`status` is `landed`, `reverted`, or `pending` (not waited for, or not mined within 45 seconds). A landed launch also carries `token` and `curve`, read from the factory's event.
### Request and response
`POST https://api.shrine.trade/rh/api/local-trade`
**Request**
| Field | | |
|---|---|---|
| `action` | `"buy"` or `"sell"` | |
| `token` | address | The Pons token. |
| `amount` | decimal string | Buy: ETH to spend, whatever the token is priced in. Sell: tokens (`"1000"`) or a share of your balance (`"50%"`, `"100%"`). |
| `from` | address | Your wallet. |
| `slippage` | number, optional | Percent, default `5`. |
| `toEth` | boolean, optional | Sell only, tokens priced in an ERC-20: also convert the proceeds to ETH. |
**Response** - a real one, a buy of an RDDT-priced token for 0.001 ETH:
```json
{
"action": "buy",
"token": "0x77df8B452670deb3DA51B367CD58044422dcE4b0",
"name": "Honest Work",
"symbol": "HONEST",
"curve": "0x07365E838a3f62675b81b3438137824F4B03457e",
"phase": "NotGraduated",
"venue": "pons_curve",
"amountIn": "14171692423151839",
"amountInFormatted": "0.014171692423151839",
"quoteAsset": {
"address": "0x05b37Fb53A299a1b874A619e1c4C404D52C36F4C",
"symbol": "RDDT",
"decimals": 18,
"isNative": false
},
"quote": {
"expectedOut": "819167564794497226305697",
"expectedOutFormatted": "819167.564794497226305697",
"minOut": "778209186554772364990412",
"curveFeeBps": 100,
"creatorTaxBps": 0,
"snipeTaxBps": 0,
"shrineFeeBps": 50,
"slippagePct": 5.0
},
"ethIn": "1000000000000000",
"ethInFormatted": "0.001",
"txs": [
{
"to": "0x05b37Fb53A299a1b874A619e1c4C404D52C36F4C",
"data": "0x\u2026",
"value": "0",
"gas": 80000,
"maxFeePerGas": "580242501",
"maxPriorityFeePerGas": "1",
"nonce": 5,
"chainId": 4663,
"type": 2,
"description": "approve_quote"
},
{
"to": "0xEc20E594D28a17511264dc73a84cd4AA957B0ABc",
"data": "0x\u2026",
"value": "0",
"gas": 400000,
"maxFeePerGas": "580242501",
"maxPriorityFeePerGas": "1",
"nonce": 6,
"chainId": 4663,
"type": 2,
"description": "buy"
}
]
}
```
- `txs` - sign and send in order. `value`, `maxFeePerGas`, `maxPriorityFeePerGas` are decimal strings in wei; the rest are numbers. `description` says what each one is: `buy`, `sell`, a one-time `approve` / `approve_quote` (curve) or `approve_permit2` / `approve_router` (Uniswap), `buy_quote_with_eth` (a swap in front of a buy), `convert_to_eth` (a swap after a curve sell).
- `name` / `symbol` - the token's own, so your script can say what it traded.
- `protocol` - `PONS_V2` or `PONS_V1`. `venue` - `pons_curve` while a v2 token is on its bonding curve, `uniswap_v4` after graduation, `uniswap_v3` for every v1 token. The request is the same in all cases. v1 tokens are always priced in ETH, so `toEth` and the quote-asset conversion don't apply.
- `quoteAsset` - what the trade is settled in: ETH, or the tokenised asset the token is priced in. `decimals` is not always 18.
- `amountIn` - what the curve or pool receives, in `quoteAsset`. On a buy of an ERC-20-priced token, `ethIn` is the ETH you asked for and `amountIn` is what that ETH buys of the asset.
- `quote.expectedOut` - what you should receive; `minOut` has your slippage applied and is enforced on-chain. Base units; the `*Formatted` fields are the human numbers.
- `quote` fees - `curveFeeBps` and `creatorTaxBps` are Pons's, `shrineFeeBps` is ours (50 = 0.5%), `snipeTaxBps` is Pons's opening tax for your wallet right now.
- `quoteSwap` - only when your wallet is short of the quote asset and a swap was added in front: `quoteOut` bought, `ethIn` quoted, `ethInMax` carried (refunded if unused), `route` the pools, found on-chain.
- `toEth` - only on a sell with `toEth: true`: `ethOut` after our fee, `ethOutMin` enforced on-chain, `route` the pools. Graduated tokens do it in the same transaction; curve tokens add a `convert_to_eth` transaction.
---
# Token Info API - any launchpad on Robinhood Chain
> Read any token on Robinhood Chain: which launchpad it came from (Pons, Flap, Bags, o1, Pools.trade, PAIR, Long, letscash), where it trades, curve reserves, graduation phase, snipe tax for your wallet and unswept creator fees.
# Token Info
Free, keyless reads. Use them to decide what to trade and to size a buy before you call `local-trade`. Amounts are raw base-unit strings (wei for ETH, 18-decimal units for tokens); fields ending in `Formatted` are the human-readable decimal.
Works for a token from any launchpad: `protocol` names it (`PONS_V2`, `PONS_V1`, `FLAP`, `BAGS`, `O1`, `POOLS_TRADE`, `LETSCASH`, `PAIR`, `LONG`, or `UNISWAP_V4` / `UNISWAP_V3` for a plain pool) and `venue` says where it trades right now (`pons_curve`, `flap_curve`, `bags_curve`, `uniswap_v4`, `uniswap_v3`, `uniswap_v2`). For a pool, `curve` is the pool id and `hook` the launchpad's hook; `curveFeeBps` is the venue's own fee on a trade.
## Token
```
GET https://api.shrine.trade/rh/api/token/{address}
GET https://api.shrine.trade/rh/api/token/{address}?recipient=0x…
```
**JavaScript**
```js
const TOKEN = "0xEad55618651062963DeE22728EFb1F3275DA7393";
async function main() {
const res = await fetch(`https://api.shrine.trade/rh/api/token/${TOKEN}`);
const t = await res.json();
if (t.error) throw new Error(`${t.error}: ${t.message}`);
console.log(t.phase, "| sellable:", t.sellableTokensFormatted, "| curve fee:", t.curveFeeBps, "bps");
console.log("reserves:", t.quoteReserve, "wei /", t.tokenReserve, "tokens");
console.log("unswept creator fees:", t.unsweptFees.creatorShareEstimateFormatted);
}
main();
```
Save it as `token.js` and run `node token.js` - no packages needed.
**Python**
```python
import requests
TOKEN = "0xEad55618651062963DeE22728EFb1F3275DA7393"
t = requests.get(f"https://api.shrine.trade/rh/api/token/{TOKEN}").json()
if "error" in t:
raise SystemExit(f"{t['error']}: {t['message']}")
print(t["phase"], "| sellable:", t["sellableTokensFormatted"], "| curve fee:", t["curveFeeBps"], "bps")
print("reserves:", t["quoteReserve"], "wei /", t["tokenReserve"], "tokens")
print("unswept creator fees:", t["unsweptFees"]["creatorShareEstimateFormatted"])
```
Save it as `token.py`, `pip install requests`, then `python token.py`.
Add `?recipient=0x…` to get `snipeTaxBps` for that wallet. Example response:
```json
{
"token": "0x1111111111111111111111111111111111111111",
"curve": "0x2222222222222222222222222222222222222222",
"deployer": "0x3333333333333333333333333333333333333333",
"creatorFeeRecipient": "0x3333333333333333333333333333333333333333",
"pairToken": "ETH",
"phase": "NotGraduated",
"graduated": false,
"readyToGraduate": false,
"graduationThreshold": "4200000000000000000",
"quoteReserve": "1712522872270353145",
"tokenReserve": "981008795387803266283543676",
"sellableTokens": "695294509673517551997829391",
"sellableTokensFormatted": "695294509.673517551997829391",
"curveFeeBps": 100,
"creatorTaxBps": 0,
"buybackEnabled": false,
"poolFee": 0,
"tickSpacing": 200,
"snipeTaxBps": 0,
"unsweptFees": {
"heldBy": "curve",
"tradingFee": "747321571942453",
"tradingFeeFormatted": "0.000747321571942453",
"creatorTax": "682955361004953",
"creatorTaxFormatted": "0.000682955361004953",
"creatorShareEstimate": "944517911184812",
"creatorShareEstimateFormatted": "0.000944517911184812",
"protocolShareBps": 3000,
"buybackShareBps": 5000,
"buybackEnabled": true,
"creatorCanSweep": false,
"hasUnswept": true
}
}
```
| Field | Meaning |
|---|---|
| `protocol` | `PONS_V2`, `PONS_V1`, `UNISWAP_V4` or `UNISWAP_V3`. For v1 tokens `curve` is the Uniswap V3 pool, `phase` is always `PoolCreated`, `quoteReserve` / `tokenReserve` are the pool's balances, and the fee and creator-fee fields are zero - v1 has no curve, no creator tax and no escrow. `UNISWAP_*` is any other token with a Uniswap pool on Robinhood Chain (the tokenised stocks included): `curve` is the pool, `pairToken` what it is priced in, `poolFee` the tier, and every Pons-only field is zero. |
| `curve` | The bonding-curve contract — where trades go while `phase` is `NotGraduated`. |
| `pairToken` | The quote asset. `"ETH"` for native-ETH launches (the only kind this API trades). |
| `phase` / `graduated` | `NotGraduated` (curve live) · `Swept` · `PoolCreated` (trading on Uniswap v4) · `Rescued`. `graduated` is `true` for anything but `NotGraduated`. |
| `readyToGraduate` | `true` once nothing is left to sell; sells revert from here on. |
| `graduationThreshold` | Quote (wei) the curve must collect before it graduates. |
| `quoteReserve` / `tokenReserve` | Current curve reserves — the price is their ratio. |
| `sellableTokens` | Tokens still purchasable on the curve. `0` means it's about to graduate. |
| `curveFeeBps` / `creatorTaxBps` | Pons's base curve fee and the launcher's creator tax (basis points, on the quote asset). |
| `poolFee` / `tickSpacing` | The Uniswap v4 pool parameters this token graduates into. |
| `snipeTaxBps` | Only meaningful with `?recipient=` — the opening tax *that wallet* would pay on a buy right now. |
| `unsweptFees` | Fees this launch has earned that are still sitting on the curve (`heldBy: "curve"`) or the pool hook after graduation (`heldBy: "hook"`), in the quote asset. `creatorShareEstimate` is what the creator gets of them, and [`/api/claim-fees`](/creator-fees) collects it together with the escrow balance; `creatorCanSweep` is `false` for launches with buybacks on, where Pons collects on its own schedule. |
---
# How to Copy Trade on Robinhood Chain with Lightspeed
> A complete Node.js copy trader: follow wallets on the Lightspeed stream, see their Pons buys the moment the chain orders them, and mirror them from a Lightspeed wallet in one request. With the fastest-connection setup.
# How to copy trade with ⚡ Lightspeed
Follow one or more wallets, and when one of them buys a Pons token, buy it too, a few milliseconds later; when they sell, sell. Everything runs through [Lightspeed](/lightspeed/overview): the [stream](/lightspeed/stream) shows their trade the moment the chain orders it, before any node has the block, and one request sends yours from the same box.
## What you need
- Node.js 22 or newer (WebSocket is built in). Nothing else: no RPC, no signing library.
- A [Lightspeed account](/lightspeed/account) funded with ETH on Robinhood Chain, and a stream key (ask in the [Telegram group](https://t.me/+nEqAowTK8BZhZjFk); the account key works for the stream too).
- A wallet to follow. The [advanced data stream](/advanced-stream) and the [live launches feed](/live-launches) are good places to spot one.
- Ideally a server in Chicago (below).
## The fastest connection
Lightspeed runs in Chicago, a few milliseconds from Robinhood Chain's own servers. Your bot's only round trip is to us, so where it runs is most of its latency:
| Your bot runs in | Request to our box, one way |
|---|---|
| Chicago | about 1 ms |
| US East coast | 15-25 ms |
| Western Europe | 90-120 ms |
| Asia | 150-250 ms |
A copy trade that leaves a Chicago VPS lands a block or two behind the trade it copies; one from Europe is ten blocks behind (Robinhood Chain makes about ten blocks a second). Any provider with a Chicago region will do. Keep one WebSocket open to the stream, and keep the trade connection warm by sending a request now and then (a balance read is enough), so the first real trade doesn't pay for a TLS handshake.

## The script
```js
// ─── change these ─────────────────────────────────────
const LS_KEY = "ls_…"; // your Lightspeed account key
const STREAM_KEY = LS_KEY; // or a stream key from the Telegram group
const FOLLOW = ["0xWALLET_TO_COPY"]; // one or more wallets
const BUY_ETH = 0.005; // ETH per copied buy, whatever they spent
const MAX_OPEN = 5; // positions at once
const SLIPPAGE = 10; // percent; copies land in the same blocks as the original
const DRY_RUN = true; // log what would be sent, send nothing
// ──────────────────────────────────────────────────────
const LS = "https://rh-lightspeed.shrine.trade";
const open = new Map(); // token -> { symbol, since }
async function trade(body) {
if (DRY_RUN) { console.log(" dry-run:", JSON.stringify(body)); return { success: true, dryRun: true }; }
const r = await (await fetch(`${LS}/api/lightspeed`, {
method: "POST",
headers: { "content-type": "application/json", "x-api-key": LS_KEY },
body: JSON.stringify(body),
})).json();
if (!r.success) throw new Error(`${r.error}: ${r.message}`);
console.log(` ${body.action} sent in ${r.sendMs} ms: ${r.explorer}`);
return r;
}
function connect() {
const url = `${LS.replace("https", "wss")}/api/lightspeed/stream?key=${STREAM_KEY}&wallets=${FOLLOW.join(",")}`;
const ws = new WebSocket(url);
ws.onopen = () => console.log("following", FOLLOW.join(", "));
ws.onclose = () => { console.log("stream closed, reconnecting"); setTimeout(connect, 1000); };
ws.onmessage = async (m) => {
const e = JSON.parse(m.data);
if (e.type !== "trade") return;
// Our own trades show up here too: never copy ourselves.
if (!FOLLOW.map((w) => w.toLowerCase()).includes(e.trader.toLowerCase())) return;
const name = e.symbol || e.token.slice(0, 10);
try {
if (e.side === "buy" && !open.has(e.token)) {
if (open.size >= MAX_OPEN) { console.log(`skip ${name}: ${MAX_OPEN} positions open`); return; }
console.log(`${e.trader.slice(0, 8)} bought ${name} for ${e.amountIn.formatted} ${e.quoteAsset.symbol} on ${e.venue}`);
open.set(e.token, { symbol: name, since: Date.now() });
await trade({ action: "buy", token: e.token, amount: BUY_ETH, slippage: SLIPPAGE });
} else if (e.side === "sell" && open.has(e.token)) {
console.log(`${e.trader.slice(0, 8)} sold ${name}`);
open.delete(e.token);
await trade({ action: "sell", token: e.token, amount: "100%", slippage: SLIPPAGE });
}
} catch (err) {
console.error(` ${name}: ${err.message}`);
if (e.side === "buy") open.delete(e.token);
}
};
}
// A warm connection to the trade endpoint: the first real request then
// skips the TLS handshake.
setInterval(() => fetch(`${LS}/api/lightspeed/account`, { headers: { "x-api-key": LS_KEY } }).catch(() => {}), 30_000);
connect();
```
Run it with `DRY_RUN = true` first: it prints every trade it would copy. Flip it to `false` when the picks look right.
## What each part does
- **The stream filter.** `wallets=` in the URL means the socket carries only what those wallets do, on any Pons token, curve or pool. `e.trader` is the wallet that sent the transaction, even when it went through a router. Trades arrive as the chain orders them, so they can still revert; a copy of a trade that reverts usually reverts for the same reason and costs only gas.
- **The buy.** `amount` is ETH whatever the token is priced in. A token priced in USDG or a stock is bought with ETH in one transaction, so a copy of a stock-priced buy needs nothing extra. The response carries the expected fill and the hash; the trade was accepted by the chain when the request returns.
- **The sell.** `"100%"` sells the whole balance, with proceeds in ETH. A sell by the copied wallet that you never bought into is ignored.
- **Slippage.** Your copy lands in the same block or the next, right behind the trade it copies and often behind other copiers. 10% is a floor on a fresh launch; the response's `quote.minOut` shows what the transaction enforces.
- **Position cap.** `MAX_OPEN` keeps a busy wallet from draining yours in a minute. Add your own exits (a take-profit from the stream's trades on tokens you hold, a time limit) on top.
## Fees
0.75% on each Lightspeed trade, inside the transaction, plus Robinhood Chain gas of a few millionths of an ETH. See [Fees](/fees).
---
# Robinhood Chain Gas and Fees Explained
> What ETH on Robinhood Chain is, how to get it, what a trade or a launch actually costs, and why the wallet needs more than the trade amount.
# Robinhood Chain gas and fees explained
Robinhood Chain is an Ethereum L2 built on Arbitrum technology, chain id 4663. Gas is paid in ETH, blocks are about 100 ms apart, and a transaction costs a fraction of a cent to a few cents.
## Getting ETH onto Robinhood Chain
ETH on Ethereum mainnet, Base or Arbitrum does not count. It has to be bridged to Robinhood Chain first, through the official bridge or a third-party bridge that lists the chain. Holding USDG, NVDA or another tokenised asset does not pay for gas either. The API's `insufficient_funds` error spells this out because it is the most common first-run problem.
## What a trade costs
| | Typical gas | At 0.4 gwei |
|---|---|---|
| Buy on the curve | ~150k | ~0.00006 ETH |
| Buy on Uniswap v4 | ~250k | ~0.0001 ETH |
| One-time approval | ~50-65k | ~0.00003 ETH |
| Swap ETH into a quote asset first | ~200k | ~0.00008 ETH |
## What a launch costs
Launching deploys two contracts, so it is heavy: about 4.4 million gas, around 0.002 ETH, plus Pons's 0.0005 ETH launch fee. The API's response includes the exact figures.
## Why the wallet needs more than the trade amount
A node reserves `gasLimit × maxFeePerGas` before running a transaction and refunds what is unused. The API pads the gas limit by 20% and sets the fee ceiling at 1.25× the current base fee, so the reservation is roughly one and a half times what is actually spent. A wallet holding exactly the trade amount plus the expected gas is rejected. The API checks this before you sign, sums the whole batch when there are approvals or swaps in front, and tells you the figure to hold.
A practical rule: keep 0.01 ETH in a trading wallet on top of what you trade with.
## Where the fees go
- **Gas** to the chain.
- **Pons's curve fee and creator tax** to Pons and the creator, collected by Pons and claimable by the creator - see [Claim Creator Fees](/creator-fees).
- **Uniswap pool fees** to the pool, on graduated tokens and on the ETH conversion swaps.
Full list on [Fees](/fees).
---
# How to Snipe Pons Launches on Robinhood Chain
> A complete Node.js bot that watches the Pons launch feed, waits out the snipe tax, buys new tokens with ETH and sells at a target or a stop.
# How to snipe Pons launches
The whole loop in one script: watch every new Pons v2 launch, skip the ones you don't want, wait until the opening snipe tax is gone, buy with a fixed amount of ETH, and sell when the position hits a take-profit or a stop-loss.
## What you need
- Node.js 22 or newer (WebSocket is built in) and `npm install ethers`.
- A wallet private key with some ETH on Robinhood Chain - see [gas and fees](/tutorials/robinhood-chain-gas-and-fees).
- Ten minutes.
## Why not buy the instant a launch appears
Pons taxes buys for about five seconds after a launch, starting near 99% and decaying to zero. A bot that buys on the first event hands almost the whole order to the tax. The script below polls [`/api/token/{address}?recipient=YOUR_WALLET`](/token-info) until `snipeTaxBps` is `0`, then buys.
## The script
```js
const { Wallet } = require("ethers");
// ─── change these ─────────────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const BUY_ETH = "0.002"; // ETH per launch
const TAKE_PROFIT = 2.0; // sell when the position is worth 2x what you paid
const STOP_LOSS = 0.5; // ... or half
const MAX_POSITIONS = 3;
const ONLY_ETH_PRICED = true; // skip tokens priced in USDG, NVDA, … (they need a swap first)
const DRY_RUN = true; // quote and log, never send
// ──────────────────────────────────────────────────────
const API = "https://api.shrine.trade/rh";
const wallet = new Wallet(PRIVATE_KEY); // signs only; no node needed
const positions = new Map(); // token -> { paidEth, symbol }
const get = async (path) => (await fetch(`${API}${path}`)).json();
const post = async (path, body) =>
(await fetch(`${API}${path}`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body) })).json();
async function send(txs) {
for (const tx of txs) {
if (DRY_RUN) { console.log(" dry-run:", tx.description); continue; }
const signed = await wallet.signTransaction({
to: tx.to, data: tx.data, value: BigInt(tx.value), gasLimit: BigInt(tx.gas),
maxFeePerGas: BigInt(tx.maxFeePerGas), maxPriorityFeePerGas: BigInt(tx.maxPriorityFeePerGas),
nonce: tx.nonce, chainId: tx.chainId, type: 2,
});
const sent = await (await fetch("https://api.shrine.trade/rh/api/send", {
method: "POST", headers: { "content-type": "application/json" },
body: JSON.stringify({ signedTx: signed }),
})).json();
if (sent.error) throw new Error(`${sent.error}: ${sent.message}`);
if (sent.status !== "landed") throw new Error(`${tx.description} ${sent.status}: ${sent.explorer}`);
console.log(" " + tx.description + ": " + sent.explorer);
}
}
async function waitForSnipeTaxToClear(token) {
for (let i = 0; i < 20; i++) {
const t = await get(`/api/token/${token}?recipient=${wallet.address}`);
if (t.error) throw new Error(`${t.error}: ${t.message}`);
if (t.snipeTaxBps === 0) return t;
await new Promise((r) => setTimeout(r, 1000));
}
throw new Error("snipe tax never cleared");
}
async function onLaunch(t) {
if (positions.size >= MAX_POSITIONS || positions.has(t.token)) return;
if (ONLY_ETH_PRICED && t.pairToken !== "ETH") return;
console.log(`launch ${t.symbol} ${t.token} priced in ${t.pairToken}`);
await waitForSnipeTaxToClear(t.token);
const q = await post("/api/local-trade", { action: "buy", token: t.token, amount: BUY_ETH, from: wallet.address });
if (q.error) { console.log(" skip:", q.error, q.message); return; }
console.log(` buying ${BUY_ETH} ETH -> ~${q.quote.expectedOutFormatted} ${q.symbol}`);
await send(q.txs);
positions.set(t.token, { paidEth: Number(BUY_ETH), symbol: q.symbol });
}
async function checkPositions() {
for (const [token, pos] of positions) {
const q = await post("/api/local-trade", { action: "sell", token, amount: "100%", toEth: true, from: wallet.address });
if (q.error) continue; // insufficient_balance right after a dry-run buy is expected
const worth = Number(q.toEth ? q.toEth.ethOutFormatted : q.quote.expectedOutFormatted);
const ratio = worth / pos.paidEth;
if (ratio >= TAKE_PROFIT || ratio <= STOP_LOSS) {
console.log(`selling ${pos.symbol}: worth ${worth.toFixed(5)} ETH, ${ratio.toFixed(2)}x`);
await send(q.txs);
positions.delete(token);
}
}
}
const ws = new WebSocket(`${API.replace("https", "wss")}/api/launches/ws?protocols=PONS`);
ws.onmessage = (e) => { const t = JSON.parse(e.data); if (t.type === "new_launch") onLaunch(t).catch((err) => console.log(" error:", err.message)); };
ws.onclose = () => { console.log("feed closed"); process.exit(1); };
setInterval(() => checkPositions().catch(() => {}), 10_000);
console.log("watching launches as", wallet.address, DRY_RUN ? "(dry run)" : "");
```
Save it as `sniper.js`, then `node sniper.js`. Leave `DRY_RUN` on for the first run: it prints every launch it would buy and every transaction it would send.
## What each part does
- **The feed** delivers every launch within a second of the block. `?protocols=PONS` keeps Uniswap pool events out.
- **Filters** are yours to extend. The event carries `name`, `symbol`, `description`, `socials` and `pairToken`, so a blocklist of names or "only tokens with a website" is a one-liner.
- **The buy** is in ETH whatever the token is priced in; the API converts. `ONLY_ETH_PRICED` is on by default because a stock-priced buy needs an extra swap and approval, which costs gas and time you don't have on a snipe.
- **The sell check** quotes a full exit every ten seconds and compares it with what you paid. `toEth: true` makes the comparison honest for stock-priced tokens.
- **Nothing is stored.** Restarting the script forgets its positions. Write `positions` to a file if you run it for real.
---
# Trade Tokens Priced in NVDA, TSLA or USDG with ETH
> How Pons tokens priced in tokenised stocks work on Robinhood Chain, and how to buy and sell them holding nothing but ETH.
# Trade tokens priced in NVDA, TSLA or USDG with ETH
About half of Pons v2 launches are not priced in ETH. The creator picks one of Robinhood Chain's tokenised assets - USDG, NVDA, TSLA, SPCX, GME and the rest - and the whole curve trades in that asset. You still don't need to hold any of it.
## What "priced in NVDA" means
The token's bonding curve takes NVDA in and gives NVDA out. Its price, its graduation threshold and the creator's fees are all in NVDA. After graduation it moves to a Uniswap v4 pool paired with NVDA. Every response from the API tells you which asset in `quoteAsset`:
```json
"quoteAsset": { "address": "0xd0601CE1…", "symbol": "NVDA", "decimals": 18, "isNative": false }
```
Decimals vary: USDG is 6, cbBTC is 8. The `*Formatted` fields already account for that.
## Buying with ETH
Send the same buy as for any token, with `amount` in ETH:
```js
const q = await post("/api/local-trade", { action: "buy", token: TOKEN, amount: "0.01", from: wallet.address });
```
The API works out how much NVDA 0.01 ETH buys on Uniswap, reserves your slippage, and hands the curve that amount. If your wallet already holds enough NVDA it is used and no swap happens. Otherwise the response's `txs` opens with a swap:
| `description` | What it is |
|---|---|
| `buy_quote_with_eth` | Swap ETH for exactly the missing NVDA on Uniswap v4. Unused ETH is refunded in the same transaction. |
| `approve_quote` | One-time allowance for the router to spend your NVDA. |
| `buy` | The buy itself. |
`quoteSwap` in the response shows what the swap costs and the pools it goes through. NVDA has a direct ETH pool; an asset like RDDT routes through USDG, found on-chain each time.
## Selling back to ETH
A sell pays out in the quote asset. Add `toEth: true` to get ETH instead:
```js
const q = await post("/api/local-trade", { action: "sell", token: TOKEN, amount: "100%", toEth: true, from: wallet.address });
```
On a graduated token the sell and the conversion are one transaction. On a curve token the conversion is a second transaction of the guaranteed minimum, so a small amount of the asset can remain. `toEth` in the response says how much ETH you end up with.
## Things that bite
- **The dev buy on a launch is also in ETH** and converts the same way.
- **A first buy of any stock-priced token needs an approval**, per asset, one time. The script sends it for you.
- **Thin pools move.** Some graduated stock-priced tokens swing several percent a minute. If you see `slippage_exceeded`, retry or raise `slippage`.