Параметры
Открытый ключ владельца (в кодировке base-58)
Объект конфигурации, содержащий:Открытый ключ программы токенов (в кодировке base-58)
Уровень подтверждения (processed, confirmed, finalized)
Кодирование данных счёта (base58, base64, jsonParsed)
Длина возвращаемых данных
Ответ
Объект, содержащий:Слот, в котором был обработан запрос
Массив объектов токеновых счетов, содержащих:Открытый ключ токенового счёта (в кодировке base-58)
Количество lamports на счёте
Открытый ключ владельца счёта (в кодировке base-58)
Является ли счёт исполняемым
Эпоха, в которую счёт должен будет оплатить аренду следующий раз
Необработанное количество токенов в виде строки
Количество десятичных знаков, настроенных для монетного двора токена
Количество токенов в виде числа с плавающей точкой с учётом десятичных знаков
Количество токенов в виде строки с учётом десятичных знаков
Открытый ключ монетного двора токена (в кодировке base-58)
Открытый ключ владельца токенового счёта (в кодировке base-58)
Открытый ключ делегата (в кодировке base-58)
Программа, владеющая счётом (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
}
}
};
}
Примечания
- Возвращает все токеновые счета, принадлежащие указанному открытому ключу
- Владелец имеет полный контроль над этими счетами
- Ответ приходит немедленно, так как читается из текущего состояния
- Счета могут изменяться при переводах токенов и других операциях
- Владелец должен быть действительным открытым ключом
Рекомендации
- Используйте подходящий уровень подтверждения в зависимости от ваших потребностей
- Кэшируйте результаты там, где это уместно, для снижения нагрузки на 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;
}> = [];
// Check for removed or modified accounts
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
});
}
}
});
// Check for added accounts
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]) => {
// Check for accounts with low balances
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}`
});
}
// Check for too many accounts per 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}`
});
}
// Check for accounts without delegates
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()
}
};
}
}