参数
响应
包含以下内容的对象:
当前投票账户对象数组,包含:
投票账户的公钥(base-58 编码)
验证者的公钥(base-58 编码)
已激活质押金额(以 lamports 为单位)
账户是否在当前 epoch 中投票
最后一次投票的 slot
根的 slot
失效投票账户对象数组,结构与当前相同
代码示例
基本请求
复制
curl https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getVoteAccounts"
}'
使用 web3.js
复制
import { Connection } from '@solana/web3.js';
const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY');
// Get vote accounts
const voteAccounts = await connection.getVoteAccounts();
console.log('Vote accounts:', voteAccounts);
// Get vote accounts with analysis
async function getVoteAccountsWithAnalysis(
config: {
commitment?: string;
votePubkey?: string;
keepUnstakedDelinquents?: boolean;
delinquentSlotDistance?: number;
}
) {
const voteAccounts = await connection.getVoteAccounts(config);
return {
voteAccounts,
analysis: {
current: {
count: voteAccounts.current.length,
totalStake: voteAccounts.current.reduce((sum, acc) => sum + acc.activatedStake, 0),
averageStake: voteAccounts.current.reduce((sum, acc) => sum + acc.activatedStake, 0) / voteAccounts.current.length,
activeVoters: voteAccounts.current.filter(acc => acc.epochVoteAccount).length
},
delinquent: {
count: voteAccounts.delinquent.length,
totalStake: voteAccounts.delinquent.reduce((sum, acc) => sum + acc.activatedStake, 0),
averageStake: voteAccounts.delinquent.reduce((sum, acc) => sum + acc.activatedStake, 0) / voteAccounts.delinquent.length,
activeVoters: voteAccounts.delinquent.filter(acc => acc.epochVoteAccount).length
},
metadata: {
timestamp: Date.now(),
commitment: config.commitment
}
}
};
}
注意事项
- 返回当前的投票账户
- 响应包括当前和失效的投票账户
- 响应是即时的,因为它从当前状态读取
- 账户可能随质押变化和投票而变化
- 投票账户必须有效
最佳实践
- 根据需求使用适当的 commitment 级别
- 在适当时缓存结果以减少 RPC 负载
- 监控投票账户的变化
- 考虑使用 WebSocket 订阅获取实时更新
- 适当处理网络错误并重试
常见错误
| 错误码 | 消息 | 解决方案 |
|---|---|---|
| -32601 | Method not found | 验证是否连接到 Solana RPC 节点 |
| -32602 | Invalid params | 检查配置参数 |
| -32007 | Vote account not found | 验证投票账户是否存在 |
用例
-
投票账户分析
复制
interface VoteAccountAnalysis { accounts: { current: Array<{ votePubkey: string; nodePubkey: string; activatedStake: number; epochVoteAccount: boolean; lastVote: number; rootSlot?: number; }>; delinquent: Array<{ votePubkey: string; nodePubkey: string; activatedStake: number; epochVoteAccount: boolean; lastVote: number; rootSlot?: number; }>; }; metrics: { current: { count: number; totalStake: number; averageStake: number; activeVoters: number; participationRate: number; }; delinquent: { count: number; totalStake: number; averageStake: number; activeVoters: number; participationRate: number; }; }; metadata: { timestamp: number; commitment?: string; }; } class VoteAccountAnalyzer { async analyzeVoteAccounts( config: { commitment?: string; votePubkey?: string; keepUnstakedDelinquents?: boolean; delinquentSlotDistance?: number; } ): Promise<VoteAccountAnalysis> { const voteAccounts = await connection.getVoteAccounts(config); return { accounts: { current: voteAccounts.current.map(acc => ({ votePubkey: acc.votePubkey, nodePubkey: acc.nodePubkey, activatedStake: acc.activatedStake, epochVoteAccount: acc.epochVoteAccount, lastVote: acc.lastVote, rootSlot: acc.rootSlot })), delinquent: voteAccounts.delinquent.map(acc => ({ votePubkey: acc.votePubkey, nodePubkey: acc.nodePubkey, activatedStake: acc.activatedStake, epochVoteAccount: acc.epochVoteAccount, lastVote: acc.lastVote, rootSlot: acc.rootSlot })) }, metrics: { current: { count: voteAccounts.current.length, totalStake: voteAccounts.current.reduce((sum, acc) => sum + acc.activatedStake, 0), averageStake: voteAccounts.current.reduce((sum, acc) => sum + acc.activatedStake, 0) / voteAccounts.current.length, activeVoters: voteAccounts.current.filter(acc => acc.epochVoteAccount).length, participationRate: voteAccounts.current.filter(acc => acc.epochVoteAccount).length / voteAccounts.current.length }, delinquent: { count: voteAccounts.delinquent.length, totalStake: voteAccounts.delinquent.reduce((sum, acc) => sum + acc.activatedStake, 0), averageStake: voteAccounts.delinquent.reduce((sum, acc) => sum + acc.activatedStake, 0) / voteAccounts.delinquent.length, activeVoters: voteAccounts.delinquent.filter(acc => acc.epochVoteAccount).length, participationRate: voteAccounts.delinquent.filter(acc => acc.epochVoteAccount).length / voteAccounts.delinquent.length } }, metadata: { timestamp: Date.now(), commitment: config.commitment } }; } } -
投票账户监控
复制
interface VoteAccountChange { changes: { current: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }>; delinquent: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }>; }; metadata: { timestamp: number; }; } class VoteAccountMonitor { private previousAccounts: { current: Map<string, { stake: number; vote: boolean; }>; delinquent: Map<string, { stake: number; vote: boolean; }>; } = { current: new Map(), delinquent: new Map() }; async monitorVoteAccounts( config: { commitment?: string; votePubkey?: string; keepUnstakedDelinquents?: boolean; delinquentSlotDistance?: number; } ): Promise<VoteAccountChange | null> { const voteAccounts = await connection.getVoteAccounts(config); const changes: { current: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }>; delinquent: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }>; } = { current: [], delinquent: [] }; this.checkAccountChanges( voteAccounts.current, this.previousAccounts.current, changes.current ); this.checkAccountChanges( voteAccounts.delinquent, this.previousAccounts.delinquent, changes.delinquent ); this.previousAccounts.current = new Map( voteAccounts.current.map(acc => [ acc.votePubkey, { stake: acc.activatedStake, vote: acc.epochVoteAccount } ]) ); this.previousAccounts.delinquent = new Map( voteAccounts.delinquent.map(acc => [ acc.votePubkey, { stake: acc.activatedStake, vote: acc.epochVoteAccount } ]) ); if (changes.current.length > 0 || changes.delinquent.length > 0) { return { changes, metadata: { timestamp: Date.now() } }; } return null; } private checkAccountChanges( currentAccounts: Array<{ votePubkey: string; activatedStake: number; epochVoteAccount: boolean; }>, previousAccounts: Map<string, { stake: number; vote: boolean; }>, changes: Array<{ type: 'added' | 'removed' | 'modified'; votePubkey: string; previousStake?: number; currentStake?: number; previousVote?: boolean; currentVote?: boolean; }> ) { previousAccounts.forEach((data, votePubkey) => { const current = currentAccounts.find(acc => acc.votePubkey === votePubkey); if (!current) { changes.push({ type: 'removed', votePubkey, previousStake: data.stake, previousVote: data.vote }); } else if ( current.activatedStake !== data.stake || current.epochVoteAccount !== data.vote ) { changes.push({ type: 'modified', votePubkey, previousStake: data.stake, currentStake: current.activatedStake, previousVote: data.vote, currentVote: current.epochVoteAccount }); } }); currentAccounts.forEach(account => { if (!previousAccounts.has(account.votePubkey)) { changes.push({ type: 'added', votePubkey: account.votePubkey, currentStake: account.activatedStake, currentVote: account.epochVoteAccount }); } }); } } -
投票账户规划
复制
interface VoteAccountPlan { accounts: { current: Array<{ votePubkey: string; nodePubkey: string; activatedStake: number; epochVoteAccount: boolean; }>; delinquent: Array<{ votePubkey: string; nodePubkey: string; activatedStake: number; epochVoteAccount: boolean; }>; }; recommendations: Array<{ type: 'stake' | 'unstake' | 'monitor'; votePubkey: string; reason: string; }>; metadata: { timestamp: number; }; } class VoteAccountPlanner { private readonly minStake = 1000000000; // 1 SOL private readonly maxDelinquentVotes = 3; async planVoteAccounts( config: { commitment?: string; votePubkey?: string; keepUnstakedDelinquents?: boolean; delinquentSlotDistance?: number; } ): Promise<VoteAccountPlan> { const voteAccounts = await connection.getVoteAccounts(config); const recommendations: Array<{ type: 'stake' | 'unstake' | 'monitor'; votePubkey: string; reason: string; }> = []; voteAccounts.current.forEach(account => { if (account.activatedStake < this.minStake) { recommendations.push({ type: 'stake', votePubkey: account.votePubkey, reason: `Account has low stake (${account.activatedStake})` }); } if (!account.epochVoteAccount) { recommendations.push({ type: 'monitor', votePubkey: account.votePubkey, reason: 'Account not voting in current epoch' }); } }); voteAccounts.delinquent.forEach(account => { if (account.activatedStake > 0) { recommendations.push({ type: 'unstake', votePubkey: account.votePubkey, reason: 'Account is delinquent with stake' }); } }); return { accounts: { current: voteAccounts.current.map(acc => ({ votePubkey: acc.votePubkey, nodePubkey: acc.nodePubkey, activatedStake: acc.activatedStake, epochVoteAccount: acc.epochVoteAccount })), delinquent: voteAccounts.delinquent.map(acc => ({ votePubkey: acc.votePubkey, nodePubkey: acc.nodePubkey, activatedStake: acc.activatedStake, epochVoteAccount: acc.epochVoteAccount })) }, recommendations, metadata: { timestamp: Date.now() } }; } }