跳转到主要内容

参数

owner
string
必填
所有者的公钥(base-58 编码)
config
object
必填
包含以下内容的配置对象:
programId
string
必填
代币程序的公钥(base-58 编码)
commitment
string
Commitment 级别(processed、confirmed、finalized)
encoding
string
账户数据的编码格式(base58、base64、jsonParsed)
dataSlice
object
offset
number
账户数据的偏移量
length
number
要返回的数据长度

响应

result
object
包含以下内容的对象:
context
object
slot
number
处理请求时的 slot
value
array
代币账户对象数组,包含:
pubkey
string
代币账户的公钥(base-58 编码)
account
object
lamports
number
账户中的 lamports 数量
owner
string
账户所有者的公钥(base-58 编码)
executable
boolean
账户是否可执行
rentEpoch
number
账户下次需要支付租金的 epoch
data
object
parsed
object
info
object
tokenAmount
object
amount
string
代币的原始数量(字符串格式)
decimals
number
代币铸造配置的小数位数
uiAmount
number
考虑小数位数后的代币数量(浮点数)
uiAmountString
string
考虑小数位数后的代币数量(字符串)
mint
string
代币铸造的公钥(base-58 编码)
owner
string
代币账户所有者的公钥(base-58 编码)
delegate
string
代理人的公钥(base-58 编码)
type
string
账户类型(account)
program
string
拥有该账户的程序(spl-token)
space
number
分配给账户的字节数

代码示例

基本请求

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": "getTokenAccountsByOwner",
  "params": [
    "4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj",
    {
      "programId": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
    }
  ]
}'

使用 web3.js

import { Connection, PublicKey } from '@solana/web3.js';

const connection = new Connection('https://fra.rpc.orbitflare.com?api_key=YOUR-API-KEY');

// Get token accounts by owner
const owner = new PublicKey('4vJ9JU1bJJE96FWSJKvHsmmFADCg4gpZQff4P3bkLZj');
const programId = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
const accounts = await connection.getTokenAccountsByOwner(owner, { programId });
console.log('Token accounts:', accounts);

// Get token accounts by owner with analysis
async function getTokenAccountsByOwnerWithAnalysis(
  owner: PublicKey,
  config: { programId: PublicKey; commitment?: string }
) {
  const accounts = await connection.getTokenAccountsByOwner(owner, config);
  
  return {
    accounts,
    analysis: {
      totalAccounts: accounts.value.length,
      totalBalance: accounts.value.reduce((sum, acc) => {
        const amount = BigInt(acc.account.data.parsed.info.tokenAmount.amount);
        return sum + amount;
      }, BigInt(0)),
      byMint: accounts.value.reduce((acc, account) => {
        const mint = account.account.data.parsed.info.mint;
        const amount = BigInt(account.account.data.parsed.info.tokenAmount.amount);
        acc[mint] = (acc[mint] || BigInt(0)) + amount;
        return acc;
      }, {} as Record<string, bigint>),
      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检查所有者公钥和配置
-32007Owner not found验证所有者是否存在
-32008Invalid program ID验证程序 ID 是否正确

用例

  1. 所有者账户分析
    interface OwnerAccountAnalysis {
      owner: string;
      accounts: Array<{
        pubkey: string;
        mint: string;
        balance: string;
        delegate?: string;
      }>;
      metrics: {
        totalAccounts: number;
        totalBalance: string;
        byMint: Record<string, string>;
        delegatedAccounts: number;
      };
      metadata: {
        timestamp: number;
        commitment?: string;
      };
    }
    
    class OwnerAccountAnalyzer {
      async analyzeOwnerAccounts(
        owner: PublicKey,
        config: { programId: PublicKey; commitment?: string }
      ): Promise<OwnerAccountAnalysis> {
        const accounts = await connection.getTokenAccountsByOwner(owner, config);
        
        const byMint: Record<string, bigint> = {};
        let totalBalance = BigInt(0);
        let delegatedAccounts = 0;
        
        accounts.value.forEach(account => {
          const mint = account.account.data.parsed.info.mint;
          const amount = BigInt(account.account.data.parsed.info.tokenAmount.amount);
          const delegate = account.account.data.parsed.info.delegate;
          
          byMint[mint] = (byMint[mint] || BigInt(0)) + amount;
          totalBalance += amount;
          if (delegate) {
            delegatedAccounts++;
          }
        });
        
        return {
          owner: owner.toBase58(),
          accounts: accounts.value.map(account => ({
            pubkey: account.pubkey,
            mint: account.account.data.parsed.info.mint,
            balance: account.account.data.parsed.info.tokenAmount.amount,
            delegate: account.account.data.parsed.info.delegate
          })),
          metrics: {
            totalAccounts: accounts.value.length,
            totalBalance: totalBalance.toString(),
            byMint: Object.fromEntries(
              Object.entries(byMint).map(([mint, amount]) => [mint, amount.toString()])
            ),
            delegatedAccounts
          },
          metadata: {
            timestamp: Date.now(),
            commitment: config.commitment
          }
        };
      }
    }
    
  2. 所有者账户监控
    interface OwnerAccountChange {
      owner: string;
      changes: Array<{
        type: 'added' | 'removed' | 'modified';
        account: string;
        previousBalance?: string;
        currentBalance?: string;
        previousDelegate?: string;
        currentDelegate?: string;
      }>;
      metadata: {
        timestamp: number;
      };
    }
    
    class OwnerAccountMonitor {
      private previousAccounts: Map<string, {
        balance: string;
        delegate?: string;
      }> = new Map();
      
      async monitorOwnerAccounts(
        owner: PublicKey,
        config: { programId: PublicKey; commitment?: string }
      ): Promise<OwnerAccountChange | null> {
        const accounts = await connection.getTokenAccountsByOwner(owner, config);
        const currentAccounts = new Map(
          accounts.value.map(account => [
            account.pubkey,
            {
              balance: account.account.data.parsed.info.tokenAmount.amount,
              delegate: account.account.data.parsed.info.delegate
            }
          ])
        );
        
        const changes: Array<{
          type: 'added' | 'removed' | 'modified';
          account: string;
          previousBalance?: string;
          currentBalance?: string;
          previousDelegate?: string;
          currentDelegate?: string;
        }> = [];
        
        this.previousAccounts.forEach((data, account) => {
          if (!currentAccounts.has(account)) {
            changes.push({
              type: 'removed',
              account,
              previousBalance: data.balance,
              previousDelegate: data.delegate
            });
          } else {
            const current = currentAccounts.get(account)!;
            if (current.balance !== data.balance || current.delegate !== data.delegate) {
              changes.push({
                type: 'modified',
                account,
                previousBalance: data.balance,
                currentBalance: current.balance,
                previousDelegate: data.delegate,
                currentDelegate: current.delegate
              });
            }
          }
        });
        
        currentAccounts.forEach((data, account) => {
          if (!this.previousAccounts.has(account)) {
            changes.push({
              type: 'added',
              account,
              currentBalance: data.balance,
              currentDelegate: data.delegate
            });
          }
        });
        
        this.previousAccounts = currentAccounts;
        
        if (changes.length > 0) {
          return {
            owner: owner.toBase58(),
            changes,
            metadata: {
              timestamp: Date.now()
            }
          };
        }
        
        return null;
      }
    }
    
  3. 所有者账户规划
    interface OwnerAccountPlan {
      owner: string;
      currentAccounts: Array<{
        pubkey: string;
        mint: string;
        balance: string;
        delegate?: string;
      }>;
      recommendations: Array<{
        type: 'consolidate' | 'diversify' | 'rebalance' | 'delegate';
        accounts: string[];
        reason: string;
      }>;
      metadata: {
        timestamp: number;
      };
    }
    
    class OwnerAccountPlanner {
      private readonly minBalancePerAccount = BigInt(1000000); // 1 token
      private readonly maxAccountsPerMint = 5;
      
      async planOwnerAccounts(
        owner: PublicKey,
        config: { programId: PublicKey; commitment?: string }
      ): Promise<OwnerAccountPlan> {
        const accounts = await connection.getTokenAccountsByOwner(owner, config);
        
        const byMint = accounts.value.reduce((acc, account) => {
          const mint = account.account.data.parsed.info.mint;
          if (!acc[mint]) {
            acc[mint] = [];
          }
          acc[mint].push(account);
          return acc;
        }, {} as Record<string, typeof accounts.value>);
        
        const recommendations: Array<{
          type: 'consolidate' | 'diversify' | 'rebalance' | 'delegate';
          accounts: string[];
          reason: string;
        }> = [];
        
        Object.entries(byMint).forEach(([mint, mintAccounts]) => {
          const lowBalanceAccounts = mintAccounts.filter(account => 
            BigInt(account.account.data.parsed.info.tokenAmount.amount) < this.minBalancePerAccount
          );
          
          if (lowBalanceAccounts.length > 0) {
            recommendations.push({
              type: 'consolidate',
              accounts: lowBalanceAccounts.map(account => account.pubkey),
              reason: `Accounts with balances below minimum threshold for mint ${mint}`
            });
          }
          
          if (mintAccounts.length > this.maxAccountsPerMint) {
            recommendations.push({
              type: 'consolidate',
              accounts: mintAccounts.map(account => account.pubkey),
              reason: `Too many accounts (${mintAccounts.length}) for mint ${mint}`
            });
          }
          
          const accountsWithoutDelegates = mintAccounts.filter(
            account => !account.account.data.parsed.info.delegate
          );
          
          if (accountsWithoutDelegates.length > 0) {
            recommendations.push({
              type: 'delegate',
              accounts: accountsWithoutDelegates.map(account => account.pubkey),
              reason: `Accounts without delegates for mint ${mint}`
            });
          }
        });
        
        return {
          owner: owner.toBase58(),
          currentAccounts: accounts.value.map(account => ({
            pubkey: account.pubkey,
            mint: account.account.data.parsed.info.mint,
            balance: account.account.data.parsed.info.tokenAmount.amount,
            delegate: account.account.data.parsed.info.delegate
          })),
          recommendations,
          metadata: {
            timestamp: Date.now()
          }
        };
      }
    }