索引验证器数据
验证器数据存储在链上,可通过标准 EVM JSON-RPC 读取。您可以通过质押、惩罚和治理预编译查询当前状态,并从其事件日志中重建历史。这意味着索引器或分析平台可以通过 eth_call 和 eth_getLogs 读取所需的一切,而无需访问节点的 stabled CLI 或 Cosmos REST。
每个数据点的来源
| 数据点 | 来源 | 如何读取 |
|---|---|---|
| 验证器名称、身份、网站 | 质押预编译 validators() | description.moniker 和相关字段 |
| 质押(绑定代币) | 质押预编译 validators() | tokens 字段 |
| 佣金 | 质押预编译 validators() | commission 字段 |
| 质押随时间变化 | 质押预编译事件 | Delegate, Unbond, Redelegate 日志 |
| 加入日期 | 质押预编译事件 | CreateValidator 日志 → 块时间戳 |
| 正常运行时间 | 惩罚预编译 getSigningInfos() | (signedBlocksWindow − missedBlocksCounter) / signedBlocksWindow |
| 投票历史(聚合) | 治理预编译 getTallyResult() | 每一项提案的计票结果 |
| 投票历史(按验证器) | 治理预编译事件 | Vote, VoteWeighted 日志,投票者 = 运营者地址 |
预编译地址
| 模块 | 地址 | 用途 |
|---|---|---|
| 质押 | 0x0000000000000000000000000000000000000800 | 验证器集合、质押、佣金、委托事件 |
| 分发 | 0x0000000000000000000000000000000000000801 | 奖励和佣金提款 |
| 治理 | 0x0000000000000000000000000000000000000805 | 提案、计票和投票日志 |
| 惩罚 | 0x0000000000000000000000000000000000000806 | 签名信息和正常运行时间 |
连接到主网(链 ID 988),地址为 https://rpc.stable.xyz。有关端点和限制,请参阅主网信息。
验证器名称、质押和佣金
调用质押预编译上的 validators() 来读取当前验证器集。传入绑定状态进行过滤(例如 BOND_STATUS_BONDED)。每个条目都显示验证器的 description(包括 moniker)、tokens(绑定的质押)和 commission。
// validators.ts
import { createPublicClient, http } from "viem";
const STAKING_PRECOMPILE = "0x0000000000000000000000000000000000000800";
const client = createPublicClient({
transport: http("https://rpc.stable.xyz"),
});
// See the staking precompile reference for the full validators() ABI and structs.
const validators = await client.readContract({
address: STAKING_PRECOMPILE,
abi: stakingAbi,
functionName: "validators",
args: ["BOND_STATUS_BONDED", { key: "0x", offset: 0n, limit: 100n, countTotal: true, reverse: false }],
});
for (const v of validators[0]) {
console.log(v.description.moniker, v.tokens.toString(), v.commission.toString());
}StableNode-01 4500000000000000000000000 50000000000000000
StableNode-02 3900000000000000000000000 100000000000000000tokens 和 commission 的值按 18 位小数进行缩放。将 commission 除以 1e18 可获得费率分数(例如 0.05 为 5%)。有关完整的 Validator 结构和 BOND_STATUS_* 值,请参阅质押预编译参考。
质押随时间变化
validators() 返回一个快照。要跟踪质押的变化,请索引质押预编译的委托事件。Delegate、Unbond 和 Redelegate 带有索引的 validatorAddr 和 amount,因此您可以将每个质押变化归因于验证器和区块。
// stakeChanges.ts
import { parseAbiItem } from "viem";
const logs = await client.getLogs({
address: STAKING_PRECOMPILE,
event: parseAbiItem(
"event Delegate(address indexed delegatorAddr, string indexed validatorAddr, uint256 amount, uint256 newShares)"
),
fromBlock: 0n,
toBlock: "latest",
});
console.log(`${logs.length} delegations indexed`);1842 delegations indexedUnbond 和 Redelegate 遵循相同的结构,并且额外带有 completionTime。有关确切的签名,请参阅质押参考的事件部分。
加入日期
验证器的加入日期是其 CreateValidator 事件的区块时间戳。该事件按验证器地址索引,因此您可以筛选单个验证器或扫描完整集合,然后使用 eth_getBlockByNumber 将每个日志的 blockNumber 解析为时间戳。
// joinDate.ts
import { parseAbiItem } from "viem";
const logs = await client.getLogs({
address: STAKING_PRECOMPILE,
event: parseAbiItem("event CreateValidator(address indexed valiAddr, uint256 value)"),
fromBlock: 0n,
toBlock: "latest",
});
for (const log of logs) {
const block = await client.getBlock({ blockNumber: log.blockNumber });
console.log(log.args.valiAddr, new Date(Number(block.timestamp) * 1000).toISOString());
}0xAbc...123 2025-11-04T09:12:44.000Z
0xDef...456 2025-12-18T17:03:01.000Z正常运行时间
使用 getSigningInfos() 从惩罚预编译 (0x...806) 读取签名信息。每条记录报告 signedBlocksWindow(滑动窗口的大小)和 missedBlocksCounter(窗口内错过的区块)。计算正常运行时间为:
正常运行时间 = (signedBlocksWindow − missedBlocksCounter) / signedBlocksWindow一个 signedBlocksWindow 为 10000 且 missedBlocksCounter 为 25 的验证器在窗口内的正常运行时间为 99.75%。这是一个滚动数字,而不是生命周期正常运行时间。要跟踪正常运行时间历史记录,请按固定间隔对计数器进行快照并存储每次读取。
投票历史
治理数据分为两层。对于提案的聚合结果,在治理预编译 (0x...805) 上调用 getTallyResult()。对于谁投了什么票,索引 Vote 和 VoteWeighted 事件日志。这些日志中的投票者地址是验证器的运营者地址,因此您可以直接将投票与验证器关联起来。
// votes.ts
import { parseAbiItem } from "viem";
const GOV_PRECOMPILE = "0x0000000000000000000000000000000000000805";
const logs = await client.getLogs({
address: GOV_PRECOMPILE,
event: parseAbiItem(
"event Vote(uint64 indexed proposalId, address indexed voter, uint8 option, uint256 weight)"
),
fromBlock: 0n,
toBlock: "latest",
});
console.log(`${logs.length} votes indexed across all proposals`);38 votes indexed across all proposals实时投票日志确认了迄今为止的所有提案(提案 #1 到 #7)。当您只需要每项提案的最终计票结果时,请使用 getTallyResult(),当您需要每个验证器的记录时,请使用事件日志。

