Helius 独家功能 - getTransactionsForAddress 仅通过 Helius RPC 节点提供,并不属于标准的 Solana RPC。此端点需要 开发者计划 或更高计划,每次请求耗费 100 积分。返回 100 笔完整交易或 1,000 个签名。
getTransactionsForAddress 提供强大的交易历史查询,具备高级过滤功能、灵活的排序和高效的分页。
主要功能
灵活排序
按时间顺序排序(最早优先)或倒序(最新优先)
常见用例
此方法在多个场景中特别有用。代币发行分析有助于跟踪新项目的首次铸币交易和早期代币持有者。钱包资金历史允许您识别特定地址的资金来源和交易模式。交易分析让您可以按成功/失败状态进行过滤,专注于已完成的交易并排除失败的尝试。
该 API 还支持 审计与合规 工作流,通过状态过滤为特定时间段生成交易报告。分析仪表板可以利用历史重播功能构建全面的交易分析。最后,投资组合跟踪应用程序可以访问完整的成功交易历史,用于 DeFi 投资组合管理。
关联代币账户
在 Solana 上,您的钱包实际上并不直接持有代币。相反,您的钱包拥有代币账户,这些代币账户持有您的代币。
当有人向您发送 USDC 时,它会进入您的 USDC 代币账户,而不是您的主钱包地址。
此方法独特之处在于它允许您查询完整的代币历史。您可以查询钱包的完整历史,包括关联代币地址 (ATA)。
本地 RPC 方法如 getSignaturesForAddress 不包括 ATA。
tokenAccounts 过滤器让您可以控制此行为:
none(默认):仅返回直接引用钱包地址的交易。当您只关心直接的钱包交互时使用此选项。
balanceChanged(推荐):返回引用钱包地址或修改钱包所拥有的代币账户余额的交易。这可以过滤掉垃圾邮件和不相关的操作,如费用收集或委托行为,为您提供清晰的有意义钱包活动视图。
all:返回引用钱包地址或任何由钱包拥有的代币账户的所有交易。
遗留交易的限制:此功能不支持 2022 年 12 月之前的交易。
它依赖于 Solana 在 slot 111,491,819 中引入的一项功能(代币转账元数据)。
如果您需要支持这些遗留交易,请使用我们的解决方法。
网络支持
| 网络 | 支持 | 保留期 |
|---|
| 主网 | 是 | 无限 |
| Devnet | 是 | 2 周 |
| 测试网 | 否 | 不适用 |
快速开始
使用高级功能查询
获取在两个日期之间,按时间顺序排序的钱包的所有成功交易:// Get successful transactions between Jan 1-31, 2025 in chronological order
const response = await fetch('https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'getTransactionsForAddress',
params: [
'YOUR_ADDRESS_HERE',
{
transactionDetails: 'full',
sortOrder: 'asc',
limit: 100,
filters: {
blockTime: {
gte: 1735689600, // Jan 1, 2025
lte: 1738368000 // Jan 31, 2025
},
status: 'succeeded', // Only successful transactions
tokenAccounts: 'balanceChanged' // Include associated token accounts
}
}
]
})
});
const data = await response.json();
console.log('Successful transactions in January:', data.result.data);
了解参数
此示例显示了关键特性:
- transactionDetails: 设置为
'full' 以在一次调用中获取完整的交易数据
- sortOrder: 使用
'asc' 按时间顺序排序(最旧优先)或 'desc' 按最新优先排序
- filters.blockTime: 使用
gte(大于或等于)和 lte(小于或等于)设置时间范围
- filters.status: 仅筛选
'succeeded' 或 'failed' 的交易
- filters.tokenAccounts: 包含关联代币账户的转账、铸造和销毁
请求参数
要返回的交易详细级别:
signatures: 基本签名信息(较快)
full: 完整的交易数据(消除对 getTransaction 调用的需求,要求限制 ≤ 100)
结果的排序顺序:
desc: 最新优先(默认)
asc: 最旧优先(按时间顺序,适合历史分析)
最大返回交易数:
- 当
transactionDetails: "signatures" 时最多 1000
- 当
transactionDetails: "full" 时最多 100
来自上一个响应的分页令牌(格式:"slot:position")
承诺级别:finalized 或 confirmed。不支持 processed 承诺。
使用比较运算符筛选插槽号:gte, gt, lte, lt示例:{ "slot": { "gte": 1000, "lte": 2000 } }
使用比较运算符按 Unix 时间戳筛选:gte, gt, lte, lt, eq示例:{ "blockTime": { "gte": 1640995200, "lte": 1641081600 } }
使用比较运算符按交易签名筛选:gte, gt, lte, lt示例:{ "signature": { "lt": "SIGNATURE_STRING" } }
根据交易成功/失败状态进行过滤:
succeeded: 仅成功交易
failed: 仅失败交易
any: 成功和失败均包含(默认)
示例: { "status": "succeeded" }
对相关代币账户的交易进行过滤:
none: 仅返回引用提供地址的交易(默认)
balanceChanged: 返回引用提供地址或修改由提供地址持有的代币账户余额的交易(推荐)
all: 返回引用提供地址或任何由提供地址持有的代币账户的交易
示例: { "tokenAccounts": "balanceChanged" }
交易数据的编码格式(仅适用于 transactionDetails: "full")。与 getTransaction API 相同。选项: json, jsonParsed, base64, base58
maxSupportedTransactionVersion
设置要返回的最大交易版本。如果省略,仅返回旧版交易。设置为 0 以包含所有版本的交易。
过滤器
使用过滤器时,可以使用比较运算符针对 slot, blockTime, 或 signature,加上特殊的 status 和 tokenAccounts 过滤器。
比较运算符
这些运算符像数据库查询一样工作,使您能够精确控制数据范围。
| 运算符 | 全名 | 描述 | 示例 |
|---|
gte | 大于或等于 | 包含 ≥ 指定值的值 | slot: { gte: 100 } |
gt | 大于 | 包含 > 指定值的值 | blockTime: { gt: 1641081600 } |
lte | 小于或等于 | 包含 ≤ 指定值的值 | slot: { lte: 2000 } |
lt | 小于 | 包含 < 指定值的值 | blockTime: { lt: 1641168000 } |
eq | 等于 | 仅包含完全等于(仅限 blockTime)的值 | blockTime: { eq: 1641081600 } |
枚举过滤器
| 过滤器 | 描述 | 值 |
|---|
status | 按成功/失败过滤交易 | succeeded, failed, 或 any |
tokenAccounts | 过滤相关令牌账户的交易 | none, balanceChanged, 或 all |
组合过滤器示例:
// Time range with successful transactions only
"filters": {
"blockTime": {
"gte": 1640995200,
"lte": 1641081600
},
"status": "succeeded"
}
// Slot range
"filters": {
"slot": {
"gte": 1000,
"lte": 2000
}
}
// Only failed transactions
"filters": {
"status": "failed"
}
响应格式
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"data": [
{
"signature": "5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv",
"slot": 1054,
"transactionIndex": 42,
"err": null,
"memo": null,
"blockTime": 1641038400,
"confirmationStatus": "finalized"
}
],
"paginationToken": "1055:5"
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"data": [
{
"slot": 1054,
"transactionIndex": 42,
"blockTime": 1641038400,
"transaction": {
"signatures": ["5h6xBEauJ3PK6SWCZ1PGjBvj8vDdWG3KpwATGy1ARAXFSDwt8GFXM7W5Ncn16wmqokgpiKRLuS83KUxyZyv2sUYv"],
"message": {
"accountKeys": ["...", "..."],
"instructions": [...],
// Complete transaction structure
}
},
"meta": {
"fee": 5000,
"preBalances": [1000000, 2000000],
"postBalances": [999995000, 2000000],
// Complete metadata
}
}
],
"paginationToken": "1055:5"
}
}
响应字段
| 字段 | 类型 | 描述 |
|---|
signature | string | 交易签名(base-58 编码)。仅在签名模式下。 |
slot | number | 包含此交易的区块的插槽。 |
transactionIndex | number | 交易在其区块内的零基索引。用于交易排序和区块重建。 |
blockTime | number | null | 估计的生产时间,以 Unix 时间戳表示(自纪元开始的秒数)。 |
err | object | null | 如果交易失败则为错误,否则为 null。仅在签名模式下。 |
memo | string | null | 交易相关的备忘录。仅在签名模式下。 |
confirmationStatus | string | 交易的集群确认状态。仅在签名模式下。 |
transaction | object | 完整交易数据。仅在完整模式下。 |
meta | object | 交易状态元数据。仅在完整模式下。 |
paginationToken | string | null | 用于获取下一页的令牌,如果没有更多结果则为 null。 |
transactionIndex 字段是 getTransactionsForAddress 独有的。其他类似的端点,如 getSignaturesForAddress,getTransaction 和 getTransactions 都不包含此字段。
实用示例
基于时间的分析
生成每月交易报告:
// Get all successful transactions for January 2025
const startTime = Math.floor(new Date('2025-01-01').getTime() / 1000);
const endTime = Math.floor(new Date('2025-02-01').getTime() / 1000);
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransactionsForAddress",
"params": [
"WALLET_OR_PROGRAM_ADDRESS",
{
"transactionDetails": "signatures",
"filters": {
"blockTime": {
"gte": startTime,
"lt": endTime
},
"status": "succeeded"
},
"limit": 1000
}
]
}
分析流程:
// Calculate daily transaction volume
const dailyStats = {};
response.result.data.forEach(tx => {
const date = new Date(tx.blockTime * 1000).toISOString().split('T')[0];
dailyStats[date] = (dailyStats[date] || 0) + 1;
});
console.log('Daily Transaction Counts:', dailyStats);
代币铸造创建
查找特定代币的铸造创建交易:
{
"jsonrpc": "2.0",
"id": "find-first-mints",
"method": "getTransactionsForAddress",
"params": [
MINT_ADDRESS, // Token mint address
{
"encoding": "jsonParsed",
"maxSupportedTransactionVersion": 0,
"sortOrder": "asc", // Chronological order from the beginning
"limit": 10,
"transactionDetails": "full"
}
]
}
对于流动资金池创建,查询池地址:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransactionsForAddress",
"params": [
"POOL_ADDRESS_HERE", // Raydium/Meteora pool address
{
"transactionDetails": "full",
"sortOrder": "asc", // First transaction is usually pool creation
"limit": 1
}
]
}
用例:查找代币铸造或流动资金池创建的确切时刻,包括创建者地址和初始参数。
资金交易
查找谁为特定地址提供资金:
{
"jsonrpc": "2.0",
"id": 1,
"method": "getTransactionsForAddress",
"params": [
"TARGET_WALLET_ADDRESS",
{
"transactionDetails": "full",
"sortOrder": "asc", // Oldest first
"limit": 10
}
]
}
然后分析交易数据以找出SOL转账:
response.result.data.forEach(tx => {
// Look for SOL transfers in preBalances/postBalances
const balanceChanges = tx.meta.preBalances.map((pre, index) =>
tx.meta.postBalances[index] - pre
);
// Positive balance change = incoming SOL
balanceChanges.forEach((change, index) => {
if (change > 0) {
console.log(`Received ${change} lamports from ${tx.transaction.message.accountKeys[index]}`);
}
});
});
前几笔交易通常会揭示资金来源,并帮助识别相关地址或资金模式。
当交易数量超过您的限制时,使用响应中的paginationToken获取下一页。令牌是一个简单的字符串,格式为"slot:position",指示API从何处继续。
如何分页
使用每次响应的分页令牌获取下一页:
// First request
let paginationToken = null;
let allTransactions = [];
const getNextPage = async (paginationToken = null) => {
const params = [
'ADDRESS',
{
transactionDetails: 'signatures',
limit: 100,
...(paginationToken && { paginationToken })
}
];
const response = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'getTransactionsForAddress',
params
})
});
const data = await response.json();
return data.result;
};
// Paginate through all results
do {
const result = await getNextPage(paginationToken);
allTransactions.push(...result.data);
paginationToken = result.paginationToken;
console.log(`Fetched ${result.data.length} transactions, total: ${allTransactions.length}`);
} while (paginationToken);
多个地址
在单个请求中无法查询多个地址。要获取多个地址的交易,请在相同的时间或插槽窗口内查询每个地址,然后合并并排序:
const addresses = ['Address1...', 'Address2...', 'Address3...'];
// Query all addresses in parallel with slot filter
const results = await Promise.all(
addresses.map(address =>
fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'getTransactionsForAddress',
params: [address, {
sortOrder: 'desc',
filters: { slot: { gt: 250000000 } }
}]
})
}).then(r => r.json())
)
);
// Merge and sort by slot
const allTransactions = results
.flatMap(r => r.result.data)
.sort((a, b) => b.slot - a.slot);
对于较大的历史扫描,请遍历时间或插槽窗口(例如,每次1000个插槽)并重复此模式。
每个地址查询都算作单独的API请求(每个地址100个积分)。
最佳实践
为了获得最佳性能,当您不需要完整的交易数据时,请使用transactionDetails: "signatures"。实施合理的页面大小以获得更好的响应时间,并考虑按时间范围或特定插槽过滤以进行更有针对性的查询。
从广泛的过滤器开始,逐步缩小范围以找到所需的数据。在分析和报告工作流程中使用基于时间的过滤器。您可以结合多个过滤器来进行针对特定交易类型或时间段的精确查询。
当需要稍后恢复大型查询时,请存储分页键。监控分页深度以进行性能规划,并在需要按时间顺序重播历史事件的情况下使用升序。
错误处理
使用指数退避策略优雅地处理速率限制。始终在发出请求之前验证地址,并在适当的情况下缓存结果以减少API使用量并提高应用程序性能。
与getSignaturesForAddress有什么不同?
如果您熟悉标准的getSignaturesForAddress方法,这里是关键区别:
在一次调用中获取完整交易
使用getSignaturesForAddress,需要两个步骤:
// Step 1: Get signatures
const signatures = await connection.getSignaturesForAddress(address, { limit: 100 });
// Step 2: Get transaction details (100 additional calls!)
const transactions = await Promise.all(
signatures.map(sig => connection.getTransaction(sig.signature))
);
使用getTransactionsForAddress,只需一次调用:
const response = await fetch(heliusRpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'getTransactionsForAddress',
params: [
address,
{
transactionDetails: 'full',
limit: 100
}
]
})
});
一次调用获取令牌历史
使用 getSignaturesForAddress,您需要先调用 getTokenAccountsByOwner,然后查询每个令牌账户:
// OLD WAY (with getSignaturesForAddress)
// Step 1: Get all token accounts owned by this wallet
const tokenAccounts = await connection.getTokenAccountsByOwner(
new PublicKey(walletAddress),
{ programId: TOKEN_PROGRAM_ID }
);
// Step 2: Fetch signatures for the wallet itself
const walletSignatures = await connection.getSignaturesForAddress(
new PublicKey(walletAddress),
{ limit: 1000 }
);
// Step 3: Fetch signatures for EVERY token account (this is the painful part)
const tokenAccountSignatures = await Promise.all(
tokenAccounts.value.map(async (account) => {
return connection.getSignaturesForAddress(
account.pubkey,
{ limit: 1000 }
);
})
);
// Step 4: Merge all results together
const allSignatures = [
...walletSignatures,
...tokenAccountSignatures.flat()
];
// Step 5: Deduplicate (many transactions touch multiple accounts)
const seen = new Set();
const uniqueSignatures = allSignatures.filter((sig) => {
if (seen.has(sig.signature)) {
return false;
}
seen.add(sig.signature);
return true;
});
// Step 6: Sort chronologically
const sortedSignatures = uniqueSignatures.sort(
(a, b) => a.slot - b.slot
);
return sortedSignatures;
使用 getTransactionsForAddress,您只需要设置 filters.tokenAccounts:
// NEW WAY (with getTransactionsForAddress)
const response = await fetch("https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: "helius-example",
method: "getTransactionsForAddress",
params: [
walletAddress,
{
filters: {
tokenAccounts: "all"
}
sortOrder: "asc",
limit: 100
}
]
})
});
const { result } = await response.json();
return result;
附加功能
按时间顺序排序
使用 sortOrder: 'asc' 从旧到新排序交易
基于时间的过滤
使用 blockTime 过滤器按时间范围过滤
状态过滤
使用 status 过滤器仅获取成功或失败的交易
更简单的分页
使用 paginationToken 而不是令人困惑的 before/until 签名
不支持的地址
路由到旧档案系统
对这些地址的请求被路由到我们的旧档案系统。
| Address | Name |
|---|
Stake11111111111111111111111111111111111111 | Stake Program |
StakeConfig11111111111111111111111111111111 | Stake Config |
Sysvar1111111111111111111111111111111111111 | Sysvar Owner |
AddressLookupTab1e1111111111111111111111111 | Address Lookup Table |
BPFLoaderUpgradeab1e11111111111111111111111 | BPF Loader Upgradeable |
插槽扫描回退
对这些地址的请求被转发到我们的新档案系统,并且可以通过插槽扫描方法(最多100个插槽)进行查询。但是,这些数据没有被索引。
| Address | Name |
|---|
11111111111111111111111111111111 | System Program |
ComputeBudget111111111111111111111111111111 | Compute Budget |
MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr | Memo Program |
Vote111111111111111111111111111111111111111 | Vote Program |
is_reserved_address
请求被转发到我们的新档案系统,然而数据未被索引,查询返回为空。
| Address | Name |
|---|
BPFLoader1111111111111111111111111111111111 | BPF Loader (deprecated) |
BPFLoader2111111111111111111111111111111111 | BPF Loader |
Config1111111111111111111111111111111111111 | Config Program |
Ed25519SigVerify111111111111111111111111111 | Ed25519 Program |
Feature111111111111111111111111111111111111 | Feature Program |
KeccakSecp256k11111111111111111111111111111 | Secp256k1 Program |
LoaderV411111111111111111111111111111111111 | Loader V4 |
NativeLoader1111111111111111111111111111111 | Native Loader |
SysvarC1ock11111111111111111111111111111111 | Clock Sysvar |
SysvarEpochSchedu1e111111111111111111111111 | Epoch Schedule Sysvar |
SysvarFees111111111111111111111111111111111 | Fees Sysvar |
Sysvar1nstructions1111111111111111111111111 | Instructions Sysvar |
SysvarRecentB1ockHashes11111111111111111111 | Recent Blockhashes Sysvar |
SysvarRent111111111111111111111111111111111 | Rent Sysvar |
SysvarRewards111111111111111111111111111111 | Rewards Sysvar |
SysvarS1otHashes111111111111111111111111111 | Slot Hashes Sysvar |
SysvarS1otHistory11111111111111111111111111 | Slot History Sysvar |
SysvarStakeHistory1111111111111111111111111 | Stake History Sysvar |
SysvarEpochRewards11111111111111111111111111 | Epoch Rewards Sysvar |
SysvarLastRestartS1ot1111111111111111111111 | Last Restart Slot Sysvar |
解决方法:历史代币账户发现
对于在槽位111,491,819之前有代币账户活动的地址,由于代币余额元数据中的owner字段尚不存在,tokenAccounts筛选器无法确定所有权。要获得完整结果,可以通过解析早期交易指令手动发现这些代币账户,然后对每一个代币账户并行查询gTFA。
const HELIUS_RPC = "https://mainnet.helius-rpc.com/?api-key=YOUR_API_KEY";
const OWNER_CUTOFF_SLOT = 111_491_819;
async function rpcCall(method, params) {
const res = await fetch(HELIUS_RPC, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ jsonrpc: "2.0", id: "1", method, params }),
});
const json = await res.json();
if (json.error) throw new Error(json.error.message);
return json.result;
}
// Step 1: Discover token accounts owned by the address before the cutoff slot
// by parsing initializeAccount instructions and transfer authorities.
async function discoverHistoricalTokenAccounts(address) {
const tokenAccounts = new Set();
let paginationToken = null;
do {
const result = await rpcCall("getTransactionsForAddress", [
address,
{
transactionDetails: "full",
encoding: "jsonParsed",
maxSupportedTransactionVersion: 0,
sortOrder: "asc",
limit: 100,
filters: { slot: { lt: OWNER_CUTOFF_SLOT } },
...(paginationToken && { paginationToken }),
},
]);
if (!result?.data?.length) break;
for (const entry of result.data) {
const tx = entry.transaction;
const meta = entry.meta;
if (!tx || !meta) continue;
const allInstructions = [
...(tx.message?.instructions ?? []),
...(meta.innerInstructions ?? []).flatMap((inner) => inner.instructions ?? []),
];
for (const ix of allInstructions) {
// AToken program "create" instruction
if (ix.program === "spl-associated-token-account") {
if (ix.parsed?.type === "create" && ix.parsed.info?.wallet === address && ix.parsed.info?.account) {
tokenAccounts.add(ix.parsed.info.account);
}
continue;
}
if (ix.program !== "spl-token" && ix.program !== "spl-token-2022") continue;
const type = ix.parsed?.type;
const info = ix.parsed?.info;
// Token account initialization
if (type === "initializeAccount" || type === "initializeAccount2" || type === "initializeAccount3") {
if (info?.owner === address && info?.account) tokenAccounts.add(info.account);
}
// Transfers where our address is the authority (source account is ours)
if (type === "transfer" || type === "transferChecked") {
if (info?.authority === address && info?.source) tokenAccounts.add(info.source);
}
}
}
paginationToken = result.paginationToken;
} while (paginationToken);
return Array.from(tokenAccounts);
}
// Step 2: Fetch all signatures for an address with pagination
async function fetchAllSignatures(address, filters) {
const allSignatures = [];
let paginationToken = null;
do {
const result = await rpcCall("getTransactionsForAddress", [
address,
{
transactionDetails: "signatures",
sortOrder: "asc",
limit: 1000,
...(filters && { filters }),
...(paginationToken && { paginationToken }),
},
]);
if (!result?.data?.length) break;
allSignatures.push(...result.data);
paginationToken = result.paginationToken;
} while (paginationToken);
return allSignatures;
}
// Step 3: Get complete history by combining tokenAccounts:"all" with
// individual queries for historical token accounts
async function getCompleteHistory(address) {
const historicalAccounts = await discoverHistoricalTokenAccounts(address);
if (historicalAccounts.length === 0) {
return fetchAllSignatures(address, { tokenAccounts: "all" });
}
// Query main address with tokenAccounts:"all" + each historical account in parallel
const results = await Promise.all([
fetchAllSignatures(address, { tokenAccounts: "all" }),
...historicalAccounts.map((addr) => fetchAllSignatures(addr)),
]);
// Merge and deduplicate by signature
const seen = new Set();
const merged = [];
for (const batch of results) {
for (const tx of batch) {
if (!seen.has(tx.signature)) {
seen.add(tx.signature);
merged.push(tx);
}
}
}
return merged.sort((a, b) => a.slot - b.slot);
}
支持与社区