24 minutes de lecture
Votre flux Quicknode peut désormais diffuser vers plusieurs destinations simultanément à partir d'un seul pipeline. Pas de flux en double, pas de divergence de configuration et pas de frais supplémentaires par destination. Les destinations disponibles par flux varient selon la formule choisie. En savoir plus.
Aperçu
Le suivi des mouvements importants de jetons sur la blockchain, souvent qualifiés d'activité des « baleines », peut fournir des informations précieuses sur le sentiment du marché et l'évolution potentielle des cours.
Ce guide vous expliquera comment créer un bot d'alerte « whale » en temps réel pour le token HYPE sur la blockchain Hyperliquid. Vous développerez un système capable non seulement de détecter les transferts importants, mais aussi d'enrichir les données avec les cours du dollar américain en temps réel fournis par HyperCore, et d'envoyer des notifications instantanées vers un canal Telegram. Pour y parvenir, nous tirerons parti de la puissance et de l'efficacité de Quicknode Streams.
Ce que vous ferez
- Créer un Quicknode Stream qui filtre le HYPE
Transfertévénements provenant de l'EVM Hyperliquid - Vérifier l'authenticité de la charge utile à l'aide d'une signature HMAC avant de la traiter
- Consultez le cours au comptant HYPE fourni par HyperCore via les précompilés HyperEVM
- Envoyer des alertes « baleines » à plusieurs niveaux (poisson, dauphin, baleine) vers une chaîne Telegram
Ce dont vous aurez besoin
- Un compte Quicknode avec un point de terminaison Hyperliquid EVM
- Node.js 20 ou version ultérieure, npm (ou un autre gestionnaire de paquets) et un éditeur de code tel que VS Code
- Un compte Telegram (si vous souhaitez créer un bot Telegram)
- Connaissances de base en JavaScript
- Un outil permettant de rendre votre serveur local accessible depuis Internet, tel que ngrok ou localtunnel (si vous avez besoin de tester des webhooks en local)
Les Streams Quicknode fonctionnent selon un modèle « push ». Au lieu que vous deviez interroger sans cesse la blockchain pour obtenir de nouvelles données (polling), les Streams surveillent la chaîne à votre place et transmettent les données pertinentes à votre application dès qu'un événement se produit.
Cette approche est très efficace et présente plusieurs avantages majeurs :
- Données en temps réel: recevez des notifications instantanément, sans le temps de latence lié aux cycles d'interrogation.
- Réduction des coûts d'exploitation: vous évite d'avoir à gérer une infrastructure de sondage complexe et gourmande en ressources.
- Filtrage performant: les données sont traitées et filtrées côté Quicknode, de sorte que votre serveur ne reçoive que les informations dont il a réellement besoin.
- Rentable: vous ne payez que pour chaque événement transmis, ce qui en fait une solution économique pour la surveillance des données en temps réel.
Projet « Whale Alert Bot »
Le bot « Whale Alert » se compose de plusieurs éléments interconnectés qui fonctionnent ensemble pour envoyer des notifications en temps réel :
-
Blockchain hyperliquide: A
Transfertévénement (Transfert(adresse, adresse, uint256)) pour le jeton HYPE est émis lorsqu'un transfert a lieu. -
Flux Quicknode avec filtrage: le flux surveille en permanence la chaîne et capture cet événement en fonction de la fonction de filtrage que nous définissons.
-
Transmission par webhook: Quicknode envoie la charge utile filtrée via une requête POST sécurisée vers le point de terminaison de notre serveur.
-
Serveur Node.js: notre serveur reçoit les données, vérifie leur authenticité à l'aide du jeton de sécurité du webhook, puis les traite.
-
Récupération du cours: le serveur appelle le contrat de précompilation HyperCore sur Hyperliquid afin d'obtenir le cours actuel du HYPE en dollars américains.
-
Bot Telegram: Enfin, le serveur génère un message structuré et lisible, puis utilise l'API du bot Telegram pour envoyer l'alerte vers notre chaîne désignée.

Voici le flux d'événements de bout en bout que nous allons mettre en œuvre. Commençons maintenant à développer le bot Hyperliquid Whale Alert.
Étape 1 : Créez votre bot et votre chaîne Telegram
Tout d'abord, vous aurez besoin d'un bot Telegram et d'un canal sur lequel il pourra publier des alertes.
Créer un bot avec BotFather
- Ouvrez Telegram et recherchez le BotFather.
- Lancez une conversation avec BotFather et utilisez la commande
/newbotpour créer un nouveau bot. - Suivez les instructions pour définir un nom et un nom d'utilisateur pour votre bot.
- BotFather vous fournira un jeton de bot. Conservez ce jeton en lieu sûr ; vous en aurez besoin pour votre
.envfichier.
Créer une chaîne
- Dans Telegram, créez une nouvelle chaîne. Vous pouvez la rendre publique ou privée.
- Pour un canal public, choisissez un nom d'utilisateur facile à retenir (par exemple, @hyperliquid_whales). Ce nom d'utilisateur est votre
TELEGRAM_CHANNEL_ID. - Pour un canal privé, vous aurez besoin de son identifiant de chat numérique. Vous pouvez l'obtenir en transférant un message provenant de ce canal vers un bot tel que
@JsonDumpCUBot, puis en vérifiant l'identifiant de discussion qu'il fournit (c'est-à-dire,forward_from_chat.id).
Ajoutez votre bot au canal
- Ouvrez les paramètres de votre chaîne que vous venez de créer.
- Ajoutez votre bot et nommez-le administrateur.
Vous disposez désormais de votre TELEGRAM_BOT_TOKEN et votre TELEGRAM_CHANNEL_ID.
Étape 2 : Créer votre point de terminaison Hyperliquid EVM sur Quicknode
Créez maintenant votre point de terminaison Quicknode Hyperliquid EVM qui servira à interagir avec Hyperliquid Core afin de récupérer les données de cours HYPE.
Tout d'abord, vous devez disposer d'un compte Quicknode. Si vous en avez déjà un, connectez-vous simplement. Une fois sur votre tableau de bord Quicknode :
- Accédez à la page « Endpoints »
- Cliquez sur le bouton « Nouveau point de terminaison »
- Sélectionnez le réseau Hyperliquid EVM Mainnet
- Créer votre point de terminaison
Une fois votre point de terminaison créé, copiez son URL et gardez-la à portée de main. Vous devrez l'ajouter à votre .env fichier lors d'une étape ultérieure.
Étape 3 : Créer votre flux Quicknode
Configurons maintenant le flux Quicknode qui permettra de surveiller la blockchain Hyperliquid.
Créer le flux
- Accédez au tableau de bord Quicknode, puis rendez-vous dans la section « Streams ».
- Cliquez sur « Créer un flux » et sélectionnez « Hyperliquid EVM Mainnet » comme blockchain.
- Sélectionnez « Bloc avec reçus » comme ensemble de données
- Cliquez sur « Personnaliser votre charge utile » pour appliquer un filtre, puis sélectionnez l'option « Créer votre propre filtre »
Définissez vos critères de filtrage
Dans ce guide, nous allons créer une fonction JavaScript personnalisée pour mettre en œuvre notre logique d'alerte à plusieurs niveaux.
La fonction que nous allons utiliser examine chaque transaction de chaque nouveau bloc. Elle recherche plus précisément les journaux d'événements qui correspondent à la norme ERC20 Transfert signature (0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) et proviennent de la HYPE contrat de jeton. Pour un Transfert Dans cet événement, le tableau « topics » contient l'expéditeur et le destinataire, tandis que log.data contient le montant. Notre code décode ces informations, compare le montant à nos seuils et ne renvoie une charge utile de données valides que si le transfert est suffisamment important. Ce prétraitement est extrêmement efficace, car il garantit que notre serveur ne gaspille pas de ressources sur des données non pertinentes.
Dans la zone de fonction, collez le code suivant :
// 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;
}
Testez votre filtre
Sélectionnez un bloc (par exemple 12193297) pour tester les conditions de votre filtre et vérifier que les alertes se déclenchent correctement. Vous devriez voir une charge utile contenant les transferts classés par catégorie, comme ceci :
{
"largeTransfers": [
{
"blockNumber": 12193297,
"from": "0x7c97cd7b57b736c6ad74fae97c0e21e856251dcf",
"tier": "small_fish",
"timestamp": 1756245832,
"to": "0xaaa2851ec59f335c8c6b4db6738c94fd0305598a",
"tokenContract": "0x5555555555555555555555555555555555555555",
"transactionHash": "0xafe522067fca99d4b44030d82885cabb757943255b991b3f2e95564807dbe0f7",
"value": "2200000000000000000000"
}
]
}
Obtenir le jeton de sécurité et configurer l'URL du webhook
Une fois que vous passerez à l'étape suivante, il vous sera demandé de sélectionner votre destination. Choisissez Webhook comme type de destination. Ensuite, Quicknode générera automatiquement un Jeton de sécurité pour vérifier l'authenticité des requêtes entrantes. Copiez ce jeton dans votre .env fichier comme valeur du WEBHOOK_SECRET variable.
Pour l'URL de destination du flux, vous aurez besoin d'un point de terminaison accessible au public. Pendant le développement, vous pouvez utiliser ngrok ou localtunnel pour rendre votre serveur local accessible. Vous pouvez exécuter ngrok http 3000 (en supposant que votre serveur fonctionne sur le port 3000) et copiez l'URL de redirection HTTPS une fois que votre serveur est en marche. N'oubliez pas d'ajouter /webhook à cela (par exemple, https://your-ngrok-id.ngrok.io/webhook) car c'est là que vous allez créer votre écouteur de webhook.
Comme notre serveur n'est pas encore prêt, nous allons nous arrêter là et nous reviendrons pour tester et activer le flux une fois le serveur mis en place.
Étape 4 : Créer le serveur de webhooks
Créons maintenant l'application Node.js qui recevra et traitera les données provenant de notre webhook.
Configuration du projet et dépendances
Commencez par créer un répertoire pour votre projet et installez les dépendances nécessaires. Vous pouvez utiliser npm ou tout autre gestionnaire de paquets (tel que fil, pnpm, petit pain) pour cela. Par exemple :
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
Ensuite, mettez à jour votre package.json afin d'y inclure les éléments suivants :
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
Structure du projet
Créez les fichiers suivants afin de disposer d'une structure de base pour votre projet :
├── config.js // Paramètres de configuration
├── index.js // Point d'entrée principal
├── priceService.js // Logique liée aux prix
├── security.js // Logique liée à la sécurité
├── telegramService.js // Intégration du bot Telegram
└── .env // Variables d'environnement
└── .gitignore // Fichier d'exclusion Git fichier
Voici la commande d'une seule ligne que vous pouvez exécuter dans votre terminal pour créer tous ces fichiers en un clin d'œil :
touch config.js index.js priceService.js security.js telegramService.js .env .gitignore
# Si la commande « touch » n'est pas disponible, vous pouvez utiliser :
# (echo > config.js) && (echo > index.js) && (echo > priceService.js) && (echo > security.js) && (echo > telegramService.js) && (echo > .env) && (echo > .gitignore)
Variables d'environnement
Mettre à jour le .env fichier situé à la racine de votre projet pour stocker vos variables d'environnement. Ajoutez les variables suivantes :
# Configuration de Telegram
TELEGRAM_BOT_TOKEN=votre_jeton_de_bot_Telegram
TELEGRAM_CHANNEL_ID=votre_identifiant_de_chaîne
# Configuration du serveur
PORT=3000
# Sécurité des webhooks des flux
WEBHOOK_SECRET=votre_secret_webhook_facultatif
# Configuration de Quicknode pour Hyperliquid EVM RPC
HYPERLIQUID_RPC=https://your-endpoint.quiknode.pro/your-token/
# Environnement
NODE_ENV=développement
Mise en œuvre du code
.gitignore
Il est important d'ajouter un .gitignore fichier à votre projet afin d'éviter de valider des informations sensibles et des fichiers superflus. Créez un .gitignore fichier situé à la racine de votre projet et ajoutez-y les lignes suivantes :
node_modules
.env
config.js
Ce fichier contient les paramètres de configuration de votre application, notamment les variables d'environnement et d'autres constantes.
Le spot Le fichier de précompilation « price » se trouve à l'adresse 0x...0808, tandis que le oracle Le prix de la précompilation est de 0x...0807. Vous pouvez utiliser l'un ou l'autre en fonction de votre source de prix ; ce guide utilise spot. Vérifiez toujours les adresses dans les documents officiels et les guides à jour.
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
Ce service est chargé de récupérer le cours de HYPE auprès d'HyperCore.
Nous appellerons le SPOT price est précompilé directement avec un index codé selon l'ABI de 32 octets. La précompilation renvoie un uint64 lorsque la mise à l'échelle décimale dépend de la valeur de l'actif szDecimals (Le système tarifaire d'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
Ce module contient la logique permettant de valider les webhooks entrants. Quicknode recommande d'utiliser la vérification HMAC via les en-têtes. X-QN-Nonce, X-QN-Timestamp, et X-QN-Signature et ce module met en œuvre cette vérification.
Pour plus d'informations, consultez le guide « Validation des signatures des flux ».
// 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
Ce module met en forme et envoie le message final sur votre chaîne Telegram.
// 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
Il s'agit du fichier principal qui relie tous les éléments entre eux.
Nous utilisons un analyseur de corps « raw » Express avant tout middleware JSON pour le /webhook point de terminaison afin de pouvoir vérifier les signatures. Cela garantit que le corps du message est disponible dans sa forme d'origine pour la vérification HMAC.
// 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);
});
Étape 5 : Exécuter et tester le système
Vous êtes désormais prêt à donner vie à votre bot.
Démarrez votre serveur
Exécutez la commande suivante dans votre terminal pour démarrer le serveur. nodemon permet au serveur de redémarrer automatiquement en cas de modification des fichiers.
# Démarrer en mode développement avec nodemon
npm run dev
Rendre votre localhost accessible
Si ce n'est pas déjà fait, ouvrez une nouvelle fenêtre de terminal et exécutez la commande suivante : ngrok http 3000. Copiez l'URL de redirection HTTPS.
Testez votre flux
- Retournez sur la page de votre webhook dans le tableau de bord Quicknode.
- Collez votre URL ngrok (par exemple,
https://your-ngrok-id.ngrok.io/webhook) dans le champ « URL du webhook », puis enregistrez. - Cliquez maintenant sur le bouton « Envoyer une charge utile d'exemple ». Quicknode enverra une charge utile d'exemple à votre serveur en cours d'exécution.
- Vérifiez les journaux de la console de votre serveur et consultez votre chaîne Telegram pour voir s'il y a des alertes.
Voici un exemple de ce à quoi ressemblera l'alerte finale :

Activer votre flux
Une fois que vous avez vérifié que tout fonctionne correctement, cliquez sur le bouton « Créer un flux » pour créer votre flux. Votre bot est désormais opérationnel et surveillera en temps réel tous les nouveaux transferts HYPE.
Dépannage
Il arrive parfois que, si votre serveur ou ngrok s'il ne s'arrête pas correctement, vous risquez de rencontrer une erreur du type « adresse déjà utilisée » lorsque vous tenterez de le redémarrer. Voici comment résoudre rapidement ce problème.
Étape 1 : Libérer le port
Commencez par identifier l'ID du processus (PID) à l'aide du port, puis arrêtez-le. Les commandes ci-dessous s'appliquent à macOS et Linux ; elles peuvent varier en fonction de votre système d'exploitation.
# Find the Process ID (PID) using port 3004
lsof -i :3004
# Replace <PID> with the number you found and run:
kill -9 <PID>
Étape 2 : Redémarrer et mettre à jour
Redémarrez votre serveur, puis ngrok. Important : ngrok crée une nouvelle URL à chaque démarrage. Vous devez copier cette nouvelle URL et la mettre à jour dans les paramètres de votre webhook Quicknode.
Conclusion
Félicitations ! Vous venez de mettre au point avec succès un système d’alerte en temps réel, prêt à être déployé en production, dédié à la blockchain Hyperliquid. En combinant la puissance de Quicknode Streams pour les données sur la chaîne, un serveur Node.js sécurisé pour la logique métier et l’API Telegram pour les notifications, vous avez créé un outil précieux pour la surveillance de l’écosystème DeFi.
Ce modèle est flexible : vous pouvez étendre ses niveaux, l'enrichir avec du contexte supplémentaire sur la chaîne, ou changer de sources et de destinations de données à mesure que votre cas d'utilisation évolue.
Prochaines étapes : déploiement en production
Alors que ngrok est idéal pour le développement, mais vous aurez besoin d'une solution plus permanente pour un environnement de production. Envisagez de déployer votre application sur :
- Un serveur privé virtuel (VPS) tel que DigitalOcean ou AWS, utilisant un gestionnaire de processus comme PM2 pour assurer son fonctionnement
- Un service conteneurisé utilisant Docker
- Une plateforme en tant que service (PaaS) comme Heroku ou Render
Améliorations possibles
Ce projet vous offre une base solide sur laquelle vous pouvez vous appuyer pour aller plus loin. Voici quelques idées pour faire passer votre bot au niveau supérieur :
-
Prise en charge de plusieurs jetons: modifiez le code pour qu'il accepte un tableau d'adresses de jetons. Vous pourrez ainsi surveiller plusieurs jetons avec des seuils différents et envoyer des alertes en conséquence.
-
Tableau de bord des données historiques: Quicknode Streams prend en charge non seulement les données en temps réel, mais aussi les données historiques. Stockez les données de transfert entrantes dans une base de données (par exemple, PostgreSQL ou MongoDB). Vous pourrez ensuite développer une dApp web pour visualiser les tendances historiques, suivre les flux nets de certains portefeuilles de « baleines » et effectuer des analyses on-chain plus approfondies.
-
Ajouter d'autres canaux de notification: intégrez d'autres services de notification, tels que les webhooks Discord.
-
Déclencheurs de trading automatisé: dans le cadre d'un cas d'utilisation plus avancé, vous pouvez utiliser ces alertes pour déclencher des actions sur la blockchain. Par exemple, un transfert important pourrait déclencher un swap.
Autres ressources
- Documentation Hyperliquid
- Guides Quicknode Hyperliquid
- Documentation Quicknode Hyperliquid
- Vidéo : Qu'est-ce que HyperLiquid HyperEVM et comment s'y mettre?
- Vidéo : Comment créer un tableau de bord analytique Hyperliquid
Si vous rencontrez des difficultés ou si vous avez des questions, n'hésitez pas à les poser sur notre Discord. Restez informé des dernières actualités en nous suivant sur X (anciennement Twitter) (@Quicknode) ou sur notre chaîne d'annonces Telegram.
Nous ❤️ les commentaires !
Faites-nous savoir si vous avez des commentaires ou des suggestions de nouveaux sujets. Nous serions ravis de vous entendre.
