Robinhood 抢开盘怎么盯:eth_subscribe 看盘 + 打标签
抢开盘时,盘口节奏往往先体现在链上:区块心跳、池子 logs 突发、观察地址进出。这篇用 BlockReq 公共 RPC(Free 3M 请求配额)把「打狗盘面」做成可订阅的监听栈——eth_subscribe 推流 + 模式打标签,方便看板与告警接线。
| 用途 | Endpoint |
|---|---|
| 公共 HTTPS | https://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public |
| 公共 WSS | wss://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public |
| 私有 HTTPS / WSS | 把末尾 public 换成你的 {API_KEY}(Key 放本地配置) |
链身份:EVM,chainId = 0x1237(十进制 4663)。文档:公开端点 · eth_subscribe · Supported Networks · 套餐与定价(Free 3M Requests / 30 days)。
若你要盯的是 Solana 新 mint(另一条链上的发射窗口),可交叉阅读:浏览器里监听 Solana 新币发射(
logsSubscribe)。本文默认 Robinhood Chain 的 EVMeth_subscribe。
1. 抢开盘盯什么:区块心跳 / 池子 logs / 观察地址进出
开盘窗口里,三类信号最适合直接挂到订阅上:
| 盯什么 | 链上对应 | 盘面语感 |
|---|---|---|
| 区块心跳 | newHeads:高度、时间戳、gasUsed / baseFee | 出块稳不稳、gas 有没有突然拉高 |
| 池子 logs | 窄过滤的 logs:Transfer / Swap / 自定义 topic | 池子突然动了、同块插针般的事件簇 |
| 观察地址进出 | 指定 address 的 logs,或 HTTP eth_getBalance / eth_getTransactionCount | 金库、做市账户、你自己的 watch list |
把每条推送落成结构化字段(blockNumber、txHash、address、topics),后面打标签、告警、对账都好接。
2. 轮询 vs WSS
| HTTPS 轮询 | WSS eth_subscribe | |
|---|---|---|
| 典型调用 | eth_blockNumber、eth_getLogs、eth_getTransactionReceipt | newHeads、logs |
| 延迟 | 取决于轮询间隔 + 限流 | 推送,更贴盘口节奏 |
| 空窗 | 你自己控 fromBlock / toBlock | 断线后用 HTTP 回补(见第 5 节) |
| 限流压力 | 高频轮询容易打满 public IP / Free 配额 | 订阅本身轻;重连风暴仍会撞限流 |
| 适用 | 低频看板、对账回放、补洞 | 抢开盘几分钟的实时心跳与事件流 |
怎么选(短):
- 只确认「还在出块」→ 30–60s 轮询
eth_blockNumber往往够用。 - 开盘窗口要盯事件流 → WSS 订
newHeads+ 窄过滤logs。 - 生产正确性 → 订阅负责低延迟,HTTP
eth_getLogs负责断线回补。
3. BlockReq public(Free 3M):eth_subscribe newHeads + 窄 logs;keyed;StackBlitz
EVM 侧方法名是 eth_subscribe(WSS only)。Solana 的 logsSubscribe 是另一套协议,别混在同一个 client 里硬套。
公共 endpoint 适合打开页面盯几分钟;账号侧 Free 套餐带 3M Requests / 30 days,长期 7×24 心跳可换成 keyed(Dashboard 创建 Key,路由确认 WSS 已开)。
3.1 订新头 newHeads
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_subscribe",
"params": ["newHeads"]
}
成功后先收到 subscription id,随后推送 eth_subscription,params.result 里是区块头字段(number、timestamp、hash、gasUsed 等)。
3.2 订日志 logs(窄过滤)
{
"jsonrpc": "2.0",
"id": 2,
"method": "eth_subscribe",
"params": [
"logs",
{
"address": "0xYourPoolOrTokenAddress",
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
]
}
]
}
说明:
topics[0]示例是 ERC-20Transfer(address,address,uint256)的事件签名哈希;换成你真正关心的 Swap / Mint 事件即可。- 务必加
address(或尽量窄的 topics)。裸订全链 logs 噪音大,也更容易撞 public / Free 配额。 - 把
0xYourPoolOrTokenAddress换成你自己要观察的合约;下方 demo 默认只订newHeads,可选勾选 Transfer logs。
3.3 public vs keyed
| 项 | 公共 endpoint | 私有(keyed) |
|---|---|---|
| URL | …/v1/rpc/public | …/v1/rpc/{API_KEY} |
| 限速 | Best-effort,按 IP | 按套餐 RU / req/s |
| 配额入口 | Free 3M Requests / 30 days(注册后) | Starter / Growth / … |
| 适合 | 学习、短时抢开盘盯盘 demo | 长期看板、告警、生产订阅 |
| Key | 无 | Dashboard 创建;放本地环境变量 |
3.4 浏览器可跑:单文件 HTML(默认 public)
把下面存成 robinhood-open-watch.html,用本地静态服务器打开(部分浏览器对 file:// 的 WebSocket 更挑,推荐 npx serve)。只连 public,无 Key。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>Robinhood Chain 抢开盘盯盘(newHeads + 打标签)</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; }
.tag { display: inline-block; background: #fef3c7; color: #92400e;
padding: 0.05rem 0.4rem; border-radius: 4px; font-size: 11px; margin-left: 0.35rem; }
</style>
</head>
<body>
<h1>Robinhood Chain · 抢开盘盯盘</h1>
<p>公共 WSS · <code>eth_subscribe → newHeads</code> · 可选 Transfer logs · 模式打标签</p>
<p>
WSS:<code>wss://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public</code><br />
Free 3M 配额够跑短时 demo;长期看板换 keyed。
</p>
<label>
<input type="checkbox" id="alsoLogs" />
同时订 Transfer logs(需填写 address)
</label>
<p>
<input id="watchAddress" placeholder="0x… 合约地址(可选)" 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尖峰");
}
if (gasUsed != null) lastGasUsed = gasUsed;
const n = parseInt(r.number, 16);
const logCount = (logsByBlock.get(n) || []).length;
if (logCount >= 3) tags.push("同块密集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("同块插针");
const sameTo = arr.filter((x) => (x.address || "").toLowerCase() === (r.address || "").toLowerCase());
if (sameTo.length >= 2) tags.push("同一to突发");
}
tags.push("池子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: 请填写合法 0x 地址");
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>
在线示例已落到 blockreq/blog-demos 的 examples/robinhood-open-watch(main 分支)。换 keyed:只改 WSS 常量为 wss://robinhood-mainnet-rpc.blockreq.com/v1/rpc/{API_KEY},Key 放本地环境变量。
4. 识别相关模式(打标签)
订阅推过来的是原始头 / log;「打狗盘面」的可读性来自 本地打标签。常见标签与判定启发式:
| 标签 | 信号怎么抓 | 实现提示 |
|---|---|---|
| 同块密集 tx / logs | 同一 blockNumber 短时堆积多条 log 或 receipt | 按块号桶计数,阈值 ≥ N 打标 |
| 同一 to | 多笔 tx / log 指向同一合约 address(或 receipt to) | 分组 address.toLowerCase() |
| gas / priority 尖峰 | gasUsed、baseFeePerGas、或 receipt 的 effectiveGasPrice 相对滑动窗口陡升 | 与上一头 / 近 N 块均值比较 |
| 池子 Transfer / Swap 突发 | 窄 logs 在窗口内速率突增 | 按 topic0 + address 做滑动计数 |
标签是 解释层:把 JSON 推送变成看板色块、告警文案、Webhook payload 字段。阈值按你盯的池子自己调;demo 里用了简易阈值方便上手。
进阶可以再订一层:对打了「同块插针」的块号,补一次 eth_getBlockByNumber(…, true) 看同块 tx 列表,或对相关 hash 拉 eth_getTransactionReceipt 核对 to / gas。
5. 断线重连、限流、eth_getLogs 回补
- 断线必发生 — 浏览器休眠、网络抖动、公共节点踢空闲连接都很常见。Demo 用指数退避重连并重新 subscribe。
- 空窗要回补 — 记下最后处理的
blockNumber,重连后用 HTTPS:
{
"jsonrpc": "2.0",
"id": 3,
"method": "eth_getLogs",
"params": [{
"fromBlock": "0xFROM",
"toBlock": "0xTO",
"address": "0xYourPoolOrTokenAddress",
"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]
}]
}
POST 到 https://robinhood-mainnet-rpc.blockreq.com/v1/rpc/public。窗口按块分段,避免一次拉太大撞 public / Free 配额。
- 限流表现 — HTTP 429 / 空响应 / WSS 秒断。对策:收窄订阅、拉长回补间隔、开盘高峰换 keyed、同一 IP 少开「全链 logs」。
- 去重 — 用
blockHash/txHash+logIndexSet,避免重放刷屏。 - pending —
newPendingTransactions噪音大,且路由未必稳定开启;抢开盘优先newHeads+ 窄logs。
6. 小结 + public / Free 3M
- 抢开盘盯盘 = Robinhood Chain(
0x1237)上的实时窗口:区块心跳、池子 logs、观察地址进出。 - 低频用心跳轮询;开盘事件流用
newHeads+ 窄过滤logs;本地打标签(同块密集、同一 to、gas 尖峰、池子突发)。 - 断线用
eth_getLogs回补;去重 + 分段窗口保吞吐。 - 公共 WSS / HTTPS 可直接跑 demo;账号 Free 3M 请求配额够短时盯盘;长期看板换 keyed。
下一步通常是:固定观察地址列表 → 把标签接到 Webhook / 看板 → 把回补 checkpoint 落盘。公共入口与配额说明见 公开端点 与 套餐与定价。