跳到主要内容

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 inRequest to our box, one way
Chicagoabout 1 ms
US East coast15-25 ms
Western Europe90-120 ms
Asia150-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.

Your bot in Chicago, a millisecond from shrine.trade Lightspeed, which is a few milliseconds from Robinhood Chain

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.trader is 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. amount is 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.minOut shows what the transaction enforces.
  • Position cap. MAX_OPEN keeps 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.