跳转至主要内容

构建一个实时Hyperliquid投资组合追踪器

更新于
2026年4月22日

阅读时间 17 分钟

概述

作为 Hyperliquid 上的长期交易者,拥有一个全面的投资组合追踪工具对于实时监控您的持仓、盈亏和保证金使用率至关重要。本指南将向您展示如何利用 Quicknode 的 Hyperliquid 信息接口,构建一个功能强大的投资组合追踪工具,用于监控任何 Hyperliquid 钱包。 除了创建一个实用的交易工具外,本教程还将揭秘如何获取、组织和利用 HyperCore 数据来构建该应用程序。该应用程序展示了:


  • 持仓实时追踪——对永续合约持仓的实时更新及盈亏(PnL)
  • 投资组合分析——账户价值、保证金使用情况及风险指标
  • 保管库管理——跟踪保管库价值和封存计划
  • Spot Holdings- 监控代币余额和美元价值
  • 搜索任意钱包- 在不同交易账户之间切换

主页


仪表盘页面

您将负责的工作内容

分4个阶段构建一个完整的投资组合追踪工具:


  1. 设置 Quicknode 端点和 Supabase 账户
  2. 设置数据库模式
  3. 创建一个每500毫秒从HyperCore获取数据的索引器
  4. 使用现代 UI 库构建一个显示实时交易数据的仪表盘

您需要准备的物品


为什么选择 Quicknode 端点?

Quicknode 提供了专用的 Hyperliquid API 端点,您无需再自行运行节点:

  • 预配置的端点,无需任何设置
  • 负责连接管理和故障转移
  • 无需额外基础设施即可直接访问 HyperCore 数据

架构概述

该投资组合追踪器由三个组件构成,这些组件通过 PostgreSQL 数据库进行通信。索引器从 Hyperliquid 获取数据并将其存储在数据库中,前端则通过查询数据库来显示数据。

技术栈组件

  • 前端:React + TypeScript + Tailwind CSS + shadcn/ui 和 Radix UI

    • 在响应式界面中显示交易数据
    • 每1000毫秒查询一次数据库以获取更新
    • 通过数据库请求处理钱包切换
  • 后端:基于 Node.js 的索引器,轮询间隔为 500 毫秒

    • 从 5 个不同的 Hyperliquid 端点获取数据
    • 将数据存储在 PostgreSQL 中,并进行正确的精度处理
    • 处理来自前端的钱包切换请求
关于投票的注意事项

本指南采用较短的轮询间隔(索引器为 500 毫秒,前端为 1000 毫秒)来演示实时更新。如有需要,您可以调整这些间隔:

  • 前端: src/Dashboard.tsx 第 260-264 行——将 1000 值位于:
    const interval = setInterval(async () => {
    await fetchData(currentWallet);
    }, 1000);
  • 索引员: src/indexer/indexer.ts 第 623-630 行——将 500 值位于:
    setInterval(async () => {
    await indexer.checkForWalletSwitch();
    await indexer.indexData();
    }, 500);

监控您的 Quicknode 和 Supabase 使用情况,以优化成本。

  • 数据库:Supabase PostgreSQL

    • 将交易数据以金融精度存储在 6 张表中(DECIMAL 类型)
    • 通过以下方式处理前端与索引器之间的通信: 钱包切换请求 表格
    • 使用唯一约束来防止重复条目
  • 数据来源: 超流体 信息 通过 Quicknode 访问端点

    • 提供账户数据、持仓、金库持仓、现货余额和授权信息
    • 以 JSON 格式返回数据,其中数值采用字符串形式表示,以保证精度
    • 通过带有钱包地址参数的 HTTP POST 请求访问
                    ┌─────────────────┐
│ 涉案交易员 │
└─────────┬───────┘
│ 1. 输入钱包地址

┌─────────────────┐
│ React 仪表盘 │◄─────────────────┐
└─────────┬───────┘ │
│ 2. 存储请求 │ 6. 读取并显示数据
▼ │
┌─────────────────┐ │
│ Supabase │◄─────────────────┤
│ PostgreSQL │ │
└─────────┬───────┘ │
│ 3. 检测请求 │ 5. 存储数据
▼ │
┌─────────────────┐ │
│ 索引器 │──────────────────┘
│ (500毫秒轮询) │
└─────────┬───────┘
│ 4. 获取HyperCore数据

┌─────────────────┐
│ Quicknode │
│ Hyperliquid │
│ 端点 │
└─────────────────┘

申请流程

第一次钱包搜索:


  1. 用户进入钱包→ 前端进行验证并将请求存储在数据库中
  2. 索引器检测到请求(每500毫秒轮询一次)→ 切换到新钱包
  3. 开始收集数据→ 独立从5个指定的端点获取数据,并将其存储到数据库中
  4. 前端每隔 1000 毫秒查询数据库→ UI 根据从索引器获取的最新数据进行更新

第二次钱包搜索及后续:


  1. 用户添加新钱包→ 前端调用相关函数清除任何先前钱包的数据,并指令索引器切换至新钱包
  2. 索引器检测到新请求→ 立即切换到新钱包,并使用新钱包地址开始获取数据
  3. 数据实时更新→ 仪表盘开始获取新钱包的持仓情况和指标

项目结构

该项目采用简洁的模块化架构,将数据收集、UI 组件和业务逻辑分层分离。这种结构使代码库易于维护,并便于扩展功能。

目录分类

├── src/
│ ├── indexer/
│ │ ├── indexer.ts # 主索引器协调与钱包管理
│ │ └── apicalls.ts # Hyperliquid 信息端点查询
│ ├── components/
│ │ ├── ui/ # shadcn/ui 组件(Button、Input、Card 等)
│ │ └── dashboard/ # 仪表盘组件(WalletHeader、PortfolioMetrics 等)
│ ├── shared/
│ │ ├── types.ts # TypeScript 接口与类型
│ │ ├── utils.ts # 格式化、计算及辅助函数
│ │ ├── constants.ts # 仪表盘的 UI 常量
│ │ └── supabase.ts # 用于前端访问的 Supabase 客户端实例
│ ├── Dashboard.tsx # 仪表盘核心逻辑
│ └── main.tsx
├── supabase/
│ └── schema.sql # 完整的数据库模式
├── package.json
└── .env

项目执行

步骤 1:克隆代码库

首先,克隆项目仓库,然后进入项目目录:

git clone https://github.com/quiknode-labs/qn-guide-examples.git
cd qn-guide-examples/sample-dapps/hyperliquid-portfolio-tracker

步骤 2:配置环境文件

创建您的 .env 通过运行以下命令来处理该文件:

cp .env.example .env

第 3 步:Supabase 数据库设置

Supabase 网站上创建一个新的 Supabase 账户,或登录您的现有 Supabase 账户。

创建一个新项目,然后点击“连接”按钮。

DB Connect


在“应用框架”部分,选择 React 并更改 使用 字段到 Vite。复制 VITE_SUPABASE_URL 以及 VITE_SUPABASE_ANON_KEY 值,并将它们添加到你的 .env 文件。

DB selectReact

最后,转到右上角的 SQL 编辑器,将 schema.sql 文件,然后点击“运行”。这将创建所有必要的表和函数,以便为前端存储和检索数据。

SQL 编辑器

第 4 步:Quicknode 设置

注册免费试用 Quicknode 账户, 然后创建您的第一个 Hyperliquid RPC 端点,并将其粘贴到您的 .env 文件。

信息

请务必删除现有的 /evm 并添加 /info 在您的 Quicknode 端点 URL 末尾添加此内容,即可访问 Hyperliquid 信息端点。

第 5 步:启动应用程序

创建完表并配置好环境后,您可以在根目录下运行以下命令来启动项目:

npm install && npm run dev:both

这将同时运行前端应用程序和索引器。

当索引器开始运行时,它会等待您搜索一个有效的钱包地址:

索引器正在启动

在 localhost 网址上打开您的前端页面,然后点击“演示钱包”按钮,即可获取一个示例钱包地址:

翻找钱包

在搜索到钱包后,索引器将开始每隔 500 毫秒为该地址获取数据:

索引器正在运行

仪表盘将显示该钱包的实时统计数据:

仪表盘页面

您现在可以实时查看账户价值、持仓情况及其他交易数据。

故障排除

在使用索引器时,您可能会在配置或运行期间遇到以下问题:


  • 索引器停止响应
  • 搜索钱包后未显示任何数据
  • 意外地停止了索引器

解决方案:

运行以下命令重启索引器:

npm run dev:indexer

请尝试输入一个有效的钱包地址,然后重新搜索。

深入解析代码库

既然投资组合追踪器已经安装并运行起来,让我们来探讨一下它背后的运作原理。本节将深入探讨使实时投资组合追踪成为可能的三个核心组件:

索引器 - 支持 Hyperliquid 的 信息 通过端点 apicalls.ts,检测钱包切换请求,并每500毫秒协调从5个不同的Hyperliquid端点收集数据。

模式与数据库——演示了用于以金融级精度存储交易数据的 PostgreSQL 表结构,包括前端与索引器之间通信的协调机制。

仪表盘(前端)——涵盖 React 状态管理、实时数据库轮询、钱包地址验证,以及用于显示实时交易数据的用户界面。

索引器

索引器作为一个独立的 Node.js 进程运行,每 500 毫秒轮询一次 Quicknode 端点。它会获取当前钱包地址的交易数据,并将其存储在数据库中。索引器等待来自前端的钱包切换请求,并通过锁文件实现进程隔离。

钱包余额变化检测

索引器轮询 钱包切换请求 每500毫秒检查一次表,以检测前端钱包的切换,并利用状态字段来防止切换过程中出现竞争条件。

超流体 信息 端点集成

索引器使用 HyperliquidAPI 类在 apicalls.ts 用于与 Quicknode 的 Hyperliquid 端点进行通信。每个 API 方法都遵循一致的模式来获取不同类型的交易数据:

// From apicalls.ts - Main account data fetching
async getClearinghouseState(walletAddress: string): Promise<ClearinghouseStateResponse> {
const payload = {
type: 'clearinghouseState',
user: walletAddress
};

const response = await fetch(QUICKNODE_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});

return await response.json();
}

getUserVaultEquities() 该方法遵循相同的结构,但 type: 'userVaultEquities',演示了每个端点只需提供钱包地址和端点类型即可。所有方法都包含全面的错误处理和日志记录功能,以便调试交易数据问题。

深入解析索引器

主索引循环调用了 5 个不同的 信息 端点,并将结果存储在不同的数据库表中:

// From indexer.ts - Core data fetching and storage
const data = await hyperliquidAPI.getClearinghouseState(CURRENT_WALLET_ADDRESS);
const stateId = await this.storeClearinghouseState(data);

// Store positions with atomic replacement to prevent UI flickering
await this.storeAssetPositions(data.assetPositions, data.time);

// Fetch additional data types with error handling
try {
const rateLimitData = await hyperliquidAPI.getUserRateLimit(CURRENT_WALLET_ADDRESS);
await this.storeUserRateLimit(rateLimitData, timestamp);
} catch (error) {
console.log(`No rate limit data available for ${CURRENT_WALLET_ADDRESS}`);
}

索引器能够优雅地处理单个端点的故障——如果一个数据源发生故障,其余数据源仍会正常运行。

索引器使用基于文件的锁来防止多个实例同时运行:

// From indexer.ts - Lock file creation and process checking
function createLock(): boolean {
if (fs.existsSync(LOCK_FILE)) {
const { pid } = JSON.parse(fs.readFileSync(LOCK_FILE, 'utf8'));
try {
process.kill(pid, 0);
console.error(`❌ Another indexer is already running (PID: ${pid})`);
return false;
} catch (e) {
fs.unlinkSync(LOCK_FILE); // Remove stale lock
}
}

fs.writeFileSync(LOCK_FILE, JSON.stringify({ pid: process.pid }));
return true;
}

索引器使用锁定文件来防止多个实例同时运行,从而确保数据的一致性。

模式与数据库(Supabase)

既然我们已经了解了索引器是如何收集数据的,接下来让我们看看这些数据是如何存储和组织结构的。PostgreSQL 数据库将交易数据存储在 6 张表中,并负责处理前端与索引器之间的通信。每张表都使用 DECIMAL 用于确保财务数据精确性的数据类型,以及防止重复输入的独特约束条件。

表结构示例

资产头寸表——以金融级精度存储永续合约头寸:

-- From schema.sql - Core trading data storage
CREATE TABLE asset_positions (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
wallet_address TEXT NOT NULL,

coin TEXT NOT NULL, -- Asset symbol (e.g., 'BTC', 'ETH', 'SOL')
size DECIMAL(20, 5) NOT NULL, -- Position size: 20 digits total, 5 after decimal
leverage_type TEXT NOT NULL, -- 'cross' or 'isolated' margin mode
leverage_value INTEGER NOT NULL, -- Leverage multiplier (1x, 5x, 10x, etc.)

entry_price DECIMAL(20, 5), -- Average entry price with 5 decimal precision
position_value DECIMAL(20, 5), -- Current USD value of the position
unrealized_pnl DECIMAL(20, 5), -- Profit/loss before closing position
liquidation_price DECIMAL(20, 5), -- Price at which position gets liquidated
margin_used DECIMAL(20, 5), -- Amount of margin allocated to this position

timestamp BIGINT NOT NULL, -- Unix timestamp in milliseconds from HyperCore
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() -- Database insertion time
);

-- Unique constraint prevents duplicate positions per wallet-coin pair
ALTER TABLE asset_positions ADD CONSTRAINT unique_position_per_wallet
UNIQUE (wallet_address, coin);

DECIMAL(20, 5) 该数据类型共提供 20 位数字,小数点后有 5 位。

钱包切换请求表——协调前端与索引器之间的通信:

-- 摘自 schema.sql - 前端与索引器的协调
CREATE TABLE wallet_switch_requests (
id UUID 默认值 gen_random_uuid() 主键 KEY,
requested_wallet_address TEXT NOT NULL, -- 以太坊地址(0x 格式)
status TEXT NOT NULL 默认 'pending', -- 状态机:待处理 → 处理中 → 已完成
创建时间 TIMESTAMP WITH TIME ZONE 默认 当前时间()
);

-- 由索引器创建的用于高效基于状态查询的索引
CREATE INDEX idx_wallet_switch_requests_status ON wallet_switch_requests(status);

该表充当 React 前端与 Node.js 索引器之间的消息队列。当用户输入新的钱包地址时,前端会插入一条 “待处理” 请求。索引器会轮询 “待处理” 每 500 毫秒发送一次请求,将状态更新为 “处理中” 为避免竞争条件,随后执行钱包切换。这种状态进展机制确保每次仅发生一次钱包切换,即使用户快速搜索不同的地址也是如此。

仪表盘(前端)

索引器负责收集数据,数据库负责存储数据,最后一个环节就是用户界面。React 前端每隔 1000 毫秒向数据库轮询一次以获取最新的交易数据,并通过模块化组件将其显示出来。它通过向数据库插入请求并立即清除本地状态来管理钱包切换。

概述

仪表盘架构以集中式状态管理为核心,并采用模块化组件组合方式:

// From Dashboard.tsx - Centralized state management
const [latestState, setLatestState] = useState<ClearinghouseState | null>(null);
const [positions, setPositions] = useState<AssetPosition[]>([]);
const [vaultEquities, setVaultEquities] = useState<UserVaultEquity[]>([]);
const [spotBalances, setSpotBalances] = useState<SpotBalance[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [hasInitialData, setHasInitialData] = useState(false);

仪表盘 该组件将所有交易数据保存在 React 状态中,并作为 props 传递给子组件。这种模式使状态管理变得简单,并且在调试时便于追踪数据流。

自动刷新轮询

前端每隔 1000 毫秒使用一个 setInterval 带有正确清理操作的循环:

// From Dashboard.tsx - Auto-refresh with cleanup
useEffect(() => {
if (currentWallet && hasStarted && hasInitialData) {
const interval = setInterval(async () => {
await fetchData(currentWallet);
}, 1000);
return () => clearInterval(interval);
}
}, [currentWallet, hasStarted, hasInitialData, fetchData]);

// Data freshness detection provides visual feedback
const isDataStale = latestState && (Date.now() - latestState.timestamp > 3000);

轮询会根据用户操作自动启动和停止,仅在需要时运行,以节省资源。界面会显示数据过期(超过3秒)的情况,以便用户了解数据的新鲜度。

数据库查询模式

前端使用经过优化的查询模式,从 Supabase 实时获取交易数据:

// From Dashboard.tsx - Real-time data queries
const { data: latestData, error: latestError } = await supabase
.from('clearinghouse_states')
.select('*')
.eq('wallet_address', walletAddress)
.order('timestamp', { ascending: false })
.limit(1)
.maybeSingle();

// Get all positions for this wallet
const { data: positionsData, error: positionsError } = await supabase
.from('asset_positions')
.select('*')
.eq('wallet_address', walletAddress)
.order('timestamp', { ascending: false });

if (positionsError && positionsError.code !== 'PGRST116') throw positionsError;

这些查询能妥善处理钱包无交易数据的情况,显示空结果而非报错。

管理钱包切换

前端会在切换时立即验证钱包地址并清空状态:

// From Dashboard.tsx - Wallet validation and switching
const isValidWalletAddress = (address: string): boolean => {
return /^0x[a-fA-F0-9]{40}$/.test(address);
};

const handleWalletSearch = async () => {
if (!isValidWalletAddress(address)) {
setError('Invalid wallet address format');
return;
}

// Clear ALL old data immediately when switching wallets
setLatestState(null);
setPositions([]);
setVaultEquities([]);
setSpotBalances([]);
setIsSearching(true);

// Signal indexer to switch
await switchIndexerWallet(address);
setCurrentWallet(address);
};

该界面会验证钱包地址,并在切换时立即清除旧数据,并在新数据加载完成前显示加载状态。

数据显示

交易数据从 React 状态流向专门的 UI 组件,这些组件负责对信息进行格式化并呈现:

// From PortfolioMetrics.tsx - Portfolio overview card component
interface PortfolioMetricsProps {
totalAccountValue: number;
totalUnrealizedPnl: number;
userRateLimit: UserRateLimit | null;
vaultEquities: UserVaultEquity[];
delegations: Delegation[];
formatCurrency: (value: number) => string;
}

export const PortfolioMetrics: React.FC<PortfolioMetricsProps> = ({
totalAccountValue,
totalUnrealizedPnl,
formatCurrency
}) => {
return (
<Card className="bg-slate-900/50 border-slate-700/50 backdrop-blur-sm mb-6">
<CardContent className="p-4">
<div className="text-xs text-slate-400 mb-3 font-medium tracking-wide uppercase">
Perp Account Value
</div>
<div className="text-2xl font-bold text-white">
{formatCurrency(totalAccountValue)}
</div>
</CardContent>
</Card>
);
};

仪表盘 该组件将计算出的值和格式化函数传递给子组件,由子组件负责处理交易数据的视觉呈现和样式设置。

结论

恭喜!您已成功利用 Quicknode 的 Hyperliquid 信息接口构建了一个实时 Hyperliquid 投资组合追踪器。您已经学会了如何获取永续合约交易数据、为实现财务精度而构建 PostgreSQL 数据库,以及构建一个能够实时更新的响应式仪表盘。这一基础为开发高级交易工具(如自动化风险监控和多钱包对比)开辟了可能性。

您可以通过集成Recharts等图表库,或者利用您喜欢的通知服务构建交易提醒功能,进一步扩展该项目。请查阅我们的其他Hyperliquid 指南,探索更多基于 Hyperliquid 进行开发的途径。

下一步

既然你已经拥有了一个可以正常运行的投资组合追踪器,以下是几种扩展和改进该应用程序的方法:

  • 当持仓接近危险保证金水平时触发的平仓警告
  • 通过图表跟踪投资组合随时间推移的表现
  • 钱包搜索之间的加载时间更短

更多资源


如果您遇到困难或有任何疑问,请在我们的Discord 频道中提出。关注我们的Twitter账号(@Quicknode)或Telegram 公告频道,随时掌握最新动态。

我们 ❤️ 您的反馈!

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

分享本指南