BlockReq
Back to all articles

Rpc Tutorials

Watch Robinhood opens with eth_subscribe (tape + pattern tags)

Use BlockReq public WSS eth_subscribe (newHeads + narrow logs) on Robinhood Chain, tag tape patterns, and backfill gaps—Free 3M request quota included.

BlockReq EngineeringSeptember 5, 20269 min read

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.

UseEndpoint
Public HTTPShttps://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public
Public WSSwss://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public
Private HTTPS / WSSReplace 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 EVM eth_subscribe.

1. What to watch on the open: block heartbeat / pool logs / watch-address flows

Three signal classes map cleanly onto subscriptions:

What to watchOn-chain counterpartTape feel
Block heartbeatnewHeads: height, timestamp, gasUsed / baseFeeSteady blocks? Sudden gas spikes?
Pool logsNarrow logs: Transfer / Swap / custom topicsPool just moved; same-block pin-like clusters
Watch-address flowsLogs filtered by address, or HTTP eth_getBalance / eth_getTransactionCountVaults, 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 pollingWSS eth_subscribe
Typical callseth_blockNumber, eth_getLogs, eth_getTransactionReceiptnewHeads, logs
LatencyPoll interval + rate limitsPush; closer to tape
GapsYou own fromBlock / toBlockBackfill over HTTP after disconnect (§5)
Rate-limit pressureHigh-frequency polls burn public IP / Free quotaSubs are light; reconnect storms still hit limits
FitLow-frequency dashboards, replay, gap fillReal-time heartbeat + event stream in the open window

How to choose (short):

  • Only need “still producing blocks” → poll eth_blockNumber every 30–60s.
  • Need an event stream for the open minutes → WSS newHeads + narrowly filtered logs.
  • Production correctness → subscribe for low latency; HTTP eth_getLogs for 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-20 Transfer(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 0xYourPoolOrTokenAddress with your watch contract; the demo defaults to newHeads only, with optional Transfer logs.

3.3 Public vs keyed

ItemPublic endpointPrivate (keyed)
URL…/v1/rpc/public…/v1/rpc/{API_KEY}
Rate limitBest-effort, per IPPer plan RU / req/s
Quota entryFree 3M Requests / 30 days (after signup)Starter / Growth / …
FitLearning, short open-watch demosLong-running dashboards, alerts, production subs
KeyNoneCreate 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.

Raw heads / logs become readable tape when you tag locally. Common tags and heuristics:

TagHow to catch itImplementation tip
Same-block dense tx / logsMany logs or receipts pile up under one blockNumberBucket by block; tag when count ≥ N
Same toMultiple txs / logs hit the same contract address (or receipt to)Group on address.toLowerCase()
Gas / priority spikegasUsed, baseFeePerGas, or receipt effectiveGasPrice jumps vs a sliding windowCompare to previous head / last N-block mean
Pool Transfer / Swap burstNarrow logs rate spikes inside a windowSliding 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

  1. Disconnects happen — browser sleep, network jitter, and idle kicks on public nodes are common. The demo reconnects with exponential backoff and resubscribes.
  2. 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.

  1. 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.
  2. Dedupe — use a Set of blockHash / txHash+logIndex to avoid replay spam.
  3. PendingnewPendingTransactions is noisy and may not be stably enabled on every route; prefer newHeads + narrow logs for open watching.

6. Summary + public / Free 3M

  1. Open watching = a real-time window on Robinhood Chain (0x1237): block heartbeat, pool logs, watch-address flows.
  2. Low frequency → heartbeat polling; open event streams → newHeads + narrow logs; tag locally (dense same-block, same to, gas spike, pool burst).
  3. Disconnects → eth_getLogs backfill; dedupe + chunked windows keep throughput healthy.
  4. 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.