跳到主要内容

如何在 Robinhood 链上狙击 Pons 新币

一个脚本跑完整个流程:监听每一次 Pons v2 发射,跳过不想要的,等开盘狙击税消失,用固定数量的 ETH 买入,持仓到达止盈或止损时卖出。

你需要什么

  • Node.js 22 以上(自带 WebSocket 客户端),并安装 ethers:npm install ethers
  • 一个钱包私钥,里面有一点 Robinhood 链上的 ETH,见 Gas 与费用
  • 十分钟。

为什么不能一看到发射就买

Pons 在发射后约五秒内对买入征税,从接近 99% 开始衰减到零。第一条事件就下单的机器人,几乎整单都交了税。下面的脚本轮询 /api/token/{address}?recipient=你的钱包,直到 snipeTaxBps0 再买。

脚本

const { Wallet } = require("ethers");

// ─── 改这些 ─────────────────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const BUY_ETH = "0.002"; // 每次发射投入的 ETH
const TAKE_PROFIT = 2.0; // 持仓价值达到买入成本 2 倍时卖出
const STOP_LOSS = 0.5; // ……或者跌到一半
const MAX_POSITIONS = 3;
const ONLY_ETH_PRICED = true; // 跳过以 USDG、NVDA 等计价的代币(要先兑换)
const DRY_RUN = true; // 只报价和打印,不发送任何交易
// ──────────────────────────────────────────────────────

const API = "https://api.shrine.trade/rh";
const wallet = new Wallet(PRIVATE_KEY); // 只用来签名,不连任何节点
const positions = new Map(); // 代币地址 -> { 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(" 演练:", 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("狙击税一直没有归零");
}

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 是正常的
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("推送已断开"); process.exit(1); };
setInterval(() => checkPositions().catch(() => {}), 10_000);
console.log("正在监听发射,钱包", wallet.address, DRY_RUN ? "(演练模式)" : "");

保存为 sniper.js,然后 node sniper.js。第一次运行时保持 DRY_RUN 开启:它会打印每一个它会买的发射和每一笔它会发的交易。

每一部分在做什么

  • 推送在区块确认一秒内送达每一次发射。?protocols=PONS 把 Uniswap 池子事件挡在外面。
  • 过滤条件随你扩展。事件里有 namesymboldescriptionsocialspairToken,所以一份名称黑名单或者"只买有官网的",一行就写完。
  • 买入用 ETH,不管代币用什么计价,API 负责兑换。ONLY_ETH_PRICED 默认开启,因为股票计价的买入要多一笔兑换和授权,狙击时既费 Gas 又费时间。
  • 持仓检查每十秒请求一次全部卖出的报价,和当初花的钱比较。toEth: true 让股票计价代币的比较也是诚实的。
  • 状态在内存里。 重启脚本就忘了持仓;用真钱之前,先把 positions 写到硬盘。