How to snipe Flap launches
The whole loop in one script: watch every new Flap launch, skip the ones you don't want, buy with a fixed amount of BNB, and sell when the position hits a take-profit or a stop-loss.
What you need
- Node.js 22+ (it ships a WebSocket client) with ethers installed:
npm install ethers. - A wallet private key with some BNB on BNB Chain - see gas and fees.
- Ten minutes.
What to filter on
Flap has no opening snipe tax, so there is nothing to wait out - but two things decide whether a launch is worth buying, and both arrive in the launch event itself:
- The tax. Most launches are tax tokens. A 10% sell tax means you need a 10% move just to break even on the way out.
buyTaxBpsandsellTaxBpsare on every event. - The quote asset.
quoteTokenisBNB, a ticker likeUSD1orNVDAB, or an address for a quote the API has no name for. BNB-priced tokens need one transaction; the rest need Flap to swap on the way in, which works for every listed asset but costs more gas.
Some tokens also cap how much one wallet may buy on the curve. The quote already reflects your allowance, so the script never overpays; it just buys less.
The script
const { Wallet } = require("ethers");
// ─── change these ─────────────────────────────────────
const PRIVATE_KEY = "0xYOUR_PRIVATE_KEY";
const BUY_BNB = "0.01"; // BNB 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 MAX_SELL_TAX = 500; // skip tokens taxing sells above 5% (bps)
const ONLY_BNB_PRICED = true; // skip tokens priced in USD1 or another asset
const DRY_RUN = true; // quote and log, never send
// ──────────────────────────────────────────────────────
const API = "https://api.shrine.trade/bnb";
const wallet = new Wallet(PRIVATE_KEY); // signs only; no node needed
const positions = new Map(); // token -> { paidBnb, symbol }
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/bnb/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 onLaunch(t) {
if (positions.size >= MAX_POSITIONS || positions.has(t.token)) return;
if (ONLY_BNB_PRICED && t.quoteToken !== "BNB") return;
if ((t.sellTaxBps ?? 0) > MAX_SELL_TAX) return;
console.log(`launch ${t.symbol} ${t.token} tax ${t.buyTaxBps}/${t.sellTaxBps} bps, priced in ${t.quoteToken}`);
const q = await post("/api/local-trade", { action: "buy", token: t.token, amount: BUY_BNB, from: wallet.address });
if (q.error) { console.log(" skip:", q.error, q.message); return; }
console.log(` buying ${BUY_BNB} BNB -> ~${q.quote.expectedOutFormatted} ${q.symbol}`);
await send(q.txs);
positions.set(t.token, { paidBnb: Number(BUY_BNB), symbol: q.symbol });
}
async function checkPositions() {
for (const [token, pos] of positions) {
const q = await post("/api/local-trade", { action: "sell", token, amount: "100%", from: wallet.address });
if (q.error) continue; // insufficient_balance right after a dry-run buy is expected
const worth = Number(q.quote.expectedOutFormatted); // BNB, after tax and fees
const ratio = worth / pos.paidBnb;
if (ratio >= TAKE_PROFIT || ratio <= STOP_LOSS) {
console.log(`selling ${pos.symbol}: worth ${worth.toFixed(5)} BNB, ${ratio.toFixed(2)}x`);
await send(q.txs);
positions.delete(token);
}
}
}
const ws = new WebSocket(`${API.replace("https", "wss")}/api/launches/ws?protocols=FLAP`);
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)" : "");
Run it with node sniper.js. Keep DRY_RUN true until the output looks right: in that mode the bot logs the launches it would take and the transactions it would sign, and sends nothing.
What each part does
- The feed delivers every launch within a second of the block - several hundred a day.
?protocols=FLAPkeeps graduation events out. - Filters are yours to extend. The event carries
name,symbol,creator,tokenType, the tax andmetaUri(the creator's image, description and links as JSON), so "only tokens with a website" or a creator blocklist is a few lines. - The buy is in BNB. The quote is Flap's own, simulated as your wallet, so the tax, the curve fee and any buy cap are already in
expectedOut. - Position checks run every ten seconds: the bot asks for a full-exit quote and compares the BNB it would get, after the sell tax, with the BNB it spent.
- State lives in memory. Kill the process and it forgets what it holds; persist
positionsto disk before trusting it with real size.