> ## Documentation Index
> Fetch the complete documentation index at: https://docs.chainstream.io/llms.txt
> Use this file to discover all available pages before exploring further.

# 構建價格預警機器人

> 使用 WebSocket + Telegram 構建實時價格監控機器人

本教程將帶您從零構建一個實時價格監控機器人，當目標 Token 價格變動超過設定閾值時，自動傳送 Telegram 通知。

<Info>
  **預計時間**：30 分鐘\
  **難度等級**：⭐⭐ 入門
</Info>

***

## 目標

構建一個監控代幣價格並自動通知的 Bot：

```mermaid theme={null}
flowchart TD
    A[ChainStream WebSocket] --> B[Price Bot]
    B --> C[Telegram 通知]
```

**功能清單**：

* ✅ 訂閱實時價格流
* ✅ 設定價格變動觸發條件（> X%）
* ✅ 傳送 Telegram 通知
* ✅ 支援多幣種監控

***

## 技術棧

| 元件   | 技術               | 用途     |
| ---- | ---------------- | ------ |
| 語言   | Node.js 18+      | 主開發語言  |
| 實時資料 | WebSocket        | 訂閱價格流  |
| 通知   | Telegram Bot API | 傳送告警   |
| 配置   | 環境變數             | 儲存敏感資訊 |

***

## 前置條件

* ChainStream 賬戶（獲取 Access Token）
* Node.js 18+
* Telegram 賬號

***

## Step 1：連線 WebSocket

### 1.1 安裝依賴

```bash theme={null}
npm install @chainstream-io/sdk node-telegram-bot-api dotenv
```

### 1.2 建立專案結構

```
price-alert-bot/
├── .env
├── config.js
├── bot.js
└── index.js
```

### 1.3 配置檔案

**.env**：

```
CHAINSTREAM_ACCESS_TOKEN=your_access_token
TELEGRAM_BOT_TOKEN=your_bot_token
TELEGRAM_CHAT_ID=your_chat_id
```

**config.js**：

```javascript theme={null}
import 'dotenv/config';

// ChainStream 配置
export const CHAINSTREAM_ACCESS_TOKEN = process.env.CHAINSTREAM_ACCESS_TOKEN;

// Telegram 配置
export const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
export const TELEGRAM_CHAT_ID = process.env.TELEGRAM_CHAT_ID;

// 监控配置
export const WATCH_TOKENS = [
  {
    chain: 'sol',
    address: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN',
    symbol: 'EXAMPLE',
    thresholdPercent: 3.0  // 3% 变动触发
  },
  {
    chain: 'sol',
    address: 'So11111111111111111111111111111111111111112',
    symbol: 'SOL',
    thresholdPercent: 5.0  // 5% 变动触发
  }
];
```

### 1.4 WebSocket 連線

**index.js**：

```javascript theme={null}
import { ChainStreamClient } from '@chainstream-io/sdk';
import { CHAINSTREAM_ACCESS_TOKEN, WATCH_TOKENS } from './config.js';
import { sendAlert } from './bot.js';

class PriceMonitor {
  constructor() {
    this.client = new ChainStreamClient(CHAINSTREAM_ACCESS_TOKEN);
    this.lastPrices = new Map(); // 记录上次价格
  }

  async start() {
    console.log('✅ 开始监控价格...');

    // 订阅每个 Token 的统计数据
    for (const token of WATCH_TOKENS) {
      this.subscribeToken(token);
    }
  }

  subscribeToken(token) {
    this.client.stream.subscribeTokenStats({
      chain: token.chain,
      tokenAddress: token.address,
      callback: (data) => this.handlePriceUpdate(token, data)
    });

    console.log(`📡 已订阅 ${token.symbol} 价格流`);
  }

  handlePriceUpdate(token, data) {
    const currentPrice = data.price || data.p;
    if (!currentPrice) return;

    const lastPrice = this.lastPrices.get(token.address);

    if (lastPrice) {
      // 计算变动百分比
      const changePercent = ((currentPrice - lastPrice) / lastPrice) * 100;

      // 检查是否触发告警
      if (Math.abs(changePercent) >= token.thresholdPercent) {
        this.triggerAlert(token, currentPrice, changePercent);
      }
    }

    // 更新价格记录
    this.lastPrices.set(token.address, currentPrice);
  }

  async triggerAlert(token, price, change) {
    const direction = change > 0 ? '📈 上涨' : '📉 下跌';

    const message = `
${direction} 价格告警！

🪙 Token: ${token.symbol}
💰 当前价格: $${price.toFixed(6)}
📊 变动幅度: ${change >= 0 ? '+' : ''}${change.toFixed(2)}%
⚡ 触发阈值: ${token.thresholdPercent}%
    `.trim();

    await sendAlert(message);
    console.log(`🚨 已发送告警: ${token.symbol} ${change >= 0 ? '+' : ''}${change.toFixed(2)}%`);
  }
}

// 启动监控
const monitor = new PriceMonitor();
monitor.start();
```

***

## Step 2：設定觸發條件

觸發條件已在 `config.js` 中配置：

```javascript theme={null}
export const WATCH_TOKENS = [
  {
    symbol: 'EXAMPLE',
    thresholdPercent: 3.0  // 价格变动 > 3% 时触发
  },
  // ...
];
```

### 高階觸發條件

可以擴充套件為更復雜的條件：

```javascript theme={null}
// 多条件触发
const ALERT_CONDITIONS = {
  priceChange: {
    enabled: true,
    thresholdPercent: 5.0
  },
  priceAbove: {
    enabled: true,
    value: 100  // 价格超过 $100 时触发
  },
  priceBelow: {
    enabled: true,
    value: 50   // 价格低于 $50 时触发
  }
};
```

***

## Step 3：傳送通知

### 3.1 建立 Telegram Bot

<Steps>
  <Step title="建立 Bot">
    在 Telegram 中搜尋 `@BotFather`，傳送 `/newbot`
  </Step>

  <Step title="獲取 Token">
    按提示建立 Bot，獲取 Bot Token
  </Step>

  <Step title="獲取 Chat ID">
    * 給 Bot 傳送一條訊息
    * 訪問 `https://api.telegram.org/bot<TOKEN>/getUpdates`
    * 找到 `chat.id`
  </Step>
</Steps>

### 3.2 Telegram 通知模組

**bot.js**：

```javascript theme={null}
import TelegramBot from 'node-telegram-bot-api';
import { TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID } from './config.js';

const bot = new TelegramBot(TELEGRAM_BOT_TOKEN);

export async function sendAlert(message) {
  try {
    await bot.sendMessage(TELEGRAM_CHAT_ID, message, {
      parse_mode: 'HTML'
    });
  } catch (error) {
    console.error(`❌ Telegram 发送失败: ${error.message}`);
  }
}

export async function sendAlertWithRetry(message, maxRetries = 3) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      await sendAlert(message);
      return true;
    } catch (error) {
      if (attempt < maxRetries - 1) {
        // 指数退避
        await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000));
      } else {
        console.error(`❌ 通知发送失败，已重试 ${maxRetries} 次`);
        return false;
      }
    }
  }
}
```

***

## 驗證安裝

### 執行 Bot

```bash theme={null}
node index.js
```

### 預期輸出

```
✅ 开始监控价格...
📡 已订阅 EXAMPLE 价格流
📡 已订阅 SOL 价格流
```

### 觸發測試

可以臨時將閾值設為 0.01% 來快速測試：

```javascript theme={null}
thresholdPercent: 0.01  // 测试用
```

***

## 擴充套件建議

<Tabs>
  <Tab title="多幣種監控">
    ```javascript theme={null}
    // 从 API 动态获取监控列表
    async function fetchWatchlist() {
      const response = await fetch('https://api.chainstream.io/v1/watchlist');
      return response.json();
    }
    ```
  </Tab>

  <Tab title="持久化儲存">
    ```javascript theme={null}
    import Database from 'better-sqlite3';

    const db = new Database('alerts.db');

    // 创建表
    db.exec(`
      CREATE TABLE IF NOT EXISTS alerts (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        symbol TEXT,
        price REAL,
        change REAL,
        timestamp INTEGER
      )
    `);

    function saveAlert(alertData) {
      const stmt = db.prepare(`
        INSERT INTO alerts (symbol, price, change, timestamp)
        VALUES (?, ?, ?, ?)
      `);
      stmt.run(
        alertData.symbol,
        alertData.price,
        alertData.change,
        Date.now()
      );
    }
    ```
  </Tab>

  <Tab title="Web 儀表盤">
    ```javascript theme={null}
    import express from 'express';

    const app = express();

    app.get('/alerts', (req, res) => {
      const alerts = getRecentAlerts();
      res.json({ alerts });
    });

    app.post('/config', (req, res) => {
      // 更新监控配置
      updateConfig(req.body);
      res.json({ success: true });
    });

    app.listen(3000);
    ```
  </Tab>

  <Tab title="多通知渠道">
    ```javascript theme={null}
    async function sendNotification(message, channels) {
      const tasks = [];
      
      if (channels.includes('telegram')) {
        tasks.push(sendTelegram(message));
      }
      if (channels.includes('discord')) {
        tasks.push(sendDiscord(message));
      }
      if (channels.includes('email')) {
        tasks.push(sendEmail(message));
      }
      
      await Promise.all(tasks);
    }
    ```
  </Tab>
</Tabs>

***

## 常見問題

<AccordionGroup>
  <Accordion title="WebSocket 連線失敗？" icon="plug">
    1. 檢查 Access Token 是否正確
    2. 確認網路可訪問 ChainStream
    3. 檢視是否有防火牆限制 WebSocket
  </Accordion>

  <Accordion title="Telegram 通知收不到？" icon="telegram">
    1. 確認 Bot Token 正確
    2. 確認 Chat ID 正確
    3. 確保已給 Bot 傳送過訊息（啟用對話）
  </Accordion>

  <Accordion title="如何監控更多 Token？" icon="coins">
    在 `config.js` 的 `WATCH_TOKENS` 陣列中新增更多配置即可。
  </Accordion>
</AccordionGroup>

***

## 相關文件

<CardGroup cols={2}>
  <Card title="WebSocket API" icon="plug" href="/zh-Hant/api-reference/endpoint/websocket/api">
    WebSocket 訂閱詳情
  </Card>

  <Card title="Webhook 基礎" icon="webhook" href="/zh-Hant/docs/recipes/webhook-fundamentals">
    使用 Webhook 替代 WebSocket
  </Card>
</CardGroup>
