Skip to content

A launch buyer with limits

Example

This is an example. Every value in it (names, amounts, addresses) is there to show you how; change any of them to suit you. It’s recipe-launch-buyer.mjs from developers/examples, as it is. Anything that sends waits for SEND=1: there is no testnet, so a sent transaction spends real ETH.

recipe-launch-buyer.mjs
// A bot that buys new launches, within limits you set: only coins whose bundled wallets and snipers hold little,
// a fixed amount each, at most MAX_BUYS, never a coin whose round trip loses more than MAX_ROUND_TRIP_LOSS_PCT.
// It earns its builder a fee on every buy it makes (feeBps to FEE_RECIPIENT). Without SEND=1 it only reports.
// PRIVATE_KEY=0x… SPEND=0.001 MAX_BUYS=3 FEE_RECIPIENT=0x… SEND=1 node recipe-launch-buyer.mjs
import { JsonRpcProvider, Wallet } from 'ethers';
const API = process.env.UPRALLY_API ?? 'https://uprally.co/api/v1';
const SPEND = process.env.SPEND ?? '0.001';
const MAX_BUYS = Number(process.env.MAX_BUYS ?? 3);
const MAX_BUNDLED = Number(process.env.MAX_BUNDLED_PCT ?? 10);
const MAX_SNIPERS = Number(process.env.MAX_SNIPERS_PCT ?? 5);
const MAX_LOSS = Number(process.env.MAX_ROUND_TRIP_LOSS_PCT ?? 10);
const stopAt = Date.now() + Number(process.env.SECONDS ?? 300) * 1000;
const chain = await (await fetch(`${API}/chain`)).json();
const wallet = new Wallet(process.env.PRIVATE_KEY, new JsonRpcProvider(chain.rpc, chain.chainId));
const fee = process.env.FEE_RECIPIENT ? { feeBps: 50, feeRecipient: process.env.FEE_RECIPIENT } : {};
let since = (await (await fetch(`${API}/coins/new?limit=1`)).json()).coins[0]?.createdAt ?? new Date().toISOString();
let bought = 0;
while (bought < MAX_BUYS && Date.now() < stopAt) {
const { coins = [] } = await (await fetch(`${API}/coins/new?since=${encodeURIComponent(since)}&limit=60`)).json();
if (coins.length) since = coins[0].createdAt;
for (const c of coins) {
// Wait for its holders to be counted; skip a coin its bundled wallets or snipers already hold much of.
const h = await (await fetch(`${API}/coins/${c.address}/holders`)).json();
if (!h.tracked || h.groups.bundled.heldPct > MAX_BUNDLED || h.groups.snipers.heldPct > MAX_SNIPERS) continue;
const res = await fetch(`${API}/swap`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ wallet: wallet.address, token: c.address, side: 'buy', amount: SPEND, ...fee }) });
const built = await res.json();
if (!res.ok) { console.log('skip', c.ticker, built.error.code); continue; }
// The API sold it straight back in the same simulation: what a round trip costs right now.
const lossPct = 100 * (1 - Number(built.sellBack.eth.amount) / Number(SPEND));
if (lossPct > MAX_LOSS) { console.log('skip', c.ticker, `round trip loses ${lossPct.toFixed(1)}%`); continue; }
console.log('buy', c.ticker, c.address, built.amountOut.amount, `(round trip -${lossPct.toFixed(1)}%)`);
if (process.env.SEND === '1') {
const sent = await wallet.sendTransaction({ to: built.tx.to, data: built.tx.data, value: BigInt(built.tx.value), gasLimit: BigInt(built.tx.gas), chainId: built.tx.chainId });
console.log(' sent', `${chain.explorer}/tx/${sent.hash}`);
}
bought += 1;
if (bought >= MAX_BUYS) break;
}
await new Promise((r) => { setTimeout(r, 3000); });
}
console.log(`RESULT ${bought}`);