Watch Robinhood opens with eth_subscribe (tape + pattern tags)
When the open hits, tape rhythm shows up on-chain first: block heartbeat, pool log bursts, and watch-address flows. This post uses BlockReq public RPC (Free 3M request quota) to turn that into a subscribe stack—eth_subscribe push plus local pattern tags—ready for dashboards and alerts.
| Use | Endpoint |
|---|---|
| Public HTTPS | https://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public |
| Public WSS | wss://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public |
| Private HTTPS / WSS | Replace trailing public with your {API_KEY} (keep keys in local config) |
Chain identity: EVM, chainId = 0x1237 (decimal 4663). Docs: Public endpoints · eth_subscribe · Supported Networks · Plans & pricing (Free 3M Requests / 30 days).
If what you want to watch is a Solana new mint (a launch window on another chain), cross-read: Watch new Solana mints in the browser (
logsSubscribe). This post defaults to Robinhood Chain’s EVMeth_subscribe.
1. What to watch on the open: block heartbeat / pool logs / watch-address flows
Three signal classes map cleanly onto subscriptions:
| What to watch | On-chain counterpart | Tape feel |
|---|---|---|
| Block heartbeat | newHeads: height, timestamp, gasUsed / baseFee | Steady blocks? Sudden gas spikes? |
| Pool logs | Narrow logs: Transfer / Swap / custom topics | Pool just moved; same-block pin-like clusters |
| Watch-address flows | Logs filtered by address, or HTTP eth_getBalance / eth_getTransactionCount | Vaults, MM accounts, your watch list |
Persist each push as structured fields (blockNumber, txHash, address, topics) so tagging, alerts, and reconciliation stay easy.
2. Polling vs WSS
| HTTPS polling | WSS eth_subscribe | |
|---|---|---|
| Typical calls | eth_blockNumber, eth_getLogs, eth_getTransactionReceipt | newHeads, logs |
| Latency | Poll interval + rate limits | Push; closer to tape |
| Gaps | You own fromBlock / toBlock | Backfill over HTTP after disconnect (§5) |
| Rate-limit pressure | High-frequency polls burn public IP / Free quota | Subs are light; reconnect storms still hit limits |
| Fit | Low-frequency dashboards, replay, gap fill | Real-time heartbeat + event stream in the open window |
How to choose (short):
- Only need “still producing blocks” → poll
eth_blockNumberevery 30–60s. - Need an event stream for the open minutes → WSS
newHeads+ narrowly filteredlogs. - Production correctness → subscribe for low latency; HTTP
eth_getLogsfor disconnect backfill.
3. BlockReq public (Free 3M): eth_subscribe newHeads + narrow logs; keyed; StackBlitz
On EVM the method is eth_subscribe (WSS only). Solana’s logsSubscribe is a different protocol—don’t force both into one client.
Public endpoints fit opening a page for a few minutes; account Free includes 3M Requests / 30 days. For 24×7 heartbeats, switch to keyed (create a key in Dashboard; confirm WSS is enabled on that route).
3.1 Subscribe to new heads newHeads
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_subscribe",
"params": ["newHeads"]
}
On success you first get a subscription id, then eth_subscription pushes with header fields in params.result (number, timestamp, hash, gasUsed, etc.).
3.2 Subscribe to logs logs (narrow filter)
{
"jsonrpc": "2.0",
"id": 2,
"method": "eth_subscribe",
"params": [
"logs",
{
"address": "0xYourPoolOrTokenAddress",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
]
}
]
}
Notes:
- The sample
topics[0]is the ERC-20Transfer(address,address,uint256)event signature hash; swap it for the Swap / Mint event you care about. - Always add
address(or the narrowest topics you can). Whole-chain logs are noisy and chew public / Free quota faster. - Replace
0xYourPoolOrTokenAddresswith your watch contract; the demo defaults tonewHeadsonly, with optional Transfer logs.
3.3 Public vs keyed
| Item | Public endpoint | Private (keyed) |
|---|---|---|
| URL | …/v1/rpc/public | …/v1/rpc/{API_KEY} |
| Rate limit | Best-effort, per IP | Per plan RU / req/s |
| Quota entry | Free 3M Requests / 30 days (after signup) | Starter / Growth / … |
| Fit | Learning, short open-watch demos | Long-running dashboards, alerts, production subs |
| Key | None | Create in Dashboard; keep in local env |
More: Public endpoints · Plans & pricing.
3.4 Browser-runnable: single-file HTML (defaults to public)
Save as robinhood-open-watch.html and open via a local static server (some browsers are picky about WebSocket on file://; prefer npx serve). Connects only to public—no key.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Robinhood Chain open watch (newHeads + tags)</title>
<style>
body { font-family: ui-sans-serif, system-ui, sans-serif; margin: 1.5rem; max-width: 52rem; }
button { margin-right: 0.5rem; }
#status { margin: 0.75rem 0; color: #334155; }
pre { background: #0f172a; color: #e2e8f0; padding: 1rem; min-height: 16rem;
overflow: auto; font-size: 12px; border-radius: 8px; }
code { background: #f1f5f9; padding: 0.1rem 0.35rem; border-radius: 4px; }
</style>
</head>
<body>
<h1>Robinhood Chain · open watch</h1>
<p>Public WSS · <code>eth_subscribe → newHeads</code> · optional Transfer logs · pattern tags</p>
<p>
WSS: <code>wss://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public</code><br />
Free 3M quota covers short demos; use keyed for long-running boards.
</p>
<label>
<input type="checkbox" id="alsoLogs" />
Also subscribe Transfer logs (requires address)
</label>
<p>
<input id="watchAddress" placeholder="0x… contract address (optional)" size="48" />
</p>
<button id="start" type="button">Start</button>
<button id="stop" type="button" disabled>Stop</button>
<button id="clear" type="button">Clear</button>
<div id="status">Idle</div>
<pre id="out"></pre>
<script>
const WSS = "wss://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public";
const TRANSFER =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
const out = document.getElementById("out");
const status = document.getElementById("status");
let ws = null;
let nextId = 1;
let backoffMs = 1000;
let wantRun = false;
const seen = new Set();
const headsByBlock = new Map();
const logsByBlock = new Map();
let lastGasUsed = null;
function log(line) {
const t = new Date().toISOString().slice(11, 19);
out.textContent = `[${t}] ${line}\n` + out.textContent;
}
function setStatus(s) { status.textContent = s; }
function send(method, params) {
const id = nextId++;
ws.send(JSON.stringify({ jsonrpc: "2.0", id, method, params }));
return id;
}
function tagHead(r) {
const tags = [];
const gasUsed = r.gasUsed ? parseInt(r.gasUsed, 16) : null;
if (gasUsed != null && lastGasUsed != null && gasUsed > lastGasUsed * 1.5) {
tags.push("gas-spike");
}
if (gasUsed != null) lastGasUsed = gasUsed;
const n = parseInt(r.number, 16);
const logCount = (logsByBlock.get(n) || []).length;
if (logCount >= 3) tags.push("dense-logs");
return tags;
}
function tagLog(r) {
const tags = [];
const n = r.blockNumber ? parseInt(r.blockNumber, 16) : null;
if (n != null) {
const arr = logsByBlock.get(n) || [];
arr.push(r);
logsByBlock.set(n, arr);
if (arr.length >= 3) tags.push("same-block-cluster");
const sameTo = arr.filter((x) => (x.address || "").toLowerCase() === (r.address || "").toLowerCase());
if (sameTo.length >= 2) tags.push("same-to-burst");
}
tags.push("pool-transfer");
return tags;
}
function subscribeAll() {
send("eth_subscribe", ["newHeads"]);
log("sent eth_subscribe newHeads");
const addr = document.getElementById("watchAddress").value.trim();
if (document.getElementById("alsoLogs").checked) {
if (!/^0x[a-fA-F0-9]{40}$/.test(addr)) {
log("skip logs: enter a valid 0x address");
return;
}
send("eth_subscribe", ["logs", { address: addr, topics: [TRANSFER] }]);
log("sent eth_subscribe logs Transfer @ " + addr);
}
}
function connect() {
if (!wantRun) return;
setStatus("connecting…");
ws = new WebSocket(WSS);
ws.onopen = () => {
backoffMs = 1000;
setStatus("connected · subscribed");
subscribeAll();
};
ws.onmessage = (ev) => {
let msg;
try { msg = JSON.parse(ev.data); } catch { return; }
if (msg.id && msg.result && typeof msg.result === "string") {
log("subscription id=" + msg.result);
return;
}
if (msg.method !== "eth_subscription") return;
const r = msg.params && msg.params.result;
if (!r) return;
if (r.number) {
const n = parseInt(r.number, 16);
const key = "b:" + r.hash;
if (seen.has(key)) return;
seen.add(key);
headsByBlock.set(n, r);
const tags = tagHead(r);
const tagStr = tags.length ? " [" + tags.join(",") + "]" : "";
log(`head #${n} hash=${r.hash.slice(0, 12)}… ts=${parseInt(r.timestamp, 16)}${tagStr}`);
return;
}
if (r.transactionHash) {
const key = "l:" + r.transactionHash + ":" + r.logIndex;
if (seen.has(key)) return;
seen.add(key);
const tags = tagLog(r);
const tagStr = tags.length ? " [" + tags.join(",") + "]" : "";
log(`log tx=${r.transactionHash.slice(0, 12)}… addr=${r.address}${tagStr}`);
}
};
ws.onclose = () => {
setStatus("closed");
if (!wantRun) return;
setStatus(`reconnect in ${backoffMs}ms`);
setTimeout(connect, backoffMs);
backoffMs = Math.min(backoffMs * 2, 30000);
};
ws.onerror = () => { try { ws.close(); } catch {} };
}
document.getElementById("start").onclick = () => {
wantRun = true;
document.getElementById("start").disabled = true;
document.getElementById("stop").disabled = false;
connect();
};
document.getElementById("stop").onclick = () => {
wantRun = false;
document.getElementById("start").disabled = false;
document.getElementById("stop").disabled = true;
if (ws) try { ws.close(); } catch {}
setStatus("stopped");
};
document.getElementById("clear").onclick = () => {
out.textContent = "";
seen.clear();
logsByBlock.clear();
lastGasUsed = null;
};
</script>
</body>
</html>
The live example lives under blockreq/blog-demos at examples/robinhood-open-watch on main. To switch to keyed: change only the WSS constant to wss://robinhood-mainnet-rpc.blockreq.com/v1/rpc/{API_KEY} and keep the key in local env.
4. Identify related patterns (tagging)
Raw heads / logs become readable tape when you tag locally. Common tags and heuristics:
| Tag | How to catch it | Implementation tip |
|---|---|---|
| Same-block dense tx / logs | Many logs or receipts pile up under one blockNumber | Bucket by block; tag when count ≥ N |
Same to | Multiple txs / logs hit the same contract address (or receipt to) | Group on address.toLowerCase() |
| Gas / priority spike | gasUsed, baseFeePerGas, or receipt effectiveGasPrice jumps vs a sliding window | Compare to previous head / last N-block mean |
| Pool Transfer / Swap burst | Narrow logs rate spikes inside a window | Sliding count by topic0 + address |
Tags are an interpretation layer: turn JSON pushes into dashboard chips, alert copy, and Webhook payload fields. Tune thresholds per pool; the demo uses simple counts to get started.
Next step up: for blocks tagged “same-block-cluster”, fetch eth_getBlockByNumber(…, true) for the full tx list, or pull eth_getTransactionReceipt on related hashes to confirm to / gas.
5. Reconnect, rate limits, eth_getLogs backfill
- Disconnects happen — browser sleep, network jitter, and idle kicks on public nodes are common. The demo reconnects with exponential backoff and resubscribes.
- Backfill gaps — remember the last processed
blockNumber, then after reconnect use HTTPS:
{
"jsonrpc": "2.0",
"id": 3,
"method": "eth_getLogs",
"params": [{
"fromBlock": "0xFROM",
"toBlock": "0xTO",
"address": "0xYourPoolOrTokenAddress",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
}]
}
POST to https://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public. Chunk by block so you don’t burn public / Free quota on one huge window.
- Rate-limit symptoms — HTTP 429 / empty responses / WSS drops within seconds. Mitigations: narrow subscriptions, lengthen backfill intervals, switch to keyed at open peaks, and avoid a dozen “all logs” subs from one IP.
- Dedupe — use a Set of
blockHash/txHash+logIndexto avoid replay spam. - Pending —
newPendingTransactionsis noisy and may not be stably enabled on every route; prefernewHeads+ narrowlogsfor open watching.
6. Summary + public / Free 3M
- Open watching = a real-time window on Robinhood Chain (
0x1237): block heartbeat, pool logs, watch-address flows. - Low frequency → heartbeat polling; open event streams →
newHeads+ narrowlogs; tag locally (dense same-block, sameto, gas spike, pool burst). - Disconnects →
eth_getLogsbackfill; dedupe + chunked windows keep throughput healthy. - Public WSS / HTTPS run the demo immediately; account Free 3M request quota covers short open watches; long-running boards use keyed.
Typical next steps: fix a watch-address list → wire tags into Webhooks / dashboards → persist backfill checkpoints. Public entry and quota details: Public endpoints and Plans & pricing.