How to copy trade with ⚡ Lightspeed
Follow one or more wallets, and when one of them buys a Pons token, buy it too, a few milliseconds later; when they sell, sell. Everything runs through Lightspeed: the stream shows their trade the moment the chain orders it, before any node has the block, and one request sends yours from the same box.
What you need
- Node.js 22 or newer (WebSocket is built in). Nothing else: no RPC, no signing library.
- A Lightspeed account funded with ETH on Robinhood Chain, and a stream key (ask in the Telegram group; the account key works for the stream too).
- A wallet to follow. The advanced data stream and the live launches feed are good places to spot one.
- Ideally a server in Chicago (below).
The fastest connection
Lightspeed runs in Chicago, a few milliseconds from Robinhood Chain's own servers. Your bot's only round trip is to us, so where it runs is most of its latency:
| Your bot runs in | Request to our box, one way |
|---|---|
| Chicago | about 1 ms |
| US East coast | 15-25 ms |
| Western Europe | 90-120 ms |
| Asia | 150-250 ms |
A copy trade that leaves a Chicago VPS lands a block or two behind the trade it copies; one from Europe is ten blocks behind (Robinhood Chain makes about ten blocks a second). Any provider with a Chicago region will do. Keep one WebSocket open to the stream, and keep the trade connection warm by sending a request now and then (a balance read is enough), so the first real trade doesn't pay for a TLS handshake.
The script
// ─── change these ─────────────────────────────────────
const LS_KEY = "ls_…"; // your Lightspeed account key
const STREAM_KEY = LS_KEY; // or a stream key from the Telegram group
const FOLLOW = ["0xWALLET_TO_COPY"]; // one or more wallets
const BUY_ETH = 0.005; // ETH per copied buy, whatever they spent
const MAX_OPEN = 5; // positions at once
const SLIPPAGE = 10; // percent; copies land in the same blocks as the original
const DRY_RUN = true; // log what would be sent, send nothing
// ──────────────────────────────────────────────────────
const LS = "https://rh-lightspeed.shrine.trade";
const open = new Map(); // token -> { symbol, since }
async function trade(body) {
if (DRY_RUN) { console.log(" dry-run:", JSON.stringify(body)); return { success: true, dryRun: true }; }
const r = await (await fetch(`${LS}/api/lightspeed`, {
method: "POST",
headers: { "content-type": "application/json", "x-api-key": LS_KEY },
body: JSON.stringify(body),
})).json();
if (!r.success) throw new Error(`${r.error}: ${r.message}`);
console.log(` ${body.action} sent in ${r.sendMs} ms: ${r.explorer}`);
return r;
}
function connect() {
const url = `${LS.replace("https", "wss")}/api/lightspeed/stream?key=${STREAM_KEY}&wallets=${FOLLOW.join(",")}`;
const ws = new WebSocket(url);
ws.onopen = () => console.log("following", FOLLOW.join(", "));
ws.onclose = () => { console.log("stream closed, reconnecting"); setTimeout(connect, 1000); };
ws.onmessage = async (m) => {
const e = JSON.parse(m.data);
if (e.type !== "trade") return;
// Our own trades show up here too: never copy ourselves.
if (!FOLLOW.map((w) => w.toLowerCase()).includes(e.trader.toLowerCase())) return;
const name = e.symbol || e.token.slice(0, 10);
try {
if (e.side === "buy" && !open.has(e.token)) {
if (open.size >= MAX_OPEN) { console.log(`skip ${name}: ${MAX_OPEN} positions open`); return; }
console.log(`${e.trader.slice(0, 8)} bought ${name} for ${e.amountIn.formatted} ${e.quoteAsset.symbol} on ${e.venue}`);
open.set(e.token, { symbol: name, since: Date.now() });
await trade({ action: "buy", token: e.token, amount: BUY_ETH, slippage: SLIPPAGE });
} else if (e.side === "sell" && open.has(e.token)) {
console.log(`${e.trader.slice(0, 8)} sold ${name}`);
open.delete(e.token);
await trade({ action: "sell", token: e.token, amount: "100%", slippage: SLIPPAGE });
}
} catch (err) {
console.error(` ${name}: ${err.message}`);
if (e.side === "buy") open.delete(e.token);
}
};
}
// A warm connection to the trade endpoint: the first real request then
// skips the TLS handshake.
setInterval(() => fetch(`${LS}/api/lightspeed/account`, { headers: { "x-api-key": LS_KEY } }).catch(() => {}), 30_000);
connect();
Run it with DRY_RUN = true first: it prints every trade it would copy. Flip it to false when the picks look right.
What each part does
- The stream filter.
wallets=in the URL means the socket carries only what those wallets do, on any Pons token, curve or pool.e.traderis the wallet that sent the transaction, even when it went through a router. Trades arrive as the chain orders them, so they can still revert; a copy of a trade that reverts usually reverts for the same reason and costs only gas. - The buy.
amountis ETH whatever the token is priced in. A token priced in USDG or a stock is bought with ETH in one transaction, so a copy of a stock-priced buy needs nothing extra. The response carries the expected fill and the hash; the trade was accepted by the chain when the request returns. - The sell.
"100%"sells the whole balance, with proceeds in ETH. A sell by the copied wallet that you never bought into is ignored. - Slippage. Your copy lands in the same block or the next, right behind the trade it copies and often behind other copiers. 10% is a floor on a fresh launch; the response's
quote.minOutshows what the transaction enforces. - Position cap.
MAX_OPENkeeps a busy wallet from draining yours in a minute. Add your own exits (a take-profit from the stream's trades on tokens you hold, a time limit) on top.
Fees
0.75% on each Lightspeed trade, inside the transaction, plus Robinhood Chain gas of a few millionths of an ETH. See Fees.