参数
所有者的公钥(base-58 编码)
响应
包含以下内容的对象:
代币账户对象数组,包含:
代币账户的公钥(base-58 编码)
账户中的 lamports 数量
账户所有者的公钥(base-58 编码)
账户是否可执行
账户下次需要支付租金的 epoch
拥有该账户的程序(spl-token)
分配给账户的字节数
代码示例
基本请求
复制
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
}
}
};
}
注意事项
- 返回给定公钥拥有的所有代币账户
- 所有者完全控制这些账户
- 响应是即时的,因为它从当前状态读取
- 账户可能随代币转账和其他操作而变化
- 所有者必须是有效的公钥
最佳实践
- 根据需求使用适当的 commitment 级别
- 在适当时缓存结果以减少 RPC 负载
- 监控所属账户的变化
- 考虑使用 WebSocket 订阅获取实时更新
- 适当处理网络错误并重试
常见错误
| 错误码 | 消息 | 解决方案 |
|---|---|---|
| -32601 | Method not found | 验证是否连接到 Solana RPC 节点 |
| -32602 | Invalid params | 检查所有者公钥和配置 |
| -32007 | Owner not found | 验证所有者是否存在 |
| -32008 | Invalid program ID | 验证程序 ID 是否正确 |
用例
-
所有者账户分析
复制
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 } }; } } -
所有者账户监控
复制
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; } } -
所有者账户规划
复制
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() } }; } }