Tài khoản lịch sử
getHistoricalTokenAccountsByOwner
Liệt kê các tài khoản SPL Token và Token-2022 mà một ví Solana sở hữu tại một slot trước đây hoặc ngay sau một giao dịch cụ thể, có hỗ trợ bộ lọc mint và phân trang.
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": "Tham số không hợp lệ"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Không được ủy quyền"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32005,
"message": "Quá nhiều yêu cầu"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Lỗi nội bộ"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32002,
"message": "Dịch vụ không khả dụng"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32003,
"message": "Hết thời gian chờ cổng kết nối"
},
"id": "1"
}getHistoricalTokenAccountsByOwner đang ở giai đoạn Beta. Các tham số, mức giá, định dạng phản hồi và phạm vi dữ liệu có thể thay đổi.Tổng quan
getHistoricalTokenAccountsByOwner trả về các tài khoản SPL Token và Token-2022 mà một ví sở hữu tại một slot trước đây hoặc ngay sau một giao dịch cụ thể trong slot đó. Đây là phương thức RPC độc quyền của Helius. getTokenAccountsByOwner tiêu chuẩn chỉ trả về trạng thái hiện tại.
Với kiểu mã hóa jsonParsed, số lượng token sử dụng số chữ số thập phân của từng mint tại vị trí được yêu cầu, do đó số dư phản ánh lượng token mà ví nắm giữ tại thời điểm đó.
Dữ liệu lịch sử có sẵn từ slot 444,959,999 (ngày 7 tháng 9 năm 2026) trở đi, tương ứng với trạng thái tại thời điểm bắt đầu epoch 1030.
getHistoricalTokenAccountsByOwner có sẵn trên mainnet cho các gói Business trở lên và có giá 0 credit cho mỗi lệnh gọi trong giai đoạn beta. Mức giá có thể thay đổi.
Điểm khác biệt so với getTokenAccountsByOwner
- Bộ lọc mint hoặc chương trình là tùy chọn. Khi không có bộ lọc, bạn nhận được mọi tài khoản token mà ví sở hữu. Để thu hẹp kết quả, hãy đặt
minthoặcprogramIdtrong đối tượng cấu hình (tham số thứ ba), nhưng không đặt cả hai. - Tham số thứ hai là đối tượng truy vấn dùng để chọn vị trí trong lịch sử, không phải bộ lọc mint hoặc chương trình.
result.valuelà một đối tượng cóaccountsvàpaginationKey, không phải một mảng.
Chọn một vị trí trong lịch sử
Tham số thứ hai là một đối tượng truy vấn:- Bị bỏ qua hoặc
{}: slot mới nhất đã được lập chỉ mục, giới hạn theo mức cam kết được yêu cầu. { "slot": 445000000 }: cuối slot 445,000,000.{ "slot": 445000000, "transactionIndex": 10 }: ngay sau khi giao dịch tại chỉ mục 10 trong slot đó được thực thi.
Yêu cầu mẫu
Yêu cầu này trả về các tài khoản token USDC của ví tại thời điểm cuối slot 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" }
]
}'
Phân trang qua tất cả tài khoản token
Kết quả được sắp xếp trước tiên theo mint, sau đó theo địa chỉ tài khoản token. Mỗi phản hồi trả vềvalue.accounts và value.paginationKey, một con trỏ có dạng <mint>:<tokenAccount>. Truyền lại con trỏ này trong yêu cầu tiếp theo cho đến khi nó là null.
Một trang có thể có ít mục hoặc thậm chí trống nhưng vẫn chứa
paginationKey. Chỉ khi paginationKey là null thì bạn mới đến cuối.slot rõ ràng khi phân trang. Nếu bỏ qua, mỗi trang sẽ đọc slot mới nhất đã được lập chỉ mục, vì vậy các trang có thể đến từ những thời điểm khác nhau.
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);
Đọc phản hồi
Mỗi mục trongvalue.accounts có pubkey của tài khoản token và một đối tượng account. Đối tượng tài khoản có các trường giống getAccountInfo, cùng với lastUpdatedAt:
lastUpdatedAt.slotvàlastUpdatedAt.transactionIndexxác định thao tác ghi đã tạo ra trạng thái này.lastUpdatedAt.slotbằng 444,959,999 vớitransactionIndexlànullcó nghĩa là tài khoản token chưa thay đổi kể từ khi phạm vi dữ liệu lịch sử bắt đầu.context.slotlà slot mới nhất mà Helius đã lập chỉ mục khi xử lý yêu cầu, không phải slot bạn đã truy vấn.
Lỗi
| Mã | Ý nghĩa |
|---|---|
-32018 | Slot nằm trước thời điểm bắt đầu phạm vi dữ liệu lịch sử (slot 444,959,999) |
-32004 | Slot mới hơn mức mà cam kết được yêu cầu cho phép. Hãy sử dụng confirmed hoặc truy vấn một slot cũ hơn |
-32602 | Tham số không hợp lệ, chẳng hạn như đặt cả mint và programId, programId không phải là một chương trình token hoặc mint của paginationKey không khớp với mint |
-32601 | Không tìm thấy phương thức. Hãy gọi endpoint mainnet hoặc Gatekeeper của Helius; phương thức này không có trên devnet |
Các phương thức liên quan
Historical Accounts Guide
Cách hoạt động của vị trí, phạm vi dữ liệu và lastUpdatedAt đối với các phương thức tài khoản lịch sử
getHistoricalAccountInfo
Đọc một tài khoản tại một slot trước đây
getTransactionsForAddress
Tìm slot và transactionIndex của các giao dịch bạn muốn kiểm tra
getTokenAccountsByOwnerV2
Phân trang qua các tài khoản token hiện tại của một ví
Tham số yêu cầu
Địa chỉ được mã hóa Base-58 của ví sở hữu các tài khoản token. Truyền ví chủ sở hữu, không phải tài khoản token.
Slot dùng để đọc trạng thái. Trả về tài khoản ở thời điểm cuối slot này. Slot sớm nhất được hỗ trợ là 444959999, tương ứng với trạng thái tại thời điểm bắt đầu epoch 1030. Khi bị bỏ qua, phương thức sẽ đọc slot mới nhất đã được lập chỉ mục, giới hạn theo mức cam kết được yêu cầu.
Vị trí của một giao dịch trong slot. Trả về trạng thái tài khoản ngay sau khi giao dịch đó được thực thi thay vì tại thời điểm cuối slot. Yêu cầu phải có slot.
Chỉ trả về các tài khoản token do chương trình token này sở hữu. Không thể kết hợp với mint.
TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DATokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
Định dạng mã hóa cho dữ liệu tài khoản. Với jsonParsed, số lượng token sử dụng số chữ số thập phân của mint tại vị trí được yêu cầu. base58 và binary không được hỗ trợ.
base64base64+zstdjsonParsed
Chỉ trả về một phần dữ liệu của từng tài khoản. Áp dụng cho kiểu mã hóa base64 và base64+zstd.
Con trỏ từ paginationKey của phản hồi trước, có dạng
<mint>:<tokenAccount>. Khi cũng đặt mint, mint của con trỏ phải khớp với giá trị đó.Ủy quyền
Khóa API Helius của bạn. Bạn có thể nhận khóa miễn phí trong bảng điều khiển.
Nội dung
application/json
Mảng chứa địa chỉ ví của chủ sở hữu, một đối tượng truy vấn tùy chọn để chọn vị trí trong lịch sử và một đối tượng cấu hình tùy chọn.
Required array length:
1 - 3 elementsĐịa chỉ được mã hóa base-58 của ví từng sở hữu các tài khoản token. Truyền ví của chủ sở hữu, không phải tài khoản token.
Ví dụ:
"86xCnPeV69n6t3DnyGvkKobf9FdN2H9oiVDdaMpo2MMY"
Trang này có hữu ích không?
⌘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": "Tham số không hợp lệ"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Không được ủy quyền"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32005,
"message": "Quá nhiều yêu cầu"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Lỗi nội bộ"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32002,
"message": "Dịch vụ không khả dụng"
},
"id": "1"
}{
"jsonrpc": "2.0",
"error": {
"code": -32003,
"message": "Hết thời gian chờ cổng kết nối"
},
"id": "1"
}