Memecoins on Solana move fast. A token can drop 40% in two minutes and recover within ten. If you're watching charts manually, you're already too late by the time you open your wallet and type in the amount.
A dip-buying bot solves this by watching token prices continuously and executing a buy the instant a predefined threshold is hit. The hard part is detection speed. Most price monitoring solutions poll APIs every 5-30 seconds — an eternity in memecoin time. By the time you detect a 30% dip via polling, the token may have already bounced back to -10%.
This guide builds a dip-buying bot using MadeOnSol's Price Alert API, which evaluates token market caps inside the mc-tracker publish loop at 250ms intervals, so a dip is checked four times a second instead of once per polling interval. You define the dip threshold, the API watches it, and your bot receives a push notification the moment it triggers.
Architecture Overview
The bot has four components:
- Alert creation — register a token + drop threshold via the API
- Webhook receiver — HTTP server that receives
price_alert:dip events
- Buy execution — swap SOL for the dipped token via Jupiter
- Recovery tracking — optionally get notified when the token bounces back
No polling loops. No WebSocket connections to maintain. The alert system pushes to you — we cover this push-vs-poll tradeoff in depth in our guide to automated dip buying with webhooks.
Prerequisites
- Node.js 18+ and TypeScript
- A MadeOnSol PRO+ API key — current per-chain price-alert limits are PRO 5, ULTRA 25 and BUSINESS 125 — get one at madeonsol.com/pricing
- A Solana wallet with SOL for trading
- A publicly accessible HTTP endpoint for webhook delivery (use ngrok for development)
Install the SDK:
npm install madeonsol @solana/web3.js bs58 dotenv express
npm install -D typescript @types/node @types/express ts-node
Step 1: Create a Price Alert
A price alert captures the token's current market cap as the baseline when you create it. You set a drop_pct — when the MC falls below baseline * (1 - drop_pct / 100), the alert fires.
// src/create-alert.ts
import MadeOnSol from "madeonsol";
const client = new MadeOnSol({ apiKey: process.env.MADEONSOL_API_KEY! });
async function createDipAlert(tokenMint: string) {
const alert = await client.priceAlerts.create({
token_mint: tokenMint,
drop_pct: 25, // fire when MC drops 25% from current
recovery_pct: 15, // then fire again when MC recovers 15% from the dip low
name: "Dip buy alert",
delivery_mode: "webhook",
webhook_url: "https://your-server.com/webhook/dip",
});
console.log("Alert created:", alert.id);
console.log("Baseline MC:", alert.baseline_mc_usd);
console.log("Webhook secret:", alert.webhook_secret);
// Store the webhook_secret — you need it to verify signatures
return alert;
}
createDipAlert("TOKEN_MINT_HERE").catch(console.error);
The API returns a webhook_secret on creation, shown once only. Store it securely (the server below reads it as WEBHOOK_SECRET). Every webhook payload is signed with HMAC-SHA256 so you can verify it came from MadeOnSol and not an attacker.
Alert Lifecycle
Each alert is one-shot: watching then dipped then optionally recovered. After it fires, create a new one to re-track. This is intentional — it forces you to reassess the baseline rather than blindly re-entering at old levels.
Step 2: Webhook Receiver
When the price drops past your threshold, MadeOnSol POSTs a price_alert:dip event to your webhook URL. The body is an envelope { event, data, timestamp }, and the alert fields sit under data: alert_id, token_mint, baseline_mc_usd, current_mc_usd, drop_pct_configured, drop_pct_actual and fired_at.
// src/webhook-server.ts
import express from "express";
import crypto from "crypto";
import { executeBuy } from "./buy";
const app = express();
// No global express.json(): the signature is computed over the raw body bytes
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET!; // the webhook_secret from Step 1
const handled = new Set<string>(); // use a DB unique key in production
// rawBody: the exact request bytes (never re-serialized JSON)
// secret: the webhook secret exactly as the API returned it (do not hex-decode)
function verifyMadeOnSol(rawBody: Buffer | string, timestamp: string | undefined, signature: string | undefined, secret: string): boolean {
// X-MadeOnSol-Timestamp: unix milliseconds, fresh on every retry
if (typeof timestamp !== "string" || !/^\d{1,16}$/.test(timestamp)) return false;
// Replay window: your choice, the server does not enforce one
if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60_000) return false;
// X-MadeOnSol-Signature: 64 hex characters, bare (no algorithm prefix)
if (typeof signature !== "string" || !/^[0-9a-f]{64}$/i.test(signature)) return false;
const expected = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.`)
.update(rawBody)
.digest();
// Both sides are exactly 32 bytes here, so this compares in constant time and cannot throw
return crypto.timingSafeEqual(expected, Buffer.from(signature, "hex"));
}
app.post("/webhook/dip", express.raw({ type: "application/json" }), (req, res) => {
const ok = verifyMadeOnSol(
req.body,
req.get("X-MadeOnSol-Timestamp"),
req.get("X-MadeOnSol-Signature"),
WEBHOOK_SECRET,
);
if (!ok) return res.status(401).send("Invalid signature");
// Acknowledge BEFORE buying. A delivery that takes longer than 8 seconds is
// retried, and a retry must never become a second buy.
res.status(200).send("OK");
const { event, data } = JSON.parse(req.body.toString("utf8"));
if (event !== "price_alert:dip") return;
const key = `${event}:${data.alert_id}`; // each alert fires its dip once
if (handled.has(key)) return;
handled.add(key);
console.log(`DIP DETECTED on ${data.token_mint}`);
console.log(`Baseline MC: $${data.baseline_mc_usd}`);
console.log(`Current MC: $${data.current_mc_usd}`);
console.log(`Drop: ${data.drop_pct_actual.toFixed(1)}%`);
executeBuy(data.token_mint)
.then((sig) => console.log(`Buy executed: ${sig}`))
.catch((err) => console.error("Buy failed:", err));
});
app.listen(4000, () => console.log("Webhook server running on :4000"));
The check follows MadeOnSol's scheme: X-MadeOnSol-Timestamp is unix milliseconds (new on every delivery attempt), and X-MadeOnSol-Signature is the bare lowercase hex HMAC-SHA256 of the timestamp, a dot and the raw body, keyed with the webhook_secret string exactly as returned. The 5 minute window is your own choice; MadeOnSol does not enforce one. See verifying MadeOnSol webhook signatures in Node.js and Python for the full walkthrough.
The signature verification is not optional. Without it, anyone who discovers your webhook URL can trigger fake buy signals.