A launch sniper with caps
Tested on BSC testnet before every release
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-sniper.mjs, exactly as our tests run it on testnet. Point it at testnet with MM_API, or leave it for mainnet.
// A launch sniper with caps: buys each new launch once, the moment it is indexed,// never more than you allow, and takes profit on the way up.// PRIVATE_KEY=0x… node recipe-sniper.mjs//// Every limit is yours to set, and there is no "unlimited":// BUY=0.01 BNB per launch// MAX_TOTAL=0.05 BNB across the whole run: no buys after this// MAX_CAP_BNB=20 skip a launch already worth more than this (market cap, in BNB)// TAKE_PROFIT=2 sell half a position once it is worth this many times what it cost// STOP_LOSS=0.5 sell all of it once it is worth this fraction of what it cost// SECONDS= stop after this long (default: run until stopped)// ONCE=0x… treat this token as a new launch (to try it out)import { MarketMayhem } from '@marketmayhem/sdk';import { Contract, JsonRpcProvider } from 'ethers';
const buy = Number(process.env.BUY ?? '0.01');const maxTotal = Number(process.env.MAX_TOTAL ?? '0.05');const maxCapBnb = Number(process.env.MAX_CAP_BNB ?? '20');const takeProfit = Number(process.env.TAKE_PROFIT ?? '2');const stopLoss = Number(process.env.STOP_LOSS ?? '0.5');const stopAt = process.env.SECONDS ? Date.now() + Number(process.env.SECONDS) * 1000 : Infinity;if (!(buy > 0) || !(maxTotal >= buy)) throw new Error('BUY must be above 0 and no bigger than MAX_TOTAL.');
const mm = MarketMayhem.fromPrivateKey(process.env.PRIVATE_KEY, { api: process.env.MM_API });const { chainId } = await mm.settings();const provider = new JsonRpcProvider(chainId === 56 ? 'https://bsc-dataseed.bnbchain.org' : 'https://bsc-testnet-rpc.publicnode.com', chainId);
/** Market cap in BNB, from a tiny quote and the token's supply. */async function marketCapBnb(token) { const q = await mm.quote(token, 'buy', '0.001'); const supply = await new Contract(token, ['function totalSupply() view returns (uint256)'], provider).totalSupply(); return (0.001 / Number(q.receive.amount)) * (Number(supply) / 1e18);}/** What a position would sell for now, in BNB. */const worth = async (token, tokens) => Number((await mm.quote(token, 'sell', tokens.toFixed(6))).receive.amount);
let spent = 0;const positions = new Map(); // token -> { cost, tokens, tookProfit }const log = [];
async function onLaunch(l) { if (positions.has(l.token)) return; if (spent + buy > maxTotal + 1e-12) { console.log(`skip $${l.symbol}: MAX_TOTAL reached`); return; } const cap = await marketCapBnb(l.token); if (cap > maxCapBnb) { console.log(`skip $${l.symbol}: worth ${cap.toFixed(2)} BNB, above MAX_CAP_BNB`); return; } // What we really got: the balance before and after, not the quote. const erc20 = new Contract(l.token, ['function balanceOf(address) view returns (uint256)'], provider); const before = await erc20.balanceOf(mm.address); const r = await mm.buy(l.token, { spend: String(buy) }); spent += buy; const got = Number((await erc20.balanceOf(mm.address)) - before) / 1e18; positions.set(l.token, { symbol: l.symbol, cost: buy, tokens: got, tookProfit: false }); log.push({ bought: l.token, hash: r.hash }); console.log(`bought $${l.symbol} for ${buy} BNB at a ${cap.toFixed(3)} BNB market cap: ${r.hash}`);}
async function manage() { for (const [token, p] of positions) { const now = await worth(token, p.tokens); if (!p.tookProfit && now >= p.cost * takeProfit) { const r = await mm.sell(token, { percent: 50 }); p.tokens /= 2; p.cost /= 2; p.tookProfit = true; log.push({ tookProfit: token, hash: r.hash }); console.log(`$${p.symbol} worth ${now.toFixed(4)} BNB: sold half, ${r.hash}`); } else if (now <= p.cost * stopLoss) { const r = await mm.sell(token, { percent: 100 }); positions.delete(token); log.push({ stopped: token, hash: r.hash }); console.log(`$${p.symbol} worth ${now.toFixed(4)} BNB: sold all (stop loss), ${r.hash}`); } }}
if (process.env.ONCE) await onLaunch({ token: process.env.ONCE, symbol: (await mm.token(process.env.ONCE)).symbol });const ctrl = new AbortController();const timer = Number.isFinite(stopAt) ? setTimeout(() => ctrl.abort(), stopAt - Date.now()) : null;const managing = setInterval(() => manage().catch((e) => console.log(`manage: ${e.message}`)), 15_000);try { for await (const l of mm.watchLaunches({ signal: ctrl.signal })) await onLaunch(l).catch((e) => console.log(`$${l.symbol}: ${e.message}`));} catch (e) { if (!ctrl.signal.aborted) throw e; }clearInterval(managing); if (timer) clearTimeout(timer);await manage();console.log(`stopped: ${spent} BNB spent, ${positions.size} open position(s)`);console.log('RESULT', JSON.stringify({ spent, open: positions.size, log }));