跳转到主要内容
Helius 独家功能 - getTransactionsForAddress 仅通过 Helius RPC 节点可用,并非标准 Solana RPC 的一部分。此端点需要 开发者计划 或更高版本。每次调用最多返回 1,000 个完整交易或 1,000 个签名。

概述

getTransactionsForAddress 提供强大的交易历史查询,具备高级过滤功能、灵活的排序和高效的分页。

主要功能

灵活排序

按时间顺序排序(最早优先)或倒序(最新优先)

高级过滤

按时间范围、插槽、签名和交易状态过滤

完整交易数据

一次调用获取完整交易详情

代币账户

包括地址关联的代币账户交易

常见用例

此方法在多个场景中特别有用。代币发行分析有助于跟踪新项目的首次铸币交易和早期代币持有者。钱包资金历史允许您识别特定地址的资金来源和交易模式。交易分析让您可以按成功/失败状态进行过滤,专注于已完成的交易并排除失败的尝试。 该 API 还支持 审计与合规 工作流,通过状态过滤为特定时间段生成交易报告。分析仪表板可以利用历史重播功能构建全面的交易分析。最后,投资组合跟踪应用程序可以访问完整的成功交易历史,用于 DeFi 投资组合管理。

关联代币账户

在 Solana 上,您的钱包实际上并不直接持有代币。相反,您的钱包拥有代币账户,这些代币账户持有您的代币。 当有人向您发送 USDC 时,它会进入您的 USDC 代币账户,而不是您的主钱包地址。
此方法独特之处在于它允许您查询完整的代币历史。您可以查询钱包的完整历史,包括关联代币地址 (ATA)。 本地 RPC 方法如 getSignaturesForAddress 不包括 ATA。 tokenAccounts 过滤器让您可以控制此行为:
  • none(默认):仅返回直接引用钱包地址的交易。当您只关心直接的钱包交互时使用此选项。
  • balanceChanged(推荐):返回引用钱包地址或修改钱包所拥有的代币账户余额的交易。这可以过滤掉垃圾邮件和不相关的操作,如费用收集或委托行为,为您提供清晰的有意义钱包活动视图。
  • all:返回引用钱包地址或任何由钱包拥有的代币账户的所有交易。
遗留交易的限制:此功能不支持 2022 年 12 月之前的交易。 它依赖于 Solana 在 slot 111,491,819 中引入的一项功能(代币转账元数据)。 如果您需要支持这些遗留交易,请使用我们的解决方法

网络支持

网络支持保留期
主网无限
Devnet2 周
测试网不适用

快速开始

1

获取您的 API 密钥

Helius Dashboard 获取您的 API 密钥。
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: 1000,
        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);
3

了解参数

此示例显示了关键特性:
  • transactionDetails: 设置为 'full' 以在一次调用中获取完整的交易数据
  • sortOrder: 使用 'asc' 按时间顺序排序(最旧优先)或 'desc' 按最新优先排序
  • filters.blockTime: 使用 gte(大于或等于)和 lte(小于或等于)设置时间范围
  • filters.status: 仅筛选 'succeeded''failed' 的交易
  • filters.tokenAccounts: 包含关联代币账户的转账、铸造和销毁

请求参数

address
string
必填
要查询交易历史的账户的 Base-58 编码公钥
transactionDetails
string
默认值:"signatures"
返回交易详情的级别:
  • signatures: 基本签名信息(更快)
  • full: 完整交易数据(消除对 getTransaction 调用的需求,支持最多 1,000 限制)
sortOrder
string
默认值:"desc"
结果的排序顺序:
  • desc: 最新优先(默认)
  • asc: 最旧优先(按时间顺序,适合历史分析)
limit
number
默认值:"1000"
要返回的最大交易数:
  • transactionDetails: "signatures" 时最多 1000
  • transactionDetails: "full" 时最多 1000
paginationToken
string
来自上一个响应的分页令牌(格式:"slot:position"
commitment
string
默认值:"finalized"
承诺级别:finalizedconfirmed。不支持 processed 承诺。
filters
object
用于缩小结果范围的高级过滤选项。
filters.slot
object
使用比较运算符筛选插槽号:gte, gt, lte, lt示例:{ "slot": { "gte": 1000, "lte": 2000 } }
filters.blockTime
object
使用比较运算符按 Unix 时间戳筛选:gte, gt, lte, lt, eq示例:{ "blockTime": { "gte": 1640995200, "lte": 1641081600 } }
filters.signature
object
使用比较运算符按交易签名筛选:gte, gt, lte, lt示例:{ "signature": { "lt": "SIGNATURE_STRING" } }
filters.status
string
根据交易成功/失败状态进行过滤:
  • succeeded: 仅成功交易
  • failed: 仅失败交易
  • any: 成功和失败均包含(默认)
示例: { "status": "succeeded" }
filters.tokenAccounts
string
默认值:"none"
对相关代币账户的交易进行过滤:
  • none: 仅返回引用提供地址的交易(默认)
  • balanceChanged: 返回引用提供地址或修改由提供地址持有的代币账户余额的交易(推荐)
  • all: 返回引用提供地址或任何由提供地址持有的代币账户的交易
示例: { "tokenAccounts": "balanceChanged" }
filters.tokenTransfer
object
过滤符合对应方、方向、铸造或原始金额范围的代币转账参与地址的交易。所有字段都是可选的,并以AND语义组合。示例: { "tokenTransfer": { "direction": "in", "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } }
filters.tokenTransfer.with
string
对方地址。匹配另一方为此地址的转账。
filters.tokenTransfer.direction
string
默认值:"any"
根据相对于查询地址的转账方向进行过滤:
  • in: 查询地址接收的转账
  • out: 查询地址发送的转账
  • any: 入站和出站的转账
filters.tokenTransfer.mint
string
用于过滤的代币铸造。
filters.tokenTransfer.amount
object
使用区块链上的原始金额进行比较,而不是UI或调整后的小数金额。支持 gt, gte, lt, 和 lte
encoding
string
用于交易数据的编码格式(仅在 transactionDetails: "full" 时适用)。与 getTransaction API 相同。选项:json, jsonParsed, base64, base58
maxSupportedTransactionVersion
number
设置要返回的最大交易版本。如果省略,则仅返回传统交易。设置为 0 可包含所有版本的交易。
minContextSlot
number
请求可以评估的最小槽位

计量

成功的响应将根据返回内容进行计量:
响应类型学分
完整交易每100个返回交易10个学分,向上取整;最低10个学分
仅签名无论数量如何,都是固定的10个学分
API响应失败免费

过滤器

使用过滤器时,可以对 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"
  }
}

响应字段

字段类型描述
signaturestring交易签名(base-58 编码)。仅在签名模式下。
slotnumber包含此交易块的槽位。
transactionIndexnumber交易在其区块内的零起始索引。用于交易排序和区块重建。
blockTimenumber | null估计生产时间,以 Unix 时间戳表示(自纪元以来的秒数)。
errobject | null如果交易失败则返回错误,成功则为 null。仅在签名模式下。
memostring | null交易相关的备忘录。仅在签名模式下。
confirmationStatusstring交易的集群确认状态。仅在签名模式下。
transactionobject完整的交易数据。仅在完整模式下。
metaobject交易状态元数据。仅在完整模式下。
paginationTokenstring | null获取下一页的令牌,如果没有更多结果则为 null。
字段 transactionIndexgetTransactionsForAddress 独有的。其他类似的端点如 getSignaturesForAddressgetTransactiongetTransactions 不包含此字段。

实际示例

基于时间的分析

生成每月交易报告:
// 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]}`);
    }
  });
});
最初的几笔交易通常揭示资金来源,并有助于识别相关地址或资金模式。

代币转账

tokenTransfer 过滤器将结果缩小到查询地址参与的特定代币转账交易,符合特定条件:特定交易对手、铸造、方向或金额范围。 使用它回答以下问题:
  • 该钱包何时从特定交易对手接收 USDC?
  • 显示每笔超过 1,000 个代币的外部转账。
  • 该钱包是否曾经涉及这个特定的铸造?
该过滤器是请求配置中 filters 对象内的可选字段:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "getTransactionsForAddress",
  "params": [
    "<address>",
    {
      "filters": {
        "tokenTransfer": {}
      }
    }
  ]
}
tokenTransfer 中的所有字段都是可选的。组合多个字段时视为 AND。
字段类型默认值描述
withstring (pubkey)-对方地址。匹配另一方为该地址的转账。
direction"in" | "out" | "any""any"查询地址是否接收、发送或任意。
mintstring (pubkey)-用于过滤的代币 mint。
amountobject-金额比较。使用原始链上金额,而非 UI 或小数调整过的金额。
金额范围运算符:
运算符含义
gt严格大于
gte大于或等于
lt严格小于
lte小于或等于
可以组合金额运算符,如 { "gte": 1000000, "lte": 5000000 } 用于闭区间。 示例: 地址的 USDC 流入:
{
  "filters": {
    "tokenTransfer": {
      "direction": "in",
      "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"
    }
  }
}
到特定对方地址的大额转账:
{
  "filters": {
    "tokenTransfer": {
      "with": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
      "direction": "out",
      "amount": { "gte": 1000000000 }
    }
  }
}
与槽范围和状态组合:
{
  "filters": {
    "status": "succeeded",
    "slot": { "gte": 100000000, "lte": 200000000 },
    "tokenTransfer": {
      "direction": "in",
      "mint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "amount": { "gte": 5000000 }
    }
  }
}
tokenTransfer 可与其他顶级过滤器(slot, blockTime, statustokenAccounts)组合。最终结果为交集。

分页

当事务超过限制时,使用响应中的 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笔交易费用10积分,向上取整,最低为10积分。仅签名响应固定费用为10积分。

最佳实践

性能

为了获得最佳性能,当您不需要完整交易数据时,使用transactionDetails: "signatures"。实现合理的分页大小以获得更好的响应时间,并考虑按时间范围或特定插槽进行过滤以进行更有针对性的查询。

过滤

从宽泛的过滤开始,逐步缩小以找到所需数据。使用基于时间的过滤来进行分析和报告工作流程。您可以结合多个过滤器进行精确查询,以定位特定交易类型或时间段。

分页

在需要稍后恢复大型查询时存储分页键。监控分页深度以进行性能规划,并在需要按时间顺序重播历史事件的情况下使用升序。

错误处理

使用指数退避策略优雅地处理速率限制。始终在请求前验证地址,并在适当时缓存结果以减少API使用并提升应用程序性能。

这与getSignaturesForAddress有何不同?

如果您熟悉标准的getSignaturesForAddress方法,以下是关键区别:

一次调用获取完整交易

使用 getSignaturesForAddress,需要两个步骤:
// Step 1: Get signatures
const signatures = await connection.getSignaturesForAddress(address, { limit: 1000 });

// Step 2: Get transaction details (1,000 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: 1000
      }
    ]
  })
});

一次调用获取令牌历史

使用 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 签名

不支持的地址

路由到旧归档

这些地址的请求被路由到我们的旧归档系统。
地址名称
Stake11111111111111111111111111111111111111质押计划
StakeConfig11111111111111111111111111111111质押配置
Sysvar1111111111111111111111111111111111111Sysvar 所有者
AddressLookupTab1e1111111111111111111111111地址查找表
BPFLoaderUpgradeab1e11111111111111111111111BPF Loader 可升级

插槽扫描回退

这些地址的请求被转发到我们的新归档系统,可以通过逐个插槽的扫描方法查询(最多100个插槽)。但是,这些数据未被索引。
地址名称
11111111111111111111111111111111系统程序
ComputeBudget111111111111111111111111111111计算预算
MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr备忘录程序
Vote111111111111111111111111111111111111111投票程序

is_reserved_address

请求被转发到我们的新归档系统,但数据未被索引,查询返回空值。
地址名称
BPFLoader1111111111111111111111111111111111BPF Loader(已弃用)
BPFLoader2111111111111111111111111111111111BPF Loader
Config1111111111111111111111111111111111111配置程序
Ed25519SigVerify111111111111111111111111111Ed25519 程序
Feature111111111111111111111111111111111111功能程序
KeccakSecp256k11111111111111111111111111111Secp256k1 程序
LoaderV411111111111111111111111111111111111Loader V4
NativeLoader1111111111111111111111111111111本地加载器
SysvarC1ock11111111111111111111111111111111时钟 Sysvar
SysvarEpochSchedu1e111111111111111111111111Epoch 计划 Sysvar
SysvarFees111111111111111111111111111111111费用 Sysvar
Sysvar1nstructions1111111111111111111111111指令 Sysvar
SysvarRecentB1ockHashes11111111111111111111最近区块哈希 Sysvar
SysvarRent111111111111111111111111111111111租赁 Sysvar
SysvarRewards111111111111111111111111111111奖励 Sysvar
SysvarS1otHashes111111111111111111111111111插槽哈希 Sysvar
SysvarS1otHistory11111111111111111111111111插槽历史 Sysvar
SysvarStakeHistory1111111111111111111111111质押历史 Sysvar
SysvarEpochRewards11111111111111111111111111Epoch 奖励 Sysvar
SysvarLastRestartS1ot1111111111111111111111最后重启插槽 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);
}

支持与社区

联系支持

获取有关技术问题和疑问的帮助

Discord 社区

加入 Helius 上数千名在 Solana 上构建的开发者