跳转至主要内容

构建一个实时Hyperliquid鲸鱼警报机器人

更新于
2026年4月22日

阅读时间 24 分钟


一条流,一个费用,多个目的地

您的Quicknode 流现可通过单一管道同时向多个目的地进行分发。无需重复流,不会出现配置漂移,且每个目的地均不收取额外费用。每个流可用的目的地数量因套餐而异。了解更多

概述

监控链上大额代币流动(通常被称为“鲸鱼”活动)可以为市场情绪和潜在的价格走势提供有价值的洞察。

本指南将引导您在 Hyperliquid 区块链上为 HYPE 代币创建一个实时大额交易提醒机器人。您将构建一个系统,该系统不仅能检测大额转账,还能通过 HyperCore 获取实时美元价格来丰富数据,并向 Telegram 频道发送即时通知。为此,我们将利用Quicknode Streams 的强大功能和高效性能。

您将负责的工作内容


  • 创建一个 Quicknode 流 能够过滤炒作的 转账 来自 Hyperliquid EVM 的事件
  • 在处理之前,使用 HMAC 签名验证有效载荷的真实性
  • 通过 HyperEVM 预编译程序读取 HyperCore 提供的 HYPE现货价格
  • 向一个Telegram频道发送分级鲸鱼警报(鱼、海豚、鲸鱼)

您需要准备的物品


  • 一个带有 Hyperliquid EVM 端点的Quicknode 账户
  • Node.js20 及以上版本、npm(或其他包管理器)以及 VS Code 等代码编辑器
  • 一个 Telegram 账号(如果您想开发一个 Telegram 机器人)
  • JavaScript 基础知识
  • 一种将本地服务器暴露到互联网上的工具,例如ngroklocaltunnel (如果你需要在本地测试 webhook)
为什么选择 Quicknode Streams?

Quicknode Streams 采用“推送”模式。您无需反复向区块链查询新数据(轮询),Streams 会为您监控区块链,并在事件发生的第一时间将相关数据推送至您的应用程序。

这种方法效率极高,具有以下几个主要优点:


  • 实时数据:即时接收通知,无需等待轮询周期的延迟。
  • 降低运维负担:免去了您管理复杂且资源消耗大的轮询基础设施的麻烦。
  • 强大的过滤功能:在 Quicknode 端对数据进行处理和过滤,因此您的服务器只会接收其确切需要的信息。
  • 经济实惠:仅按实际处理的事件数量付费,是进行实时数据监控的经济实惠的解决方案。

Whale Alert 机器人项目

鲸鱼警报机器人由几个相互关联的组件构成,这些组件协同工作以提供实时通知:

  1. Hyperliquid 区块链: A 转账 事件 (Transfer(地址, 地址, uint256)) 当发生转账时,系统会发行HYPE代币。

  2. 带过滤功能的 Quicknode 流:该流会持续监控区块链,并根据我们定义的过滤函数捕获该事件。

  3. Webhook 传递:Quicknode 通过安全的 POST 请求将经过过滤的有效载荷发送到我们的服务器端点。

  4. Node.js 服务器:我们的服务器接收数据,使用 webhook 的安全令牌验证其真实性,然后对其进行处理。

  5. 价格查询:服务器调用Hyperliquid上的HyperCore预编译合约,以获取HYPE当前的美元价格。

  6. Telegram 机器人:最后,服务器会生成一条格式丰富、易于阅读的消息,并利用 Telegram 机器人 API 将警报发送至我们指定的频道。

Hyperliquid 鲸鱼警报机器人的架构

这就是我们将要实现的端到端事件流程。现在,让我们开始构建 Hyperliquid 鲸鱼警报机器人吧。

步骤 1:创建您的 Telegram 机器人和频道

首先,你需要一个 Telegram 机器人以及一个可以发布提醒的频道。

使用 BotFather 创建一个机器人


  1. 打开Telegram,搜索“BotFather”。
  2. 与 BotFather 开始聊天,并使用以下命令 /newbot 创建一个新的机器人。
  3. 请按照提示为您的机器人设置名称和用户名。
  4. BotFather 将为您提供一个机器人令牌。请妥善保存此令牌;您在 .env 文件。

创建频道


  1. 在 Telegram 中创建一个新频道。你可以将其设为公开或私密。
  2. 如果是公共频道,请为其设置一个易于记忆的用户名(例如 @hyperliquid_whales)。这个用户名就是你的 TELEGRAM_CHANNEL_ID.
  3. 对于私有频道,您需要其数字聊天 ID。您可以通过将该频道的一条消息转发给如下机器人来获取该 ID: @JsonDumpCUBot,并检查它提供的聊天ID(即, forward_from_chat.id).

将您的机器人添加到频道中


  1. 打开您新创建的频道的设置。
  2. 添加您的机器人,并将其设为管理员。

现在您已经拥有了您的 TELEGRAM_BOT_TOKEN 还有你的 TELEGRAM_CHANNEL_ID.

步骤 2:创建您的 Quicknode Hyperliquid EVM 端点

现在,请创建您的 Quicknode Hyperliquid EVM 端点,该端点将用于与 Hyperliquid Core 交互,以获取 HYPE 价格数据。

首先,你需要一个Quicknode 账户。如果你已经拥有一个,只需登录即可。进入 Quicknode 仪表盘后:


  • 转到“端点”页面
  • 单击“新建端点”按钮
  • 选择Hyperliquid EVM 主网
  • 创建您的端点

创建端点后,请复制端点 URL 并妥善保存。您需要将其添加到您的 .env 在后续步骤中处理该文件。

第 3 步:创建您的 Quicknode 流

现在,让我们设置一个用于监控 Hyperliquid 区块链的 Quicknode Stream。

创建流


  1. 进入Quicknode 仪表盘,然后转到“流”部分。
  2. 点击“创建流”,然后选择“Hyperliquid EVM 主网”作为区块链。
  3. 选择“带收据的区块”作为数据集
  4. 点击“自定义有效载荷”以应用过滤器,并选择“编写自己的过滤器”选项

设置筛选条件

在本指南中,我们将创建一个自定义 JavaScript 函数来实现分级警报逻辑。

我们将使用的该函数会检查每个新区块中的每笔交易。它专门查找符合 ERC20 标准的事件日志 转账 签名 (0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef(简体中文(大陆))) 且源自 HYPE 代币合约。对于一个 转账 在事件中,topics 数组包含发送方和接收方,而 log.data 其中包含金额信息。我们的代码会解码这些信息,将金额与阈值进行比对,并仅在转账金额足够大时返回经过清理的数据负载。这种预处理效率极高,确保我们的服务器不会将资源浪费在无关数据上。

在函数框中,粘贴以下代码:

// 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;
}

测试您的过滤器

选择一个区块(例如 12193297) 以测试您的过滤条件,并验证警报是否能正确触发。您应该会看到一个包含已分类转账的负载,如下所示:

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

获取安全令牌并设置 Webhook URL

进入下一步后,系统会提示您选择目的地。请选择 Webhook 作为目标类型。然后,Quicknode 将自动生成一个 安全令牌 以验证传入请求的真实性。将此令牌复制到您的 .env 将文件作为 WEBHOOK_SECRET 变量。

对于流的“目标 URL”,您需要一个可公开访问的端点。在开发过程中,您可以使用 ngroklocaltunnel 以使您的本地服务器对外公开。您可以运行 ngrok http 3000 (假设您的服务器运行在3000端口上),待服务器运行后,复制HTTPS转发网址。请记住在网址末尾添加 /webhook/webhook 到它那里(例如, https://your-ngrok-id.ngrok.io/webhook) 因为你将在这里构建你的 webhook 监听器。

由于我们的服务器尚未搭建完成,我们将暂且停在这里,待服务器搭建完成后再回来测试并启用该流。

第 4 步:构建 Webhook 服务器

现在,让我们创建一个 Node.js 应用程序,用于接收并处理来自 Webhook 的数据。

项目设置与依赖项

首先,为您的项目创建一个目录,并安装必要的依赖项。您可以使用 npm 或任何其他软件包管理器(例如 纱线, pnpm, bun) 来实现这一点。例如:

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

然后,更新您的 package.json 包括以下内容:

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

项目结构

创建以下文件,以建立项目的基本结构:

├── config.js           // 配置设置
├── index.js // 主入口
├── priceService.js // 与价格相关的逻辑
├── security.js // 安全相关逻辑
├── telegramService.js // Telegram 机器人集成
└── .env // 环境变量
└── .gitignore // Git 忽略规则 文件

以下是一行命令,你可以在终端中运行它,立即创建所有这些文件:

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

# 如果无法使用 touch 命令,可以使用:
# (echo > config.js) && (echo > index.js) && (echo > priceService.js) && (echo > security.js) && (echo > telegramService.js) && (echo > .env) && (echo > .gitignore)

环境变量

更新 .env 在项目根目录下创建一个文件,用于存储环境变量。添加以下变量:

# Telegram 配置
TELEGRAM_BOT_TOKEN=您的Telegram机器人令牌
TELEGRAM_CHANNEL_ID=您的频道ID

# 服务器配置
端口=3000

# Streams Webhook 安全设置
WEBHOOK_SECRET=您的可选 Webhook 密钥

# Hyperliquid EVM RPC 的 Quicknode 配置
HYPERLIQUID_RPC=https://your-endpoint.quiknode.pro/your-token/

# 环境
NODE_ENV=development

代码实现

.gitignore

添加一个 .gitignore 文件到你的项目中,以避免提交敏感信息和不必要的文件。创建一个 .gitignore 在项目的根目录下创建一个文件,并添加以下几行代码:

node_modules
.env
config.js

该文件包含应用程序的配置设置,包括环境变量和其他常量。

spot price precompile 位于 0x...0808,而 oracle简体中文(大陆) price precompile 是 0x...0807. 您可以根据价格来源选择使用其中一种;本指南使用的是 spot. 请务必通过官方文件和最新指南确认地址。

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

该服务负责从HyperCore获取HYPE的价格。

我们将把该 SPOT price 直接使用 32 字节的 ABI 编码索引进行预编译。该预编译操作返回一个 uint64 其中小数位数取决于资产的 szDecimals (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

该模块包含用于验证传入 Webhook 的逻辑。Quicknode 建议使用标头进行 HMAC 验证 X-QN-Nonce, X-QN-时间戳,以及 X-QN-Signature 而该模块实现了该验证功能。

有关更多详细信息,请参阅《验证流签名》指南。

// 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

该模块负责对最终消息进行格式化,并将其发送至您的 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

这是将所有内容整合在一起的主文件。

在任何 JSON 中间件之前,我们使用 Express 的原始正文解析器来处理 /webhook/webhook 端点以便能够验证签名。这可确保正文以原始形式保留,以便进行 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);
});

第 5 步:运行并测试系统

现在,您可以开始让您的机器人“活”起来了。

启动服务器

在终端中运行以下命令以启动服务器。 nodemon 允许服务器在文件发生更改时自动重启。

# 使用 nodemon 以开发模式启动
npm run dev

将本地主机对外公开

如果您尚未操作,请打开一个新的终端窗口并运行 ngrok http 3000. 复制 HTTPS 转发网址。

测试您的直播


  1. 返回 Quicknode 控制面板中的 Webhook 页面。
  2. 粘贴您的 ngrok URL(例如, https://your-ngrok-id.ngrok.io/webhook) 输入到“Webhook URL”字段中,然后保存。
  3. 现在,请点击“发送示例有效载荷”按钮。Quicknode 将向您正在运行的服务器发送一个示例有效载荷。
  4. 请检查服务器的控制台日志,并查看您的 Telegram 频道是否有警报。

以下是一个最终警报显示效果的示例:

Hyperliquid Whale Bot - 示例消息

激活您的直播

确认一切正常后,点击“创建流”按钮以创建您的。您的机器人现已上线,并将实时监控所有新的 HYPE 转账。

故障排除

有时,如果您的服务器或 ngrok 如果系统未能正常关机,当你尝试重新启动时,可能会遇到“地址已被占用”之类的错误。以下是快速解决此问题的方法。

步骤 1:释放端口

首先,根据端口号查找进程 ID(PID),然后将其停止。以下命令适用于 macOS/Linux;具体命令可能因您的操作系统而异。

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

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

步骤 2:重启并更新

重启服务器,然后 ngrok. 重要提示: ngrok 每次启动时都会生成一个新的 URL。您必须复制这个新 URL,并在 Quicknode 的 webhook 设置中进行更新。

结论

恭喜!您已成功为 Hyperliquid 区块链构建了一个可投入生产使用的实时鲸鱼预警系统。通过结合Quicknode Streams的链上数据处理能力、用于业务逻辑的安全 Node.js 服务器以及用于通知的 Telegram API,您打造了一款用于监控 DeFi 生态系统的宝贵工具。

该模式具有灵活性,您可以根据用例的发展,扩展层级、补充额外的链上上下文,或更换数据源和目标。

下一步:生产环境部署

虽然 ngrok 虽然它非常适合开发,但在生产环境中,您需要一个更持久的解决方案。请考虑将您的应用程序部署到:


  • 像 DigitalOcean 或 AWS 这样的虚拟专用服务器(VPS),使用 PM2 这样的进程管理器来确保其持续运行
  • 使用 Docker 的容器化服务
  • 类似 Heroku 或 Render 的平台即服务(PaaS)

可能的改进

该项目为你打下了坚实的基础,你可以在此基础上进一步拓展。以下是一些将你的聊天机器人提升到更高水平的建议:

  • 多代币支持:修改代码以支持接收代币地址数组。这样,您就可以针对多个代币设置不同的阈值进行监控,并据此发送警报。

  • 历史数据仪表盘:Quicknode Streams 不仅支持实时数据,还支持历史数据。将传入的转账数据存储在数据库中(例如 PostgreSQL、MongoDB)。随后,您可以构建一个基于 Web 的去中心化应用(dApp),用于可视化历史趋势、追踪特定大户钱包的净流量,并进行更深入的链上分析。

  • 添加更多通知渠道:集成其他通知服务,例如 Discord Webhooks。

  • 自动交易触发条件:对于更高级的应用场景,您可以利用这些警报来触发链上操作。例如,一笔大额转账可能会触发代币兑换。

更多资源


如果您遇到困难或有任何疑问,欢迎在我们的Discord 频道中提出。请关注我们在X(原 Twitter)上的账号 (@Quicknode) 或我们的Telegram 公告频道,以获取最新动态。

我们 ❤️ 您的反馈!

如果您有任何反馈或对新主题的建议,请告诉我们。我们非常期待您的来信。

分享本指南