Skip to main content

PONS WebSocket API: Real-Time Robinhood Chain Data

One socket streams every PONS event on Robinhood Chain — new token launches, curve trades and graduations from the ponsfamily.com launchpad — delivered as transactions enter the sequencer, before a block confirms.

Endpoint: wss://rhc.pumpdev.io/ws Authentication: ?key=<API_KEY> — the same key as the Solana feed. One plan covers both chains. Free without a key: launches, graduations and metadata. Trades require a key.

Want to act on what you see?

The PONS Trading API turns a newToken event into a signed buy — one POST returns an unsigned transaction with the route and the slippage floor already solved. No API key needed for it.

Robinhood Chain is an Arbitrum Orbit L2 (chain ID 4663) with no public mempool and no priority-fee auction: the sequencer orders by arrival time, so latency is the only edge. Explorer: rh-scan.com.

Quick start​

import WebSocket from 'ws';

const ws = new WebSocket('wss://rhc.pumpdev.io/ws?key=YOUR_KEY');

ws.on('open', () => {
ws.send(JSON.stringify({ method: 'subscribeNewToken' }));
ws.send(JSON.stringify({
method: 'subscribeTokenTrade',
keys: ['0xTokenAddress']
}));
});

ws.on('message', (data) => {
const event = JSON.parse(data.toString());
console.log(event.type, event.status, event.token);
});

Subscriptions​

MethodPayloadDeliversKey
subscribeNewToken—newToken, graduation, tokenMetano
subscribeTokenTradekeys: [tokenAddress]tokenTrade for those tokensyes
subscribeAccountTradekeys: [walletAddress]tokenTrade by those walletsyes

Each has a matching unsubscribe… method with the same payload. Every call is acknowledged:

{ "type": "subscribed", "method": "subscribeTokenTrade", "keys": ["0x…"] }

keys lists what was actually accepted — invalid or over-cap keys are trimmed, not refused (see SUBSCRIBE_NORMALIZED).

Event statuses: deduplicate by txHash​

Every event says where it came from, and most events arrive twice.

statusSourceMeaning
pendingsequencer calldatain flight, may revert — this is the head start
confirmedexecuted block logssettled fact

Collapse pairs by txHash. A launch arrives pending then confirmed; a trade arrives pending then confirmed ~500ms later. A pending launch has token: null — the CREATE2 address only exists after execution — so txHash is the only correlation key.

The two stages read different sources, and together they cover the whole market: pending decodes PONS router calldata for the head start, while confirmed reads every block on the chain — aggregators, the Universal Router and post-graduation pools included. Graduations are confirmed only.

Porting a pump.fun bot​

The wire contract matches the Solana feed frame for frame — same methods, same handshake, same acks. Change the URL and, usually, nothing else. Every event carries both field names:

pump.funPONS
txType: "create"type: "newToken"
txType: "buy" | "sell"type: "tokenTrade", side
signaturetxHash (an EVM transaction hash)
minttoken
traderPublicKeytrader

Both names are present in every event. The PONS names are authoritative and are what the shapes below use; the pump.fun names are aliases kept so a working bot runs unchanged.

Two things you must change
  1. Events arrive twice (pending + confirmed). A bot counting create events will double count — filter on status or collapse by txHash.
  2. Amounts are in the quote asset, not a fixed 18-decimal native token. Read quote.decimals before formatting anything.

Event shapes​

newToken — pending​

{
"type": "newToken",
"txType": "create",
"status": "pending",
"token": null,
"deployer": "0x…",
"name": "StockPacks",
"symbol": "PACKS",
"imageUri": "ipfs://…",
"description": "…",
"socials": ["https://x.com/…", "", "", "", ""],
"creator": "0x…",
"pairToken": "0x…",
"quote": { "symbol": "USDG", "decimals": 6 },
"devBuyWei": "8800000000000000",
"snipeTaxExempt": ["0x…", "0x…"],
"salt": "0x…",
"block": 1234567,
"blockTimestamp": 1789466847,
"txHash": "0x…"
}
  • blockTimestamp — the block.timestamp of the block this launch executes in, straight from the sequencer feed. The curve's snipe tax steps down per whole second from this value (9900 → 618 → 19 → 0 bps at +0/+1/+2/+3 s), so blockTimestamp + 3 is the first second a stranger buys untaxed. See The snipe tax. null when the feed did not carry it.
  • snipeTaxExempt — the wallets the creator named at launch to buy the first second tax-free: the bundle team. [] means nobody beyond the creator; null means the launch call is one we cannot read this from, not an empty list. Eight addresses here is eight wallets buying at 0% while you would pay 99%.
  • devBuyWei — the launcher's own opening buy, in quote-asset units. This is the only place a dev buy is visible (initialBuyWei on the confirmed log reports 0). "0" means no buy; null means unknown, not zero.
  • deployer — whoever called the factory: the wallet, or the launch wrapper it went through. creator is the fee recipient, not always the same.
  • salt — the CREATE2 salt; simulating the transaction returns the token address at any block.
  • Metadata (name, symbol, imageUri, …) comes from calldata, so it arrives before tokenMeta. Every key is always present: an unusual launch wrapper still delivers the event on time, with the fields it did not carry as null.

newToken — confirmed​

{
"type": "newToken",
"txType": "create",
"status": "confirmed",
"token": "0x…",
"deployer": "0x…",
"pairToken": "0x…",
"quote": { "symbol": "USDG", "decimals": 6 },
"pool": "0x…",
"initialBuyWei": "1000000000000000",
"graduationThreshold": "42000000000",
"block": 1234567,
"blockTimestamp": 1789466847,
"txHash": "0x…"
}

When the launch was also seen pending, the confirmed event repeats its metadata fields, snipeTaxExempt included. Log-derived fields (token, pairToken, pool, graduationThreshold) always win over calldata.

pool is the token's bonding curve — the contract every router swap names as its venue until graduation, and the one the trading API prices from. Its buys are taxed for the first 3 seconds after launch — 99% in the launch second, 6.18% the next, 0.19% the one after, then 0 — so a bot that acts on this event should buy from blockTimestamp + 3, or let the trade response's snipeTaxBps say what the curve would take. See The snipe tax.

initialBuyWei and graduationThreshold are in quote-asset units — assuming 18 decimals on a USDG pair is off by 10¹².

tokenTrade — pending​

{
"type": "tokenTrade",
"txType": "buy",
"status": "pending",
"token": "0x…",
"trader": "0x…",
"side": "buy",
"amountIn": "1000000",
"amountOutMin": "980000000000000000",
"quote": { "symbol": "USDG", "decimals": 6 },
"hops": 2,
"ponsVenue": true,
"txHash": "0x…"
}

amountIn is what the trader spends, amountOutMin the slippage floor. Neither is an executed amount.

tokenTrade — confirmed​

{
"type": "tokenTrade",
"txType": "buy",
"status": "confirmed",
"token": "0x…",
"trader": "0x…",
"side": "buy",
"amountToken": "41053416785771512631551",
"amountQuote": "1608718913688474",
"amountIn": "1608718913688474",
"amountOut": "41053416785771512631551",
"quote": { "symbol": "USDG", "decimals": 6, "assetClass": "erc20", "address": "0x…" },
"amountQuoteEth": "31929000000000000",
"ethRate": { "ethPerUnit": "0.000404412321388955", "source": "implied", "ageBlocks": 0, "observedBlock": 57700908 },
"venue": "0x…",
"block": 1234567,
"txHash": "0x…"
}
  • amountToken is always the token; amountQuote the other side of the swap. amountIn/amountOut state the same trade from the trader's point of view, so amountIn reads the same across both statuses.
  • amountQuoteEth — amountQuote converted to wei of ETH so tokens on different pairs are comparable. Native units are never rewritten. The rate is an observation median from the same blocks, not an oracle; source is native or implied. When no recent rate exists, amountQuoteEth and ethRate are null, never stale.
  • venue — the pool at the end of the route. Reported, never filtered on.
  • trader — the wallet or contract receiving or giving up the token. null when there is no identifiable one (an arbitrage loop, a pool-to-pool hop). Naming a pool would be worse than saying nothing.
  • amountQuote: null means unknown, never zero — the trade is delivered either way.
  • Several trades can share one txHash (~1% of transactions — bundler batches), published largest first. Collapsing by txHash keeps the largest; collapse by (txHash, trader) to keep them all. A payout split across addresses is one trade, not several.
  • Adding or removing liquidity is not published as a trade.

graduation​

{ "type": "graduation", "status": "confirmed", "token": "0x…", "block": 1234567, "txHash": "0x…" }

tokenMeta​

{ "type": "tokenMeta", "token": "0x…", "name": "Example", "symbol": "EXMPL", "decimals": 18 }

Fetched in a batch just after the launch so the launch never waits on a round trip. Any field can be null.

Quotas and keys​

EventKey requiredMetered
newTokennono
graduationnono
tokenMetanono
tokenTradeyesyes

One plan, both chains. PONS trades are recorded on their own meter but draw on the same monthly allowance as pump.fun trades; connection and subscription caps are shared too. Both metered statuses count, so a busy token spends quota faster than its number of distinct trades — budget for frames, not for trades.

Create a key at My Account; tiers and prices are on the pricing page.

Errors and control frames​

Everything arrives as a normal message on the same socket:

{ "type": "error", "code": "CONNECTION_LIMIT", "message": "This key allows 2 simultaneous connections on the starter plan." }

Switch on code, show message. (error duplicates message for older clients; new code should ignore it.)

codeWhenSocket
DEMO_LIMITtoo many landing-page demo sockets from one IPclosed, 1013
CONNECTION_LIMITover your plan's per-key or per-IP capclosed, 1013
SERVER_AT_CAPACITYthe service is fullclosed, 1013
INVALID_JSONmessage is not JSONstays open
UNKNOWN_METHODunknown methodstays open

Cap refusals are queued before the close and carry upgradeUrl (CONNECTION_LIMIT also carries tier) — read the frame, not the close reason. A close with 1013 / slow consumer means your client stopped reading fast enough.

Non-error control frames:

  • connected + connectionStatus — sent in that order on connect; connectionStatus repeats whenever the upstream feed drops or returns, so you can tell a quiet chain from a dead feed.
  • notice / SUBSCRIBE_NORMALIZED — partial success, not a rejection: valid keys are subscribed and the ack follows. Retrying the batch will not help. invalid counts keys this feed cannot parse and truncated counts a batch clipped by your plan's batch size — a tier never shows up as invalid, so truncated: 0 rules your plan out. When the keys are well-formed but belong to the Solana feed, reason is WRONG_CHAIN_ENDPOINT and useEndpoint names the URL to use (wss://pumpdev.io/ws); invalidKeys lists what was dropped.
  • quota / exceeded — monthly trade allowance spent. Trades stop; launches, graduations and metadata keep flowing, socket stays open.
  • auth / free | ok — your key's tier changed (expired, revoked, renewed, upgraded), at any time or on connect. On free you are moved down, not disconnected; trimmedSubscriptions says how many were dropped.
  • notice / NO_API_KEY — connected with no key at all. Launches still stream, metered by IP.

Good to know​

  • pending is intent, confirmed is settlement. A pending trade is decoded before execution, so settle on the confirmed event and use the pending one for the head start.
  • Confirmed trades stream from the moment you subscribe, including a trade in the same block as the subscription itself.
  • Any PONS token works, at any age. Unknown tokens are verified against the PONS factory (getLaunchedToken, via Multicall3) before trades flow, typically within a fraction of a second, and RPC hiccups are retried rather than written off — graduated tokens included.

Frequently asked questions​

Do I need an API key to stream PONS launches?​

No. subscribeNewToken works with no key and is not metered — launches, graduations and token metadata are free on every plan. A key is required only for subscribeTokenTrade and subscribeAccountTrade.

Does streaming PONS use up my pump.fun quota?​

Yes, for trade events. One plan covers both chains and they share a single monthly trade allowance. Launches, graduations and metadata never count.

Can I subscribe to a PONS token that launched weeks ago?​

Yes. Unknown tokens are verified against the PONS factory on chain, so age does not matter — graduated tokens included.

What is the difference between a pending and a confirmed trade?​

A pending trade is decoded from the sequencer before execution — that is the head start. The confirmed event, from the executed block, is the settled fact and carries the executed amounts. Act early on pending, settle on confirmed.

What quote assets do PONS launches use?​

Several, with different decimals: many pair against USDG (6 decimals), others against ETH, WETH or tokenized equities and ETFs (NVDA, TSLA, QQQ, GLD and dozens more, all 18-decimal). Always read the quote object; pairToken always gives the address of the asset a token is paired against.

Is there a REST API for trading on PONS?​

Yes. POST https://rhc.pumpdev.io/api/trade-local builds an unsigned EVM transaction you sign locally and broadcast yourself — the route, the gas and the slippage floor are solved for you, and no API key is required. See the PONS Trading API. Token creation is still on the roadmap.