跳至主要內容

建立一個即時「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 (若您需要在本地端測試 webhooks)
為什麼選擇 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 的當前 USD 價格。

  6. Telegram 機器人:最後,伺服器會將訊息格式化為內容豐富且易於閱讀的格式,並透過 Telegram 機器人 API 將警示訊息發送至我們指定的頻道。

Hyperliquid 鯨魚警報機器人的架構

這就是我們即將實作的端到端事件流程。現在,讓我們開始建置 Hyperliquid Whale Alert 機器人吧。

步驟 1:建立您的 Telegram 機器人與頻道

首先,你需要一個 Telegram 機器人,以及一個可供該機器人發布通知的頻道。

使用 BotFather 建立一個聊天機器人


  1. 開啟 Telegram,並搜尋「BotFather」。
  2. 開啟與 BotFather 的對話,並使用以下指令 /newbot 建立一個新的聊天機器人。
  3. 請依照提示為您的聊天機器人設定名稱和使用者名稱。
  4. BotFather 將提供您一個 Bot Token。請妥善保存此代幣;您將需要它來進行您的 .env 檔案。

建立頻道


  1. 在 Telegram 中建立一個新頻道。您可以將其設為公開或私人頻道。
  2. 如果是公開頻道,請為其設定一個易於記憶的使用者名稱(例如:@hyperliquid_whales)。這個使用者名稱就是您的 TELEGRAM_CHANNEL_ID.
  3. 若要使用私人頻道,您需要該頻道的數字聊天室 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 變數。

關於串流的目標網址,您需要一個可對外存取的端點。在開發過程中,您可以使用 ngroklocaltunnel 以公開您的本地伺服器。您可以執行 ngrok http 3000 (假設您的伺服器運行於 3000 埠),待伺服器啟動後,請複製 HTTPS 轉發網址。請記得在網址後方加上 /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

# 伺服器設定
PORT=3000

# 串流 Webhook 安全性
WEBHOOK_SECRET=您的可選 Webhook 密鑰

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

# 環境
NODE_ENV=開發

程式碼實作

.gitignore

添加一個 .gitignore 將檔案加入您的專案中,以避免將敏感資訊和不必要的檔案提交至版本控制系統。建立一個 .gitignore 在專案根目錄中建立一個檔案,並加入以下幾行:

node_modules
.env
config.js

此檔案包含您應用程式的設定,包括環境變數及其他常數。

spot price precompile 位於 0x...0808, 而 oracle 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 端點才能驗證簽名。這可確保訊息正文能以原始形式供 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

公開您的 Localhost

若您尚未執行,請開啟一個新的終端機視窗,並執行 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:釋放埠號

首先,根據埠號找出程序識別碼(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)中。接著,您可以建置一個基於網頁的去中心化應用程式(dApp),用以視覺化歷史趨勢、追蹤特定鯨魚錢包的淨資金流向,並進行更深入的鏈上分析。

  • 新增更多通知管道:整合其他通知服務,例如 Discord Webhooks。

  • 自動交易觸發條件:針對更進階的使用情境,您可以利用這些警示來觸發鏈上操作。例如,一筆大額轉帳可能會觸發一筆兌換交易。

更多資源


如果您遇到困難或有任何疑問,歡迎在我們的Discord 頻道中提出。請追蹤我們的X(原 Twitter)帳號 (@Quicknode)或加入我們的Telegram 公告頻道,以掌握最新動態。

我們 ❤️ 您的回饋!

如果您有任何意見回饋或對新主題的建議,請告訴我們。我們非常樂意聽取您的意見。

分享這份指南