Перейти к основному содержанию

Параметры

owner
string
обязательно
Открытый ключ владельца (в кодировке base-58)
config
object
обязательно
Объект конфигурации, содержащий:
programId
string
обязательно
Открытый ключ программы токенов (в кодировке base-58)
commitment
string
Уровень подтверждения (processed, confirmed, finalized)
encoding
string
Кодирование данных счёта (base58, base64, jsonParsed)
dataSlice
object
offset
number
Смещение в данных счёта
length
number
Длина возвращаемых данных

Ответ

result
object
Объект, содержащий:
context
object
slot
number
Слот, в котором был обработан запрос
value
array
Массив объектов токеновых счетов, содержащих:
pubkey
string
Открытый ключ токенового счёта (в кодировке base-58)
account
object
lamports
number
Количество lamports на счёте
owner
string
Открытый ключ владельца счёта (в кодировке base-58)
executable
boolean
Является ли счёт исполняемым
rentEpoch
number
Эпоха, в которую счёт должен будет оплатить аренду следующий раз
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. Используйте подходящий уровень подтверждения в зависимости от ваших потребностей
  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;
        }> = [];
        
        // 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;
      }
    }
    
  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]) => {
          // 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()
          }
        };
      }
    }