24 minutos de leitura
O seu Quicknode Stream já permite a transmissão para vários destinos em simultâneo a partir de um único pipeline. Sem transmissões duplicadas, sem desfasamentos de configuração e sem custos adicionais por destino. Os destinos disponíveis por transmissão variam consoante o plano. Saiba mais.
Visão geral
A monitorização de grandes movimentos de tokens na cadeia de blocos, frequentemente designada por atividade das «baleias», pode fornecer informações valiosas sobre o sentimento do mercado e a evolução potencial dos preços.
Este guia irá orientá-lo na criação de um bot de alerta de grandes transações em tempo real para o token HYPE na blockchain Hyperliquid. Irá construir um sistema que não só deteta grandes transferências, mas também enriquece os dados com os preços do dólar americano em tempo real, provenientes do HyperCore, e envia notificações instantâneas para um canal do Telegram. Para tal, iremos tirar partido do poder e da eficiência do Quicknode Streams.
O que irá fazer
- Criar um Quicknode Stream que filtra o HYPE
Transferênciaeventos da Hyperliquid EVM - Verifique a autenticidade da carga útil através de uma assinatura HMAC antes do processamento
- Consulte o preço à vista do HYPE da HyperCore através das pré-compilações do HyperEVM
- Enviar alertas de baleias por níveis (peixe, golfinho, baleia) para um canal do Telegram
O que vai precisar
- Uma conta Quicknode com um ponto de ligação Hyperliquid EVM
- Node.js 20+, npm (ou outro gestor de pacotes) e um editor de código como o VS Code
- Uma conta no Telegram (caso pretenda criar um bot no Telegram)
- Conhecimentos básicos de JavaScript
- Uma ferramenta para expor o seu servidor local à Internet, como o ngrok ou o localtunnel (se precisar de testar webhooks localmente)
Os Quicknode Streams funcionam segundo um modelo «push». Em vez de ter de solicitar repetidamente novos dados à blockchain (polling), os Streams monitorizam a cadeia por si e enviam os dados relevantes para a sua aplicação no momento em que ocorre um evento.
Esta abordagem é altamente eficiente, proporcionando várias vantagens fundamentais:
- Dados em tempo real: Receba notificações instantaneamente, sem a latência dos ciclos de sondagem.
- Menos sobrecarga: evita que tenha de gerir uma infraestrutura de sondagem complexa e que consome muitos recursos.
- Filtragem avançada: Processe e filtre os dados no servidor da Quicknode, para que o seu servidor receba apenas a informação exata de que necessita.
- Económico: Pague apenas por cada evento transmitido, o que torna esta solução acessível para a monitorização de dados em tempo real.
Projeto Whale Alert Bot
O bot de alertas de baleias é composto por vários componentes interligados que funcionam em conjunto para enviar notificações em tempo real:
-
Blockchain hiperlíquida: A
Transferênciaevento (Transfer(endereço, endereço, uint256)) do token HYPE é emitida sempre que ocorre uma transferência. -
Quicknode Streams com filtragem: O Stream monitoriza constantemente a cadeia e capta este evento com base na função de filtragem que definirmos.
-
Entrega do Webhook: O Quicknode envia a carga útil filtrada através de um pedido POST seguro para o ponto final do nosso servidor.
-
Servidor Node.js: O nosso servidor recebe os dados, verifica a sua autenticidade utilizando o token de segurança do webhook e processa-os.
-
Obtenção do preço: O servidor invoca o contrato de pré-compilação HyperCore na Hyperliquid para obter o preço atual do HYPE em dólares americanos.
-
Bot do Telegram: Por fim, o servidor cria uma mensagem rica e de fácil leitura e utiliza a API do Bot do Telegram para enviar o alerta para o nosso canal designado.

Este é o fluxo completo do evento que vamos implementar. Agora, vamos começar a criar o bot «Whale Alert» no Hyperliquid.
Passo 1: Criar o seu bot e o seu canal no Telegram
Em primeiro lugar, vais precisar de um bot do Telegram e de um canal onde ele possa publicar alertas.
Criar um bot com o BotFather
- Abre o Telegram e procura o BotFather.
- Inicie uma conversa com o BotFather e utilize o comando
/newbotpara criar um novo bot. - Segue as instruções para definir um nome e um nome de utilizador para o teu bot.
- O BotFather irá fornecer-lhe um Bot Token. Guarde este token num local seguro; vai precisar dele para o seu
.envficheiro.
Criar um canal
- No Telegram, crie um novo canal. Pode torná-lo público ou privado.
- No caso de um canal público, atribui-lhe um nome de utilizador fácil de memorizar (por exemplo, @hyperliquid_whales). Este nome de utilizador é o teu
TELEGRAM_CHANNEL_ID. - No caso de um canal privado, vai precisar do seu ID numérico de chat. Pode obtê-lo reencaminhando uma mensagem do canal para um bot como
@JsonDumpCUBot, e verificar o ID do chat que este fornece (ou seja,forward_from_chat.id).
Adicione o seu bot ao canal
- Abre as definições do teu canal recém-criado.
- Adicione o seu bot e torne-o administrador.
Já tens o teu TELEGRAM_BOT_TOKEN e o teu TELEGRAM_CHANNEL_ID.
Passo 2: Criar o seu ponto de extremidade Hyperliquid EVM no Quicknode
Agora, crie o seu ponto de extremidade Quicknode Hyperliquid EVM, que será utilizado para interagir com o Hyperliquid Core, a fim de obter dados sobre os preços do HYPE.
Primeiro, vai precisar de uma conta no Quicknode. Se já tiver uma, basta iniciar sessão. Assim que estiver no painel de controlo do Quicknode:
- Aceda à página «Endpoints»
- Clique no botão «Novo ponto de extremidade »
- Selecione a rede Hyperliquid EVM Mainnet
- Crie o seu ponto de extremidade
Depois de criar o seu endpoint, copie o URL do mesmo e mantenha-o à mão. Terá de o adicionar ao seu .env ficheiro numa etapa posterior.
Passo 3: Criar o seu fluxo Quicknode
Agora, vamos configurar o Quicknode Stream que irá monitorizar a blockchain Hyperliquid.
Criar a transmissão
- Aceda ao Painel de Controlo do Quicknode e aceda à secção «Streams ».
- Clique em «Criar Stream » e selecione «Hyperliquid EVM Mainnet » como blockchain.
- Selecione «Bloco com recibos » como conjunto de dados
- Clique em «Personalizar a sua carga útil » para aplicar um filtro e selecione a opção «Criar o seu próprio filtro»
Defina o seu filtro
Para este guia, vamos criar uma função JavaScript personalizada para implementar a nossa lógica de alertas por níveis.
A função que iremos utilizar analisa todas as transações em cada novo bloco. Procura especificamente registos de eventos que correspondam ao padrão ERC20 Transferência assinatura (0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef) e têm origem no HYPE contrato de token. Para um Transferência No evento, a matriz «topics» contém o remetente e o destinatário, enquanto log.data contém o montante. O nosso código descodifica esta informação, compara o montante com os nossos limites e devolve uma carga de dados limpa apenas se a transferência for suficientemente elevada. Este pré-processamento é incrivelmente eficiente, garantindo que o nosso servidor não desperdiça recursos com dados irrelevantes.
Na caixa de função, cole o seguinte código:
// 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;
}
Teste o seu filtro
Selecione um bloco (por exemplo, 12193297) para testar as condições do seu filtro e verificar se os alertas são acionados corretamente. Deverá ver uma carga útil que contenha as transferências categorizadas, como esta:
{
"largeTransfers": [
{
"blockNumber": 12193297,
"from": "0x7c97cd7b57b736c6ad74fae97c0e21e856251dcf",
"tier": "small_fish",
"timestamp": 1756245832,
"to": "0xaaa2851ec59f335c8c6b4db6738c94fd0305598a",
"tokenContract": "0x5555555555555555555555555555555555555555",
"transactionHash": "0xafe522067fca99d4b44030d82885cabb757943255b991b3f2e95564807dbe0f7",
"value": "2200000000000000000000"
}
]
}
Obter o token de segurança e definir o URL do webhook
Assim que avançar para o passo seguinte, ser-lhe-á pedido que selecione o seu destino. Escolha Webhook como tipo de destino. Em seguida, o Quicknode irá gerar automaticamente um Token de segurança para verificar se os pedidos recebidos são autênticos. Copie este token para o seu .env ficheiro como valor do WEBHOOK_SECRET variável.
Para o URL de destino do fluxo, vai precisar de um ponto final acessível ao público. Durante o desenvolvimento, pode utilizar ngrok ou localtunnel para tornar o seu servidor local acessível. Pode executar ngrok http 3000 (partindo do princípio de que o seu servidor está a funcionar na porta 3000) e copie o URL de reencaminhamento HTTPS assim que o seu servidor estiver a funcionar. Lembre-se de acrescentar /webhook a isso (por exemplo, https://your-ngrok-id.ngrok.io/webhook) uma vez que é aqui que irá criar o seu ouvinte de webhook.
Como o nosso servidor ainda não está criado, vamos fazer uma pausa por aqui e voltaremos para testar e ativar o Stream depois de criarmos o servidor.
Passo 4: Criar o servidor de webhooks
Agora, vamos criar a aplicação Node.js que irá receber e processar os dados do nosso webhook.
Configuração do projeto e dependências
Primeiro, crie um diretório para o seu projeto e instale as dependências necessárias. Pode utilizar npm ou qualquer outro gestor de pacotes (como, por exemplo, fio, pnpm, pãozinho) para o fazer. Por exemplo:
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
Em seguida, atualize o seu package.json para incluir o seguinte:
"type": "module",
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
Estrutura do projeto
Crie os seguintes ficheiros para obter uma estrutura básica para o seu projeto:
├── config.js // Definições de configuração
├── index.js // Ponto de entrada principal
├── priceService.js // Lógica relacionada com preços
├── security.js // Lógica relacionada com a segurança
├── telegramService.js // Integração com o bot do Telegram
└── .env // Variáveis de ambiente
└── .gitignore // Ignorar no Git ficheiro
Eis o comando de uma linha que pode executar no seu terminal para criar todos esses ficheiros instantaneamente:
touch config.js index.js priceService.js security.js telegramService.js .env .gitignore
# Se o comando «touch» não estiver disponível, pode utilizar:
# (echo > config.js) && (echo > index.js) && (echo > priceService.js) && (echo > security.js) && (echo > telegramService.js) && (echo > .env) && (echo > .gitignore)
Variáveis de ambiente
Atualizar o .env ficheiro na raiz do seu projeto para guardar as suas variáveis de ambiente. Adicione as seguintes variáveis:
# Configuração do Telegram
TELEGRAM_BOT_TOKEN=o_teu_token_do_bot_do_Telegram
TELEGRAM_CHANNEL_ID=o_teu_id_de_canal
# Configuração do servidor
PORTA=3000
# Segurança do Webhook de transmissões
WEBHOOK_SECRET=o_seu_segredo_opcional_para_o_webhook
# Configuração do Quicknode para o Hyperliquid EVM RPC
HYPERLIQUID_RPC=https://your-endpoint.quiknode.pro/your-token/
# Ambiente
NODE_ENV=desenvolvimento
Implementação do código
.gitignore
É importante adicionar um .gitignore adicione o ficheiro ao seu projeto para evitar o commit de informações confidenciais e ficheiros desnecessários. Crie um .gitignore ficheiro na raiz do seu projeto e adicione as seguintes linhas:
node_modules
.env
config.js
Este ficheiro contém as definições de configuração da sua aplicação, incluindo variáveis de ambiente e outras constantes.
O ponto O preço da pré-compilação encontra-se em 0x...0808, enquanto o oráculo O preço da pré-compilação é 0x...0807. Pode utilizar qualquer uma das opções, dependendo da sua fonte de preços; este guia utiliza ponto. Confirme sempre os endereços em documentos oficiais e guias atualizados.
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
Este serviço é responsável por obter o preço do HYPE a partir do HyperCore.
Vamos chamar o SPOT A função `price` é pré-compilada diretamente com um índice codificado em ABI de 32 bytes. A pré-compilação devolve um uint64 em que a escala decimal depende do ativo szDecimals (Sistema de preços da 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
Este módulo contém a lógica para validar os webhooks recebidos. A Quicknode recomenda a verificação HMAC através dos cabeçalhos X-QN-Nonce, X-QN-Timestamp, e X-QN-Signature e este módulo implementa essa verificação.
Para mais informações, consulte o guia «Validação de assinaturas de fluxos ».
// 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
Este módulo formata e envia a mensagem final para o teu canal do 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
Este é o ficheiro principal que une tudo.
Utilizamos um analisador de corpo «raw» do Express antes de qualquer middleware JSON para o /webhook ponto final para poder verificar as assinaturas. Isto garante que o corpo da mensagem esteja disponível na sua forma original para a verificação 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);
});
Passo 5: Executar e testar o sistema
Já estás pronto para dar vida ao teu bot.
Inicie o seu servidor
Execute o seguinte comando no seu terminal para iniciar o servidor. nodemon permite que o servidor reinicie automaticamente sempre que houver alterações nos ficheiros.
# Iniciar no modo de desenvolvimento com o nodemon
npm run dev
Exponha o seu localhost
Se ainda não o fez, abra uma nova janela do terminal e execute ngrok http 3000. Copie o URL de reencaminhamento HTTPS.
Teste a sua transmissão
- Volta à página do teu webhook no painel de controlo do Quicknode.
- Cole o seu URL do ngrok (por exemplo,
https://your-ngrok-id.ngrok.io/webhook) no campo «URL do Webhook» e guarde. - Agora, clique no botão «Enviar carga útil de amostra ». O Quicknode enviará uma carga útil de amostra para o seu servidor em execução.
- Verifique os registos da consola do seu servidor e verifique se há alertas no seu canal do Telegram.
Eis um exemplo de como ficará o alerta final:

Ativar a sua transmissão
Depois de confirmar que tudo está a funcionar, clique no botão «Criar um Stream» para criar o seu Stream. O seu bot já está ativo e irá monitorizar todas as novas transferências de HYPE em tempo real.
Resolução de problemas
Por vezes, se o seu servidor ou ngrok Se o programa não encerrar corretamente, poderá surgir um erro do tipo «endereço já em uso» quando tentar reiniciá-lo. Eis como resolver rapidamente esse problema.
Passo 1: Liberar a porta
Primeiro, identifique o ID do processo (PID) utilizando a porta e, em seguida, interrompa-o. Os comandos abaixo destinam-se ao macOS/Linux; podem variar consoante o seu sistema operativo.
# Find the Process ID (PID) using port 3004
lsof -i :3004
# Replace <PID> with the number you found and run:
kill -9 <PID>
Passo 2: Reiniciar e atualizar
Reinicie o seu servidor e ngrok. Importante: ngrok cria um novo URL sempre que é iniciado. Deve copiar este novo URL e atualizá-lo nas definições do webhook do Quicknode.
Conclusão
Parabéns! Conseguiu criar com sucesso um sistema de alerta em tempo real sobre transações de grande valor (whale), pronto para produção, para a blockchain Hyperliquid. Ao combinar o potencial do Quicknode Streams para dados na cadeia, um servidor Node.js seguro para a lógica de negócio e a API do Telegram para notificações, criou uma ferramenta valiosa para monitorizar o ecossistema DeFi.
Este padrão é flexível: pode alargar as camadas, enriquecê-lo com contexto adicional na cadeia de blocos ou trocar as fontes e destinos de dados à medida que o seu caso de utilização evolui.
Próximos passos: Implementação em produção
Embora ngrok É excelente para o desenvolvimento, mas vai precisar de uma solução mais permanente para um ambiente de produção. Considere implementar a sua aplicação em:
- Um Servidor Privado Virtual (VPS), como o DigitalOcean ou o AWS, utilizando um gestor de processos como o PM2 para o manter em funcionamento
- Um serviço em contêineres que utiliza o Docker
- Uma Plataforma como Serviço (PaaS), como o Heroku ou o Render
Possíveis melhorias
Este projeto proporciona uma base sólida que podes ir ampliando. Aqui ficam algumas ideias para levares o teu bot a um novo nível:
-
Suporte a vários tokens: Altere o código para aceitar um conjunto de endereços de tokens. Desta forma, poderá monitorizar vários tokens com limiares diferentes e enviar alertas em conformidade.
-
Painel de dados históricos: O Quicknode Streams suporta não só dados em tempo real, mas também dados históricos. Armazene os dados de transferências recebidas numa base de dados (por exemplo, PostgreSQL, MongoDB). Poderá então criar uma dApp baseada na Web para visualizar tendências históricas, acompanhar o fluxo líquido de carteiras específicas de «baleias» e realizar análises mais aprofundadas na cadeia de blocos.
-
Adicionar mais canais de notificação: integrar outros serviços de notificação, como os webhooks do Discord.
-
Desencadeadores de negociação automatizada: Para um caso de utilização mais avançado, pode utilizar estes alertas para desencadear ações na cadeia de blocos. Por exemplo, uma transferência de grande valor pode desencadear uma troca.
Outros recursos
- Documentação do Hyperliquid
- Guias do Quicknode Hyperliquid
- Documentação do Quicknode Hyperliquid
- Vídeo: O que é o HyperEVM da Hyperliquid e como começar
- Vídeo: Como criar um painel de análise do Hyperliquid
Se estiver com dificuldades ou tiver dúvidas, partilhe-as no nosso Discord. Mantenha-se a par das últimas novidades seguindo-nos no X (anteriormente Twitter) (@Quicknode) ou no nosso canal de anúncios do Telegram.
Adoramos ❤️ os vossos comentários!
Diz-nos se tiveres algum comentário ou sugestão de novos temas. Adoraríamos saber a tua opinião.
