name: midnight-indexer:indexer-service description: 当需要查询Midnight索引器获取区块链数据、获取账户余额、列出交易记录、读取合约状态或构建数据驱动的DApp后端时使用。
索引器服务
连接并查询Midnight网络索引器,获取包括余额、交易和合约状态在内的区块链数据。
使用场景
- 获取账户余额和UTXO数据
- 列出地址的交易历史
- 从区块链读取合约状态
- 查询隐私与非隐私交易数据
- 构建数据驱动的DApp后端
- 为大型结果集实现分页
核心概念
索引器架构
Midnight索引器提供GraphQL API用于查询区块链数据。它索引所有链上活动并通过结构化查询暴露数据。
| 组件 | 用途 |
|---|---|
| GraphQL API | 区块链数据查询接口 |
| WebSocket | 实时订阅(参见event-subscriptions技能) |
| REST健康检查 | 服务健康状态检查 |
API版本
索引器API经历了多个版本的演进。请确保您的查询与目标版本兼容。
| 版本 | 关键特性 | 破坏性变更 |
|---|---|---|
| 2.0.0 | 基础GraphQL架构 | 初始发布 |
| 2.1.0 | 增强过滤器、分页 | 查询参数变更 |
| 2.1.4 | 性能优化 | 从2.1.0无变更 |
UTXO模型
Midnight使用UTXO(未花费交易输出)模型而非账户模型。余额查询返回地址拥有的未花费输出集合。
隐私交易
Midnight支持透明和隐私交易。索引器提供不同的访问模式:
- 透明交易:完整交易细节可见
- 隐私交易:仅承诺哈希可见,细节加密
参考文档
| 文档 | 描述 |
|---|---|
| api-versions.md | 版本差异与迁移指南 |
| query-patterns.md | 常见查询模式与优化 |
示例
| 示例 | 描述 |
|---|---|
| balance-query/ | 查询账户余额和UTXO |
| transaction-list/ | 带分页的交易历史列表 |
| contract-state/ | 读取已部署合约状态 |
快速开始
1. 配置索引器客户端
import { createIndexerClient } from '@midnight-ntwrk/midnight-js-indexer';
const indexer = createIndexerClient({
uri: 'https://indexer.testnet.midnight.network/api/v1/graphql',
wsUri: 'wss://indexer.testnet.midnight.network/api/v1/graphql',
});
2. 查询余额
const balance = await indexer.query({
query: `
query GetBalance($address: String!) {
balance(address: $address) {
total
utxos {
txHash
outputIndex
amount
}
}
}
`,
variables: { address: 'addr_test1...' },
});
console.log('总余额:', balance.data.balance.total);
3. 列出交易
const transactions = await indexer.query({
query: `
query GetTransactions($address: String!, $limit: Int!) {
transactions(address: $address, first: $limit) {
edges {
node {
hash
blockNumber
timestamp
inputs { address amount }
outputs { address amount }
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
`,
variables: { address: 'addr_test1...', limit: 20 },
});
常见模式
基于环境的配置
interface IndexerConfig {
uri: string;
wsUri: string;
}
function getIndexerConfig(): IndexerConfig {
const network = process.env.MIDNIGHT_NETWORK || 'testnet';
const configs: Record<string, IndexerConfig> = {
testnet: {
uri: 'https://indexer.testnet.midnight.network/api/v1/graphql',
wsUri: 'wss://indexer.testnet.midnight.network/api/v1/graphql',
},
mainnet: {
uri: 'https://indexer.midnight.network/api/v1/graphql',
wsUri: 'wss://indexer.midnight.network/api/v1/graphql',
},
};
return configs[network] ?? configs.testnet;
}
使用游标分页
async function fetchAllTransactions(
indexer: IndexerClient,
address: string
): Promise<Transaction[]> {
const allTransactions: Transaction[] = [];
let cursor: string | null = null;
let hasMore = true;
while (hasMore) {
const result = await indexer.query({
query: TRANSACTIONS_QUERY,
variables: {
address,
first: 100,
after: cursor,
},
});
const { edges, pageInfo } = result.data.transactions;
allTransactions.push(...edges.map(e => e.node));
hasMore = pageInfo.hasNextPage;
cursor = pageInfo.endCursor;
}
return allTransactions;
}
健康检查
async function checkIndexerHealth(baseUrl: string): Promise<boolean> {
try {
const response = await fetch(`${baseUrl}/health`);
return response.ok;
} catch {
return false;
}
}
带退避的重试
async function queryWithRetry<T>(
indexer: IndexerClient,
query: string,
variables: Record<string, unknown>,
maxRetries = 3
): Promise<T> {
let lastError: Error | null = null;
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await indexer.query({ query, variables });
} catch (error) {
lastError = error as Error;
if (attempt < maxRetries) {
await new Promise(r => setTimeout(r, 1000 * attempt));
}
}
}
throw lastError;
}
相关技能
event-subscriptions- 实时WebSocket事件流midnight-tooling:contract-calling- 调用改变状态的合约midnight-dapp:state-management- 前端状态与索引器同步
相关命令
/midnight-tooling:check- 验证索引器连接性