Skip to main content

How to snipe Pons launches

The whole loop in one script: watch every new Pons v2 launch, skip the ones you don't want, wait until the opening snipe tax is gone, buy with a fixed amount of ETH, and sell when the position hits a take-profit or a stop-loss.

What you need

  • Node.js 22 or newer (WebSocket is built in) and npm install ethers.
  • A wallet private key with some ETH on Robinhood Chain - see gas and fees.
  • Ten minutes.

Why not buy the instant a launch appears

Pons taxes buys for about five seconds after a launch, starting near 99% and decaying to zero. A bot that buys on the first event hands almost the whole order to the tax. The script below polls /api/token/{address}?recipient=YOUR_WALLET until snipeTaxBps is 0, then buys.

The script

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

// ─── change these ─────────────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const BUY_ETH = "0.002"; // ETH per launch
const TAKE_PROFIT = 2.0; // sell when the position is worth 2x what you paid
const STOP_LOSS = 0.5; // ... or half
const MAX_POSITIONS = 3;
const ONLY_ETH_PRICED = true; // skip tokens priced in USDG, NVDA, … (they need a swap first)
const DRY_RUN = true; // quote and log, never send
// ──────────────────────────────────────────────────────

const API = "https://api.shrine.trade/rh";
const wallet = new Wallet(PRIVATE_KEY); // signs only; no node needed
const positions = new Map(); // token -> { 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(" dry-run:", 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("snipe tax never cleared");
}

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 right after a dry-run buy is expected
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("feed closed"); process.exit(1); };
setInterval(() => checkPositions().catch(() => {}), 10_000);
console.log("watching launches as", wallet.address, DRY_RUN ? "(dry run)" : "");

Save it as sniper.js, then node sniper.js. Leave DRY_RUN on for the first run: it prints every launch it would buy and every transaction it would send.

What each part does

  • The feed delivers every launch within a second of the block. ?protocols=PONS keeps Uniswap pool events out.
  • Filters are yours to extend. The event carries name, symbol, description, socials and pairToken, so a blocklist of names or "only tokens with a website" is a one-liner.
  • The buy is in ETH whatever the token is priced in; the API converts. ONLY_ETH_PRICED is on by default because a stock-priced buy needs an extra swap and approval, which costs gas and time you don't have on a snipe.
  • The sell check quotes a full exit every ten seconds and compares it with what you paid. toEth: true makes the comparison honest for stock-priced tokens.
  • Nothing is stored. Restarting the script forgets its positions. Write positions to a file if you run it for real.