跳转到主要内容

参数

config
object
包含以下内容的配置对象:
commitment
string
Commitment 级别(processed、confirmed、finalized)
votePubkey
string
要查询的特定投票账户公钥(base-58 编码)
keepUnstakedDelinquents
boolean
是否包含未质押的失效投票账户
delinquentSlotDistance
number
将投票账户视为失效的最大 slot 距离

响应

result
object
包含以下内容的对象:
current
array
当前投票账户对象数组,包含:
votePubkey
string
投票账户的公钥(base-58 编码)
nodePubkey
string
验证者的公钥(base-58 编码)
activatedStake
number
已激活质押金额(以 lamports 为单位)
epochVoteAccount
boolean
账户是否在当前 epoch 中投票
epochCredits
array
epoch 信用对象数组,包含:
epoch
number
Epoch 编号
credits
number
获得的信用数
previousCredits
number
上一个 epoch 获得的信用数
lastVote
number
最后一次投票的 slot
rootSlot
number
根的 slot
delinquent
array
失效投票账户对象数组,结构与当前相同

代码示例

基本请求

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
      }
    }
  };
}

注意事项

  1. 返回当前的投票账户
  2. 响应包括当前和失效的投票账户
  3. 响应是即时的,因为它从当前状态读取
  4. 账户可能随质押变化和投票而变化
  5. 投票账户必须有效

最佳实践

  1. 根据需求使用适当的 commitment 级别
  2. 在适当时缓存结果以减少 RPC 负载
  3. 监控投票账户的变化
  4. 考虑使用 WebSocket 订阅获取实时更新
  5. 适当处理网络错误并重试

常见错误

错误码消息解决方案
-32601Method not found验证是否连接到 Solana RPC 节点
-32602Invalid params检查配置参数
-32007Vote account not found验证投票账户是否存在

用例

  1. 投票账户分析
    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
          }
        };
      }
    }
    
  2. 投票账户监控
    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
            });
          }
        });
      }
    }
    
  3. 投票账户规划
    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()
          }
        };
      }
    }