Исторические данные аккаунтов
getHistoricalTokenAccountsByOwner
Получите список счетов SPL Token и Token-2022, которыми кошелёк Solana владел в прошлом слоте или сразу после определённой транзакции, с фильтрами по минту и пагинацией.
POST
/
getHistoricalTokenAccountsByOwner
curl --request POST \
--url 'https://mainnet.helius-rpc.com/?api-key=' \
--header 'Content-Type: application/json' \
--data '
{
"jsonrpc": "2.0",
"id": "1",
"method": "getHistoricalTokenAccountsByOwner",
"params": [
"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
{
"slot": 445000000
},
{
"encoding": "jsonParsed",
"limit": 2
}
]
}
'import requests
url = "https://mainnet.helius-rpc.com/?api-key="
payload = {
"jsonrpc": "2.0",
"id": "1",
"method": "getHistoricalTokenAccountsByOwner",
"params": [
"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
{ "slot": 445000000 },
{
"encoding": "jsonParsed",
"limit": 2
}
]
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
jsonrpc: '2.0',
id: '1',
method: 'getHistoricalTokenAccountsByOwner',
params: [
'86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY',
{slot: 445000000},
{encoding: 'jsonParsed', limit: 2}
]
})
};
fetch('https://mainnet.helius-rpc.com/?api-key=', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://mainnet.helius-rpc.com/?api-key=",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'jsonrpc' => '2.0',
'id' => '1',
'method' => 'getHistoricalTokenAccountsByOwner',
'params' => [
'86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY',
[
'slot' => 445000000
],
[
'encoding' => 'jsonParsed',
'limit' => 2
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://mainnet.helius-rpc.com/?api-key="
payload := strings.NewReader("{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"method\": \"getHistoricalTokenAccountsByOwner\",\n \"params\": [\n \"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY\",\n {\n \"slot\": 445000000\n },\n {\n \"encoding\": \"jsonParsed\",\n \"limit\": 2\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://mainnet.helius-rpc.com/?api-key=")
.header("Content-Type", "application/json")
.body("{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"method\": \"getHistoricalTokenAccountsByOwner\",\n \"params\": [\n \"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY\",\n {\n \"slot\": 445000000\n },\n {\n \"encoding\": \"jsonParsed\",\n \"limit\": 2\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://mainnet.helius-rpc.com/?api-key=")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"method\": \"getHistoricalTokenAccountsByOwner\",\n \"params\": [\n \"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY\",\n {\n \"slot\": 445000000\n },\n {\n \"encoding\": \"jsonParsed\",\n \"limit\": 2\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"jsonrpc": "2.0",
"id": "1",
"result": {
"context": {
"slot": 468129571
},
"value": {
"accounts": [
{
"pubkey": "EKyGnXReqmmXyqBHmMjV2CKCLafJS47xyhXXZ5WuFMxD",
"account": {
"lamports": 531589475273,
"owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"data": [
"<string>"
],
"executable": false,
"rentEpoch": 123,
"space": 82,
"lastUpdatedAt": {
"slot": 444999837,
"transactionIndex": 700
}
}
}
],
"paginationKey": "11112zAgXhc6hGfdfr5anSY91mq7Cs4HHpSVEQc4ASG:EKyGnXReqmmXyqBHmMjV2CKCLafJS47xyhXXZ5WuFMxD"
}
}
}{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Недопустимые параметры"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Не авторизовано"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32005,
"message": "Слишком много запросов"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Внутренняя ошибка"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32002,
"message": "Сервис недоступен"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32003,
"message": "Истекло время ожидания шлюза"
},
"id": "1"
}getHistoricalTokenAccountsByOwner находится на стадии бета-тестирования. Параметры, цены, форматы ответов и охват данных могут измениться.Обзор
getHistoricalTokenAccountsByOwner возвращает счета SPL Token и Token-2022, которыми кошелёк владел в прошлом слоте или сразу после определённой транзакции в этом слоте. Этот метод RPC доступен исключительно в Helius. Стандартный метод getTokenAccountsByOwner возвращает только текущее состояние.
При кодировке jsonParsed суммы токенов используют количество десятичных знаков каждого минта в запрошенной позиции, поэтому балансы отражают средства кошелька на тот момент.
История доступна начиная со слота 444 959 999 (7 сентября 2026 года), соответствующего состоянию в начале эпохи 1030.
getHistoricalTokenAccountsByOwner доступен в основной сети для тарифных планов Business и выше. В период бета-тестирования каждый вызов стоит 0 кредитов. Цены могут измениться.
Отличия от getTokenAccountsByOwner
- Фильтр по минту или программе необязателен. Без фильтра вы получите все токен-счета, которыми владел кошелёк. Чтобы сузить результаты, укажите либо
mint, либоprogramIdв объекте конфигурации (третьем параметре), но не оба одновременно. - Второй параметр — объект запроса, который выбирает позицию в истории, а не фильтр по минту или программе.
result.value— это объект сaccountsиpaginationKey, а не массив.
Выбор позиции в истории
Второй параметр — это объект запроса:- Не указан или
{}: последний проиндексированный слот с учётом запрошенного уровня подтверждения. { "slot": 445000000 }: конец слота 445 000 000.{ "slot": 445000000, "transactionIndex": 10 }: сразу после выполнения транзакции с индексом 10 в этом слоте.
Пример запроса
Этот запрос возвращает токен-счета USDC кошелька на конец слота 445 000 000:curl https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": "1",
"method": "getHistoricalTokenAccountsByOwner",
"params": [
"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
{ "slot": 445000000 },
{ "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "encoding": "jsonParsed" }
]
}'
Получение всех токен-счетов с пагинацией
Результаты упорядочены сначала по минту, затем по адресу токен-счёта. Каждый ответ возвращаетvalue.accounts и value.paginationKey — курсор в формате <mint>:<tokenAccount>. Передавайте его в следующем запросе, пока он не станет равен null.
Страница может содержать мало записей или быть пустой, но при этом включать
paginationKey. Только значение null для paginationKey означает, что вы достигли конца.slot. Если его не указать, каждая страница будет считывать последний проиндексированный слот, поэтому страницы могут относиться к разным моментам времени.
const url = `https://mainnet.helius-rpc.com/?api-key=${process.env.HELIUS_API_KEY}`;
async function getTokenAccountsAt(owner: string, slot: number) {
const accounts = [];
let paginationKey: string | null = null;
do {
const response = await fetch(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: "1",
method: "getHistoricalTokenAccountsByOwner",
params: [
owner,
{ slot },
{ encoding: "jsonParsed", limit: 1000, ...(paginationKey && { paginationKey }) },
],
}),
});
const { result, error } = await response.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
accounts.push(...result.value.accounts);
paginationKey = result.value.paginationKey;
} while (paginationKey);
return accounts;
}
const tokenAccounts = await getTokenAccountsAt("86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY", 445000000);
Чтение ответа
Каждая запись вvalue.accounts содержит pubkey токен-счёта и объект account. Объект счёта содержит те же поля, что и getAccountInfo, а также lastUpdatedAt:
lastUpdatedAt.slotиlastUpdatedAt.transactionIndexопределяют запись, которая создала это состояние.- Значение
lastUpdatedAt.slot, равное 444 959 999, приnullвtransactionIndexозначает, что токен-счёт не изменялся с начала охвата истории. context.slot— это последний слот, проиндексированный Helius на момент обработки запроса, а не запрошенный вами слот.
Ошибки
| Код | Значение |
|---|---|
-32018 | Слот предшествует началу охвата истории (слот 444 959 999) |
-32004 | Слот новее, чем допускает запрошенный уровень подтверждения. Используйте confirmed или запросите более старый слот |
-32602 | Недопустимые параметры: например, одновременно указаны mint и programId; programId не является токен-программой; либо минт в paginationKey не соответствует mint |
-32601 | Метод не найден. Вызовите конечную точку Helius в основной сети или конечную точку Gatekeeper; метод недоступен в devnet |
Связанные методы
Historical Accounts Guide
Узнайте, как работают позиции, охват и lastUpdatedAt для методов исторических счетов
getHistoricalAccountInfo
Получите данные одного счёта в прошлом слоте
getTransactionsForAddress
Найдите slot и transactionIndex транзакций, которые хотите проверить
getTokenAccountsByOwnerV2
Получите текущие токен-счета кошелька с пагинацией
Параметры запроса
Адрес кошелька в кодировке Base58, который владел токен-счетами. Передайте кошелёк владельца, а не токен-счёт.
Слот, для которого нужно прочитать состояние. Возвращает состояние счёта на конец этого слота. Самый ранний поддерживаемый слот — 444959999, соответствующий состоянию в начале эпохи 1030. Если параметр не указан, считывается последний проиндексированный слот с учётом запрошенного уровня подтверждения.
Позиция транзакции в слоте. Возвращает состояние счёта сразу после выполнения этой транзакции, а не в конце слота. Требуется указать slot.
Возвращает только токен-счета, принадлежащие этой токен-программе. Нельзя использовать вместе с mint.
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DATokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
Формат кодирования данных счёта. При использовании jsonParsed суммы токенов учитывают количество десятичных знаков минта в запрошенной позиции. base58 и binary не поддерживаются.
base64base64+zstdjsonParsed
Курсор из paginationKey предыдущего ответа в формате
<mint>:<tokenAccount>. Если вы также указали mint, минт курсора должен ему соответствовать.Уровень подтверждения. processed не поддерживается.
finalizedconfirmed
Авторизации
Ваш ключ API Helius. Его можно получить бесплатно в панели управления.
Тело
application/json
Массив, содержащий адрес кошелька владельца, необязательный объект запроса для выбора позиции в истории и необязательный объект конфигурации.
Required array length:
1 - 3 elementsАдрес кошелька, которому принадлежали аккаунты токенов, в кодировке Base58. Передайте кошелёк владельца, а не аккаунт токена.
Пример:
"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY"
Была ли эта страница полезной?
⌘I
getHistoricalTokenAccountsByOwner
curl --request POST \
--url 'https://mainnet.helius-rpc.com/?api-key=' \
--header 'Content-Type: application/json' \
--data '
{
"jsonrpc": "2.0",
"id": "1",
"method": "getHistoricalTokenAccountsByOwner",
"params": [
"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
{
"slot": 445000000
},
{
"encoding": "jsonParsed",
"limit": 2
}
]
}
'import requests
url = "https://mainnet.helius-rpc.com/?api-key="
payload = {
"jsonrpc": "2.0",
"id": "1",
"method": "getHistoricalTokenAccountsByOwner",
"params": [
"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY",
{ "slot": 445000000 },
{
"encoding": "jsonParsed",
"limit": 2
}
]
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
jsonrpc: '2.0',
id: '1',
method: 'getHistoricalTokenAccountsByOwner',
params: [
'86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY',
{slot: 445000000},
{encoding: 'jsonParsed', limit: 2}
]
})
};
fetch('https://mainnet.helius-rpc.com/?api-key=', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://mainnet.helius-rpc.com/?api-key=",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'jsonrpc' => '2.0',
'id' => '1',
'method' => 'getHistoricalTokenAccountsByOwner',
'params' => [
'86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY',
[
'slot' => 445000000
],
[
'encoding' => 'jsonParsed',
'limit' => 2
]
]
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://mainnet.helius-rpc.com/?api-key="
payload := strings.NewReader("{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"method\": \"getHistoricalTokenAccountsByOwner\",\n \"params\": [\n \"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY\",\n {\n \"slot\": 445000000\n },\n {\n \"encoding\": \"jsonParsed\",\n \"limit\": 2\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://mainnet.helius-rpc.com/?api-key=")
.header("Content-Type", "application/json")
.body("{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"method\": \"getHistoricalTokenAccountsByOwner\",\n \"params\": [\n \"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY\",\n {\n \"slot\": 445000000\n },\n {\n \"encoding\": \"jsonParsed\",\n \"limit\": 2\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://mainnet.helius-rpc.com/?api-key=")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"jsonrpc\": \"2.0\",\n \"id\": \"1\",\n \"method\": \"getHistoricalTokenAccountsByOwner\",\n \"params\": [\n \"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY\",\n {\n \"slot\": 445000000\n },\n {\n \"encoding\": \"jsonParsed\",\n \"limit\": 2\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"jsonrpc": "2.0",
"id": "1",
"result": {
"context": {
"slot": 468129571
},
"value": {
"accounts": [
{
"pubkey": "EKyGnXReqmmXyqBHmMjV2CKCLafJS47xyhXXZ5WuFMxD",
"account": {
"lamports": 531589475273,
"owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
"data": [
"<string>"
],
"executable": false,
"rentEpoch": 123,
"space": 82,
"lastUpdatedAt": {
"slot": 444999837,
"transactionIndex": 700
}
}
}
],
"paginationKey": "11112zAgXhc6hGfdfr5anSY91mq7Cs4HHpSVEQc4ASG:EKyGnXReqmmXyqBHmMjV2CKCLafJS47xyhXXZ5WuFMxD"
}
}
}{
"jsonrpc": "2.0",
"error": {
"code": -32602,
"message": "Недопустимые параметры"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Не авторизовано"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32005,
"message": "Слишком много запросов"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Внутренняя ошибка"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32002,
"message": "Сервис недоступен"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32003,
"message": "Истекло время ожидания шлюза"
},
"id": "1"
}