Ga naar de hoofdinhoud

Bouw een realtime Hyperliquid-bot voor walviswaarschuwingen

Bijgewerkt op
22 april 2026

Leestijd: 24 min


Eén stream, één prijs, meerdere bestemmingen

Uw Quicknode Stream kan nu vanuit één enkele pijplijn tegelijkertijd naar meerdere bestemmingen verzenden. Geen dubbele streams, geen afwijkingen in de configuratie en geen extra kosten per bestemming. De beschikbare bestemmingen per stream variëren per abonnement. Meer informatie.

Overzicht

Het volgen van grote tokenbewegingen op de blockchain, ook wel ‘whale’-activiteit genoemd, kan waardevolle inzichten opleveren in het marktsentiment en mogelijke prijsontwikkelingen.

In deze handleiding wordt stap voor stap uitgelegd hoe je een realtime ‘whale alert’-bot kunt maken voor het HYPE-token op de Hyperliquid-blockchain. Je bouwt een systeem dat niet alleen grote overboekingen detecteert, maar de gegevens ook aanvult met actuele USD-koersen van HyperCore en direct meldingen verstuurt naar een Telegram-kanaal. Hiervoor maken we gebruik van de kracht en efficiëntie van Quicknode Streams.

Wat je gaat doen


  • Maak een Quicknode Stream dat HYPE filtert Overdracht gebeurtenissen van Hyperliquid EVM
  • Controleer de authenticiteit van de payload met een HMAC-handtekening voordat deze wordt verwerkt
  • Lees de HYPE-spotprijs van HyperCore via HyperEVM-precompiles
  • Stuur waarschuwingen over walvissen op verschillende niveaus (vis, dolfijn, walvis) naar een Telegram-kanaal

Wat je nodig hebt


  • Een Quicknode-account met een Hyperliquid EVM-eindpunt
  • Node.js 20+, npm (of een andere pakketbeheerder) en een code-editor zoals VS Code
  • Een Telegram-account (als je een Telegram-bot wilt bouwen)
  • Basiskennis van JavaScript
  • Een hulpmiddel om je lokale server toegankelijk te maken via het internet, zoals ngrok of localtunnel (als je webhooks lokaal wilt testen)
Waarom Quicknode Streams?

Quicknode Streams werken volgens een „push“-model. In plaats van dat je herhaaldelijk de blockchain om nieuwe gegevens vraagt (polling), houden Streams de keten voor je in de gaten en sturen ze de relevante gegevens naar je applicatie zodra er een gebeurtenis plaatsvindt.

Deze aanpak is zeer efficiënt en biedt een aantal belangrijke voordelen:


  • Realtimegegevens: Ontvang direct meldingen, zonder de vertraging die gepaard gaat met pollingcycli.
  • Minder overhead: u hoeft geen complexe en middelenintensieve polling-infrastructuur te beheren.
  • Krachtige filtering: gegevens worden aan de kant van Quicknode verwerkt en gefilterd, zodat uw server alleen precies die informatie ontvangt die hij nodig heeft.
  • Kostenefficiënt: u betaalt alleen per geleverde gebeurtenis, waardoor dit een budgetvriendelijke oplossing is voor realtime gegevensmonitoring.

Het Whale Alert Bot-project

De ‘Whale Alert’-bot bestaat uit verschillende onderling gekoppelde onderdelen die samenwerken om realtime meldingen te versturen:

  1. Hyperliquid-blockchain: A Overdracht evenement (Transfer(adres, adres, uint256)) voor het HYPE-token wordt uitgegeven wanneer er een overdracht plaatsvindt.

  2. Quicknode-streams met filtering: De stream houdt de blockchain voortdurend in de gaten en registreert deze gebeurtenis op basis van de filterfunctie die we definiëren.

  3. Webhook-verzending: Quicknode verstuurt de gefilterde payload via een beveiligd POST-verzoek naar het eindpunt van onze server.

  4. Node.js-server: Onze server ontvangt de gegevens, controleert de authenticiteit ervan aan de hand van het beveiligingstoken van de webhook en verwerkt ze.

  5. Prijsopvraging: De server roept het HyperCore-precompile-contract op Hyperliquid aan om de huidige prijs van HYPE in USD op te vragen.

  6. Telegram-bot: Ten slotte stelt de server een uitgebreid, goed leesbaar bericht op en verstuurt de server de melding via de Telegram-bot-API naar ons aangewezen kanaal.

Architectuur van de Hyperliquid Whale Alert-bot

Dit is de volledige verwerkingsstroom die we gaan implementeren. Laten we nu beginnen met het bouwen van de Hyperliquid Whale Alert-bot.

Stap 1: Maak je Telegram-bot en -kanaal aan

Allereerst heb je een Telegram-bot nodig en een kanaal waar deze meldingen kan plaatsen.

Maak een bot met BotFather


  1. Open Telegram en zoek naar de BotFather.
  2. Start een chat met BotFather en gebruik het commando /newbot om een nieuwe bot aan te maken.
  3. Volg de instructies om een naam en gebruikersnaam voor je bot in te stellen.
  4. BotFather zal je een bot-token verstrekken. Bewaar dit token op een veilige plek; je hebt het nodig voor je .env bestand.

Een kanaal aanmaken


  1. Maak in Telegram een nieuw kanaal aan. Je kunt het openbaar of privé maken.
  2. Geef een openbaar kanaal een pakkende gebruikersnaam (bijvoorbeeld @hyperliquid_whales). Deze gebruikersnaam is je TELEGRAM_CHANNEL_ID.
  3. Voor een privékanaal heb je de numerieke chat-ID nodig. Je kunt deze verkrijgen door een bericht uit het kanaal door te sturen naar een bot zoals @JsonDumpCUBot, en het controleren van de chat-ID die het opgeeft (d.w.z., forward_from_chat.id).

Voeg je bot toe aan het kanaal


  1. Open de instellingen van je zojuist aangemaakte kanaal.
  2. Voeg je bot toe en maak hem beheerder.

Je hebt nu je TELEGRAM_BOT_TOKEN en jouw TELEGRAM_CHANNEL_ID.

Stap 2: Maak je Quicknode Hyperliquid EVM-eindpunt aan

Maak nu je Quicknode Hyperliquid EVM-eindpunt aan, dat zal worden gebruikt om met de Hyperliquid Core te communiceren en HYPE-koersgegevens op te halen.

Allereerst heb je een Quicknode-account nodig. Als je er al een hebt, log dan gewoon in. Zodra je op je Quicknode-dashboard bent:


  • Ga naar de pagina ‘Endpoints’
  • Klik op de knop ‘Nieuw eindpunt’
  • Selecteer het Hyperliquid EVM Mainnet -netwerk
  • Maak je eindpunt aan

Zodra je eindpunt is aangemaakt, kopieer je de URL van je eindpunt en houd je deze bij de hand. Je moet deze toevoegen aan je .env bestand in een latere stap.

Stap 3: Maak je Quicknode-stream aan

Laten we nu de Quicknode Stream instellen die de Hyperliquid-blockchain gaat monitoren.

De stream aanmaken


  1. Ga naar het Quicknode-dashboard en ga naar het gedeelte ‘Streams ’.
  2. Klik op ‘Create Stream’ en selecteer ‘Hyperliquid EVM Mainnet’ als blockchain.
  3. Kies ‘Blok met bonnen’ als dataset
  4. Klik op ‘Je payload aanpassen’ om een filter toe te passen en kies de optie ‘Je eigen filter schrijven’

Stel je filter in

In deze handleiding gaan we een aangepaste JavaScript-functie maken om onze gelaagde waarschuwingslogica te implementeren.

De functie die we gaan gebruiken, controleert elke transactie in elk nieuw blok. Ze zoekt specifiek naar gebeurtenislogboeken die voldoen aan de ERC20-standaard Overdracht handtekening (0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) en zijn afkomstig uit de HYPE tokencontract. Voor een Overdracht In dit geval bevat de array ‘topics’ de afzender en de ontvanger, terwijl log.data bevat het bedrag. Onze code decodeert deze informatie, toetst het bedrag aan onze drempelwaarden en retourneert alleen dan schone gegevens als de overboeking groot genoeg is. Deze voorbewerking is ongelooflijk efficiënt en zorgt ervoor dat onze server geen resources verspilt aan irrelevante gegevens.

Plak de volgende code in het functieveld:

// Quicknode Stream Filter for Tiered Wrapped HYPE Token Transfers
function main(payload) {
// --- Configuration ---
// The specific token contract address for Wrapped HYPE
const WHYPE_ADDRESS = "0x5555555555555555555555555555555555555555";

// Define the thresholds for each tier (with 18 decimals)
const TIER_THRESHOLDS = {
whale: BigInt("10000000000000000000000"), // 10,000 HYPE
dolphin: BigInt("5000000000000000000000"), // 5,000 HYPE
small_fish: BigInt("1000000000000000000000"), // 1,000 HYPE
};

// --- Static Data ---
const TRANSFER_SIGNATURE =
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";

const { data } = payload;
const block = data[0].block;
const receipts = data[0].receipts || [];

const categorizedTransfers = [];

const blockNumber = parseInt(block.number, 16);
const blockTimestamp = parseInt(block.timestamp, 16);

for (const receipt of receipts) {
for (const log of receipt.logs || []) {
// Primary filter: Is this a Transfer event from the W-HYPE contract?
if (
log.address.toLowerCase() === WHYPE_ADDRESS &&
log.topics[0] === TRANSFER_SIGNATURE
) {
const transferValue = BigInt(log.data);
let tier = null;

// Tiering Logic: Check from the highest threshold down to the lowest.
if (transferValue >= TIER_THRESHOLDS.whale) {
tier = "whale";
} else if (transferValue >= TIER_THRESHOLDS.dolphin) {
tier = "dolphin";
} else if (transferValue >= TIER_THRESHOLDS.small_fish) {
tier = "small_fish";
}

// If the transfer meets any of our thresholds, process it.
if (tier) {
const fromAddress = "0x" + log.topics[1].slice(26);
const toAddress = "0x" + log.topics[2].slice(26);

categorizedTransfers.push({
tier: tier,
tokenContract: log.address,
from: fromAddress,
to: toAddress,
value: transferValue.toString(),
transactionHash: receipt.transactionHash,
blockNumber: blockNumber,
timestamp: blockTimestamp,
});
}
}
}
}

if (categorizedTransfers.length > 0) {
return {
largeTransfers: categorizedTransfers,
};
}

return null;
}

Test je filter

Selecteer een blok (zoals 12193297) om je filtervoorwaarden te testen en te controleren of de waarschuwingen correct worden geactiveerd. Je zou een payload moeten zien met daarin de gecategoriseerde overboekingen, zoals hieronder weergegeven:

{
"largeTransfers": [
{
"blockNumber": 12193297,
"from": "0x7c97cd7b57b736c6ad74fae97c0e21e856251dcf",
"tier": "small_fish",
"timestamp": 1756245832,
"to": "0xaaa2851ec59f335c8c6b4db6738c94fd0305598a",
"tokenContract": "0x5555555555555555555555555555555555555555",
"transactionHash": "0xafe522067fca99d4b44030d82885cabb757943255b991b3f2e95564807dbe0f7",
"value": "2200000000000000000000"
}
]
}

Vraag een beveiligingstoken aan en stel de webhook-URL in

Zodra je doorgaat naar de volgende stap, wordt je gevraagd je bestemming te kiezen. Kies Webhook als bestemmingstype. Vervolgens genereert Quicknode automatisch een Beveiligingstoken om te controleren of inkomende verzoeken authentiek zijn. Kopieer dit token naar je .env bestand als waarde van de WEBHOOK_SECRET variabele.

Voor de bestemmings-URL van de stream heb je een openbaar toegankelijk eindpunt nodig. Tijdens de ontwikkeling kun je gebruikmaken van ngrok of localtunnel om je lokale server toegankelijk te maken. Je kunt het volgende uitvoeren: ngrok http 3000 (ervan uitgaande dat je server op poort 3000 draait) en kopieer de HTTPS-doorstuur-URL zodra je server actief is. Vergeet niet om /webhook daarop (bijvoorbeeld, https://your-ngrok-id.ngrok.io/webhook) aangezien je hier je webhook-listener gaat bouwen.

Aangezien onze server nog niet is gebouwd, zullen we het hier even laten rusten en pas terugkomen om de Stream te testen en te activeren nadat de server is gebouwd.

Stap 4: De webhook-server opzetten

Laten we nu de Node.js-toepassing maken die de gegevens van onze webhook zal ontvangen en verwerken.

Projectconfiguratie en afhankelijkheden

Maak eerst een map aan voor je project en installeer de benodigde afhankelijkheden. Je kunt gebruikmaken van npm of een andere pakketbeheerder (zoals garen, pnpm, broodje) om dit te doen. Bijvoorbeeld:

mkdir hyperliquid-whale-alert-bot && cd hyperliquid-whale-alert-bot
npm init -y
npm i express dotenv node-telegram-bot-api viem
npm i -D nodemon

Werk vervolgens je package.json met inbegrip van het volgende:

"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}

Projectopbouw

Maak de volgende bestanden aan om een basisstructuur voor je project te krijgen:

├── config.js           // Configuratie-instellingen
├── index.js // Hoofdingang
├── priceService.js // Prijsgerelateerde logica
├── security.js // Beveiligingsgerelateerde logica
├── telegramService.js // Integratie van de Telegram-bot
└── .env // Omgevingsvariabelen
└── .gitignore // Git-ignore-bestand bestand

Hier is het commando van één regel dat je in je terminal kunt uitvoeren om al die bestanden in een oogwenk aan te maken:

touch config.js index.js priceService.js security.js telegramService.js .env .gitignore

# Als het `touch`-commando niet beschikbaar is, kun je het volgende gebruiken:
# (echo > config.js) && (echo > index.js) && (echo > priceService.js) && (echo > security.js) && (echo > telegramService.js) && (echo > .env) && (echo > .gitignore)

Omgevingsvariabelen

Werk de .env bestand in de hoofdmap van je project om je omgevingsvariabelen op te slaan. Voeg de volgende variabelen toe:

# Telegram-configuratie
TELEGRAM_BOT_TOKEN=je_telegram_bot_token
TELEGRAM_CHANNEL_ID=je_kanaal-id

# Serverconfiguratie
POORT=3000

# Beveiliging van streams-webhooks
WEBHOOK_SECRET=uw_optionele_webhook-geheim

# Quicknode-configuratie voor Hyperliquid EVM RPC
HYPERLIQUID_RPC=https://your-endpoint.quiknode.pro/your-token/

# Omgeving
NODE_ENV=development

Implementatie van de code

.gitignore

Het is belangrijk om een .gitignore bestand aan je project toe om te voorkomen dat je gevoelige informatie en overbodige bestanden vastlegt. Maak een .gitignore bestand in de hoofdmap van je project en voeg de volgende regels toe:

node_modules
.env
config.js

Dit bestand bevat de configuratie-instellingen voor uw toepassing, waaronder omgevingsvariabelen en andere constanten.

De plek De prijs van precompile staat op 0x...0808, terwijl de orakel de prijs van precompile is 0x...0807. Je kunt beide gebruiken, afhankelijk van je prijsbron; in deze handleiding wordt gebruikgemaakt van plek. Controleer adressen altijd in officiële documenten en actuele handleidingen.

import dotenv from "dotenv";

// Load environment variables
dotenv.config();

export const TIERS = {
whale: {
emoji: "🐋",
label: "WHALE",
},
dolphin: {
emoji: "🐬",
label: "DOLPHIN",
},
small_fish: {
emoji: "🐟",
label: "FISH",
},
};

export const EXPLORER = {
tx: "https://hypurrscan.io/tx/",
address: "https://hypurrscan.io/address/",
block: "https://hypurrscan.io/block/",
};

export const HYPERCORE = {
SPOT_PX_PRECOMPILE: "0x0000000000000000000000000000000000000808",
HYPE_SPOT_INDEX: 107, // Mainnet HYPE spot ID
RPC_URL: process.env.HYPERLIQUID_RPC || "https://api.hyperliquid.xyz/evm",
};

export const MESSAGE_DELAY_MS = 1000; // Delay between Telegram messages

export const PORT = process.env.PORT || 3000;
priceService.js

Deze service is verantwoordelijk voor het ophalen van de HYPE-koers uit HyperCore.

We zullen de SPOT price wordt direct voorgecompileerd met een 32-byte ABI-gecodeerde index. De voorgecompileerde versie retourneert een uint64 waarbij de decimale schaal afhangt van de szDecimals (Het prijssysteem van Hyperliquid).

// priceService.js
// Fetches HYPE price from HyperCore using precompile

import { createPublicClient, http, encodeAbiParameters, formatUnits } from "viem";
import { HYPERCORE } from "./config.js";

// Create viem client for HyperEVM
const client = createPublicClient({
transport: http(HYPERCORE.RPC_URL),
});

// Cache price for 30 seconds to avoid excessive RPC calls
let priceCache = {
price: null,
timestamp: 0,
};
const CACHE_DURATION = 30000; // 30 seconds

/**
* Fetches HYPE spot price from HyperCore precompile
* Price is returned with 6 decimals precision for HYPE
* Details: To convert to floating point numbers, divide the returned price by 10^(8 - base asset szDecimals) for spot
* Source: https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/hyperevm/interacting-with-hypercore
* @returns {Promise<number|null>} HYPE price in USD
*/
export async function getHypePrice() {
try {
// Check cache first
if (
priceCache.price &&
Date.now() - priceCache.timestamp < CACHE_DURATION
) {
console.log("Using cached HYPE price:", priceCache.price);
return priceCache.price;
}

// Encode the spot index as a uint32 parameter
const encodedIndex = encodeAbiParameters(
[{ name: "index", type: "uint32" }],
[HYPERCORE.HYPE_SPOT_INDEX]
);

// Call the spot price precompile
const result = await client.call({
to: HYPERCORE.SPOT_PX_PRECOMPILE,
data: encodedIndex,
});

// szDecimals for HYPE is 2.
const szDecimals = 2;

const priceRaw = BigInt(result.data);
const price = formatUnits(priceRaw, 8 - szDecimals); // Convert to decimal string

// Update cache
priceCache = {
price,
timestamp: Date.now(),
};

console.log(`Fetched HYPE price from HyperCore: $${price}`);
return price;
} catch (error) {
console.error("Error fetching HYPE price from HyperCore:", error);
// Return cached price if available, otherwise null
return priceCache.price || null;
}
}

/**
* Formats USD value based on HYPE amount and price
* @param {string} hypeAmount - HYPE amount as string
* @param {number} hypePrice - HYPE price in USD
* @returns {string} Formatted USD value
*/
export function formatUSD(hypeAmount, hypePrice) {
if (!hypePrice) return "";

const usdValue = parseFloat(hypeAmount) * hypePrice;
return `($${usdValue.toLocaleString("en-US", {
minimumFractionDigits: 0,
maximumFractionDigits: 0,
})})`;
}
security.js

Deze module bevat de logica voor het valideren van binnenkomende webhooks. Quicknode raadt HMAC-verificatie via headers aan X-QN-Nonce, X-QN-Tijdstempel, en X-QN-Signature en deze module zorgt voor die controle.

Raadpleeg voor meer informatie de handleiding „Streams-handtekeningen valideren ”.

// security.js
// Validates incoming webhook signatures from Quicknode

import crypto from "crypto";

/**
* Validates the webhook signature from Quicknode
* Based on Quicknode's HMAC-SHA256 signature validation
*
* @param {string} secretKey - The webhook secret key
* @param {string} payload - The request body as string
* @param {string} nonce - The nonce from headers
* @param {string} timestamp - The timestamp from headers
* @param {string} givenSignature - The signature from headers
* @returns {boolean} Whether the signature is valid
*/
export function validateWebhookSignature(
secretKey,
payload,
nonce,
timestamp,
givenSignature
) {
if (!secretKey || !nonce || !timestamp || !givenSignature) {
console.warn("⚠️ Missing required parameters for signature validation");
return false;
}

try {
// Concatenate nonce + timestamp + payload as strings
const signatureData = nonce + timestamp + payload;

// Convert to bytes
const signatureBytes = Buffer.from(signatureData);

// Create HMAC with secret key converted to bytes
const hmac = crypto.createHmac("sha256", Buffer.from(secretKey));
hmac.update(signatureBytes);
const computedSignature = hmac.digest("hex");

// Use timing-safe comparison to prevent timing attacks
const isValid = crypto.timingSafeEqual(
Buffer.from(computedSignature, "hex"),
Buffer.from(givenSignature, "hex")
);

if (isValid) {
console.log("✅ Webhook signature validated successfully");
} else {
console.error("❌ Invalid webhook signature");
}

return isValid;
} catch (error) {
console.error("Error validating webhook signature:", error);
return false;
}
}

/**
* Middleware for Express to validate webhook signatures
* Quicknode sends nonce, timestamp, and signature in headers
*/
export function webhookAuthMiddleware(req, res, next) {
// Skip validation if no secret is configured
const secretKey = process.env.WEBHOOK_SECRET;
if (!secretKey) {
console.log("ℹ️ Webhook secret not configured, skipping validation");
return next();
}

// Get Quicknode headers
const nonce = req.headers["x-qn-nonce"];
const timestamp = req.headers["x-qn-timestamp"];
const givenSignature = req.headers["x-qn-signature"];

if (!nonce || !timestamp || !givenSignature) {
console.error("🚫 Missing required Quicknode headers");
return res.status(400).json({
error: "Missing required headers",
message:
"x-qn-nonce, x-qn-timestamp, and x-qn-signature headers are required",
});
}

// Get the raw body as string
// Note: Express's JSON middleware already parsed the body, so we need to stringify it back
const payloadString = JSON.stringify(req.body);

// Validate the signature
const isValid = validateWebhookSignature(
secretKey,
payloadString,
nonce,
timestamp,
givenSignature
);

if (!isValid) {
console.error("🚫 Webhook validation failed");
return res.status(401).json({
error: "Invalid signature",
message: "The webhook signature could not be validated",
});
}

next();
}
telegramService.js

Deze module formatteert het uiteindelijke bericht en verstuurt het naar je Telegram-kanaal.

// telegramService.js
// Handles Telegram bot messaging

import TelegramBot from "node-telegram-bot-api";
import { formatEther } from "viem";
import { TIERS, EXPLORER, MESSAGE_DELAY_MS } from "./config.js";
import { getHypePrice, formatUSD } from "./priceService.js";

// Initialize Telegram bot
const bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, { polling: false });
const CHANNEL_ID = process.env.TELEGRAM_CHANNEL_ID;

/**
* Format an address for display
*/
function formatAddress(address) {
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}

/**
* Format transaction hash for display
*/
function formatTxHash(hash) {
return `${hash.slice(0, 10)}...`;
}

/**
* Display time
*/
function getTime(timestamp) {
const date = new Date(timestamp * 1000);
return date.toLocaleString("en-US");
}

/**
* Create formatted Telegram message for a transfer
*/
async function createMessage(transfer) {
const tierConfig = TIERS[transfer.tier];
const formattedValue = formatEther(BigInt(transfer.value));
const hypePrice = await getHypePrice();
const usdValue = formatUSD(formattedValue, hypePrice);

// Create message with Markdown formatting
const message = `
${tierConfig.emoji} *${tierConfig.label} ALERT* ${tierConfig.emoji}

💰 *Amount:* \`${parseFloat(formattedValue).toLocaleString("en-US", {
maximumFractionDigits: 2,
})} HYPE\` ${usdValue}

📤 *From:* [${formatAddress(transfer.from)}](${EXPLORER.address}${
transfer.from
})
📥 *To:* [${formatAddress(transfer.to)}](${EXPLORER.address}${transfer.to})

🔗 *TX:* [${formatTxHash(transfer.transactionHash)}](${EXPLORER.tx}${
transfer.transactionHash
})
📦 *Block:* [#${transfer.blockNumber}](${EXPLORER.block}${transfer.blockNumber})
⏰ *Time:* ${getTime(transfer.timestamp)}

Powered by [Hyperliquid](https://hyperliquid.xyz) & [Quicknode Streams](https://www.quicknode.com/streams)`;

return message;
}

/**
* Send message to Telegram with retry logic
*/
export async function sendMessage(message, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
await bot.sendMessage(CHANNEL_ID, message, {
parse_mode: "Markdown",
disable_web_page_preview: true,
});

console.log("✅ Message sent to Telegram successfully");
return true;
} catch (error) {
console.error(`❌ Telegram send attempt ${i + 1} failed:`, error.message);

if (i < retries - 1) {
// Wait before retrying (exponential backoff)
await new Promise((resolve) =>
setTimeout(resolve, 1000 * Math.pow(2, i))
);
}
}
}

return false;
}

/**
* Process and send alerts to Telegram
*/
export async function processAlerts(transfers) {
console.log(`📨 Processing ${transfers.length} transfers for Telegram...`);

for (const transfer of transfers) {
const message = await createMessage(transfer);
const sent = await sendMessage(message);

if (!sent) {
console.error(
"Failed to send message for transfer:",
transfer.transactionHash
);
}

// Rate limiting between messages
if (transfers.indexOf(transfer) < transfers.length - 1) {
await new Promise((resolve) => setTimeout(resolve, MESSAGE_DELAY_MS));
}
}

// Send summary if there are multiple transfers
if (transfers.length > 3) {
const summaryMessage = `
📊 *Batch Summary*

Total transfers: ${transfers.length}
🐋 Whales: ${transfers.filter((t) => t.tier === "whale").length}
🐬 Dolphins: ${transfers.filter((t) => t.tier === "dolphin").length}
🐟 Fish: ${transfers.filter((t) => t.tier === "small_fish").length}

Block: #${transfers[0].blockNumber}
`;
await sendMessage(summaryMessage);
}
}
index.js

Dit is het hoofdbestand dat alles met elkaar verbindt.

We gebruiken een Express-parser voor onbewerkte gegevens vóór alle JSON-middleware voor de /webhook eindpunt om handtekeningen te kunnen verifiëren. Dit zorgt ervoor dat de inhoud in zijn oorspronkelijke vorm beschikbaar is voor HMAC-verificatie.

// server.js
// Main webhook server for Hyperliquid whale alerts

import express from "express";
import dotenv from "dotenv";
import { PORT, HYPERCORE } from "./config.js";
import { processAlerts } from "./telegramService.js";
import { webhookAuthMiddleware } from "./security.js";

// Load environment variables
dotenv.config();

// Initialize Express app
const app = express();

// Custom middleware to capture raw body for signature validation
app.use((req, res, next) => {
if (req.path === '/webhook' && process.env.WEBHOOK_SECRET) {
// For webhook endpoint with security enabled, capture raw body
let rawBody = '';
req.setEncoding('utf8');

req.on('data', (chunk) => {
rawBody += chunk;
});

req.on('end', () => {
req.rawBody = rawBody;

// Parse JSON body
try {
req.body = JSON.parse(rawBody);
} catch (error) {
return res.status(400).json({ error: 'Invalid JSON payload' });
}

next();
});
} else {
// For other endpoints or when security is disabled, use normal JSON parsing
express.json()(req, res, next);
}
});

// Main webhook endpoint with security validation
app.post("/webhook", webhookAuthMiddleware, async (req, res) => {
try {
console.log("📨 Webhook received at", new Date().toISOString());

const { largeTransfers } = req.body;

if (!largeTransfers || largeTransfers.length === 0) {
console.log("No large transfers in this webhook");
return res.json({ success: true, processed: 0 });
}

console.log(`Processing ${largeTransfers.length} transfers...`);

// Send alerts to Telegram
await processAlerts(largeTransfers);

console.log(`✅ Processed ${largeTransfers.length} transfers successfully`);

res.json({
success: true,
processed: largeTransfers.length,
});
} catch (error) {
console.error("❌ Webhook processing error:", error);
res.status(500).json({
success: false,
error: error.message,
});
}
});

// Health check endpoint
app.get("/health", (req, res) => {
res.json({
status: "healthy",
timestamp: new Date().toISOString(),
environment: {
telegramConfigured: !!process.env.TELEGRAM_BOT_TOKEN,
webhookSecretConfigured: !!process.env.WEBHOOK_SECRET,
},
});
});

// Error handling middleware
app.use((error, req, res, next) => {
console.error("Unhandled error:", error);
res.status(500).json({
success: false,
error: "Internal server error",
});
});

// Start server
app.listen(PORT, () => {
console.log(`
╔════════════════════════════════════════════╗
║ Hyperliquid Whale Alert Server Started ║
╠════════════════════════════════════════════╣
║ Port: ${PORT}
║ Webhook: http://localhost:${PORT}/webhook ║
║ Health: http://localhost:${PORT}/health ║
╚════════════════════════════════════════════╝

Configuration:
- Telegram Bot: ${
process.env.TELEGRAM_BOT_TOKEN ? "✅ Configured" : "❌ Not configured"
}
- Telegram Channel: ${process.env.TELEGRAM_CHANNEL_ID || "Not configured"}
- Webhook Secret: ${
process.env.WEBHOOK_SECRET
? "✅ Configured"
: "⚠️ Not configured (validation disabled)"
}
- HyperCore RPC: ${HYPERCORE.RPC_URL}

Ready to receive webhooks...
`);
});

// Graceful shutdown
process.on("SIGTERM", () => {
console.log("SIGTERM received, shutting down gracefully...");
process.exit(0);
});

process.on("SIGINT", () => {
console.log("SIGINT received, shutting down gracefully...");
process.exit(0);
});

Stap 5: Het systeem opstarten en testen

Je bent nu klaar om je bot tot leven te brengen.

Start je server

Voer de volgende opdracht uit in je terminal om de server te starten. nodemon zorgt ervoor dat de server automatisch opnieuw opstart wanneer er wijzigingen in bestanden worden aangebracht.

# Start in de ontwikkelingsmodus met nodemon
npm run dev

Maak je localhost toegankelijk

Als je dat nog niet hebt gedaan, open dan een nieuw terminalvenster en voer het volgende uit: ngrok http 3000. Kopieer de HTTPS-doorstuur-URL.

Test je stream


  1. Ga terug naar de pagina van je webhook in het Quicknode-dashboard.
  2. Plak je ngrok-URL (bijv., https://your-ngrok-id.ngrok.io/webhook) in het veld ‘Webhook-URL’ en sla het op.
  3. Klik nu op de knop ‘Voorbeeldpayload verzenden ’. Quicknode stuurt dan een voorbeeldpayload naar je actieve server.
  4. Controleer de consolelogbestanden van je server en kijk of er meldingen op je Telegram-kanaal staan.

Hier volgt een voorbeeld van hoe de uiteindelijke waarschuwing eruit zal zien:

Hyperliquid Whale Bot - Voorbeeldbericht

Activeer je stream

Zodra je hebt gecontroleerd of alles naar behoren werkt, klik je op de knop ‘Een stream aanmaken’ om je stream aan te maken. Je bot is nu live en houdt alle nieuwe HYPE-overboekingen in realtime bij.

Problemen oplossen

Soms, als je server of ngrok Als het programma niet correct wordt afgesloten, kun je een foutmelding krijgen zoals ‘adres al in gebruik’ wanneer je het opnieuw probeert te starten. Hieronder lees je hoe je dat snel kunt oplossen.

Stap 1: De poort vrijmaken

Zoek eerst het proces-ID (PID) aan de hand van de poort en stop het proces vervolgens. De onderstaande opdrachten zijn bedoeld voor macOS/Linux; ze kunnen afwijken, afhankelijk van je besturingssysteem.

# Find the Process ID (PID) using port 3004
lsof -i :3004

# Replace <PID> with the number you found and run:
kill -9 <PID>

Stap 2: Opnieuw opstarten en bijwerken

Start je server opnieuw op en ngrok. Belangrijk: ngrok maakt bij elke start een nieuwe URL aan. Je moet deze nieuwe URL kopiëren en deze bijwerken in de instellingen van je Quicknode-webhook.

Conclusie

Gefeliciteerd! Je hebt met succes een productieklaar, realtime waarschuwingssysteem voor grote transacties ontwikkeld voor de Hyperliquid-blockchain. Door de kracht van Quicknode Streams voor on-chain gegevens, een beveiligde Node.js-server voor bedrijfslogica en de Telegram-API voor meldingen te combineren, heb je een waardevolle tool gecreëerd voor het monitoren van het DeFi-ecosysteem.

Dit patroon is flexibel: je kunt de lagen uitbreiden, het verrijken met extra on-chain-context of gegevensbronnen en -bestemmingen verwisselen naarmate je gebruikssituatie zich ontwikkelt.

Volgende stappen: Implementatie in de productieomgeving

Terwijl ngrok is uitstekend geschikt voor ontwikkeling, maar voor een productieomgeving heb je een meer permanente oplossing nodig. Overweeg om je applicatie te implementeren op:


  • Een Virtual Private Server (VPS), zoals DigitalOcean of AWS, waarbij een procesbeheerder zoals PM2 wordt gebruikt om de server draaiende te houden
  • Een containergebaseerde dienst met behulp van Docker
  • Een Platform-as-a-Service (PaaS) zoals Heroku of Render

Mogelijke verbeteringen

Dit project biedt een stevige basis waarop je verder kunt bouwen. Hier zijn een paar ideeën om je bot naar een hoger niveau te tillen:

  • Ondersteuning voor meerdere tokens: Pas de code aan zodat deze een array met tokenadressen accepteert. Op deze manier kun je meerdere tokens met verschillende drempelwaarden in de gaten houden en op basis daarvan waarschuwingen versturen.

  • Dashboard met historische gegevens: Quicknode Streams ondersteunt niet alleen realtime gegevens, maar ook historische gegevens. Sla de binnenkomende transactiegegevens op in een database (bijvoorbeeld PostgreSQL of MongoDB). Vervolgens kun je een webgebaseerde dApp bouwen om historische trends te visualiseren, de netto-stroom van specifieke ‘whale’-wallets te volgen en diepgaandere on-chain-analyses uit te voeren.

  • Meer meldingskanalen toevoegen: integreer andere meldingsdiensten, zoals Discord-webhooks.

  • Triggers voor geautomatiseerde handel: Voor een meer geavanceerde toepassing kun je deze meldingen gebruiken om on-chain-acties te activeren. Zo zou een grote overboeking bijvoorbeeld een swap kunnen activeren.

Aanvullende bronnen


Als je ergens niet uitkomt of vragen hebt, stel ze dan op onze Discord. Blijf op de hoogte van het laatste nieuws door ons te volgen op X (voorheen Twitter) (@Quicknode) of via ons Telegram-kanaal voor aankondigingen.

We ❤️ feedback!

Laat het ons weten als je feedback hebt of suggesties voor nieuwe onderwerpen. We horen graag van je.

Deel deze gids