全部文章
7 分钟阅读

TON 交易历史:lt、哈希与归档深度

深入解析 TON 交易历史机制:逻辑时间(lt)、交易哈希与最后交易指针。AI 智能体如何通过 MCP 分页读取历史,以及深度历史为什么需要归档节点。

TON 交易历史logical timelt hash TONget_transactionsTON 归档节点MCP TON

你在深夜值班,凌晨 3:47 弹出一条消息:用户用 TON 付了订单,机器人却没有记账。打开区块链浏览器——钱在,入账转账清清楚楚。也就是说,问题不在链上,而在于你的服务读取交易历史的方式。这时你才发现,TON 的交易历史和你在 Ethereum 上习惯的完全不是一回事。

在 TON 上做支付监控,看上去简单得有点骗人:轮询余额,余额一变就处理。可实际一跑就散架。一分钟内两笔金额相同的付款——到底是一笔还是两笔?用户付的是 USDT,GRAM 余额纹丝不动。需要拉半年的流水,liteserver 却只给最近几笔交易,更早的只字不提。

如果你正在把 AI 智能体接入 TON,或者在写一个对账支付的后端,那我们就把事情讲透:什么是 TON 交易历史,为什么需要 lt + hash 这对组合,为什么历史深度绕不开归档节点,以及如何让智能体通过 MCP 把这一切读出来。

TON 的交易历史是什么,智能体为什么需要它

在 TON 中,每个账户——钱包、合约、jetton 钱包——都有自己独立的交易链。每笔交易都是处理一条入站消息的结果:收到 GRAM、转移 jetton、调用合约方法。对于收款或对账的智能体(Claude、Cursor 或任何 MCP 客户端)来说,历史是唯一可靠的信息源:余额只回答“现在有多少”,历史才回答“具体发生了什么、什么时候发生、来自谁、金额多少”。

智能体需要历史的,都是些很具体的活儿:

  • 确认某笔付款已到账(“查一下过去一小时内这个地址有没有收到转账”);
  • 汇总某个钱包的流水;
  • 根据标识符追踪某笔具体交易;
  • 核对入账的 jetton(比如 USDT)与预期金额是否一致。

问题在于,“给我第 100–120 号交易”这种请求在 TON 里行不通。这里没有全局连续的区块编号,没法“按编号 N 取交易”。顺序是用另一种方式定义的——逻辑时间。

逻辑时间(lt):为什么 TON 没有常规的区块编号

在 Ethereum 里一切都很直白:有第 19,000,000 号区块,里面的交易按顺序排列。一个全局计数器,单调递增,所有人参照同一个编号。

TON 是一个多线程系统:主链(masterchain)、工作链(workchain),还有会随负载分裂与合并的分片(shard)。这里根本不存在一个能把全网事件线性排序的统一“区块号”。取而代之的是逻辑时间(lt,logical time)——一个单调递增的计数器,网络用它来给事件、消息和交易排序。它保证:如果事件 A 影响了事件 B,那么 lt(A) < lt(B)

由此得出一个实用结论:交易的位置不是由区块号确定的,而是由 (lt, hash) 这对值确定的。lt 负责顺序(谁在前),hash 负责唯一确定具体是哪笔交易。单拿其中任何一个做精确查询都没用:不同对象的 lt 可能非常接近,而只有 hash 又没法告诉 liteserver 该从哪里开始读。

交易哈希与 lt + hash 组合:账户最后一笔交易的指针

交易哈希就是这笔交易的密码学指纹;在 TON 上它以 base64 或 hex 形式出现。lt 说的是“按顺序排在何处”,hash 说的是“具体是哪一笔”,因为在同一个 lt 切面上理论上可能出现歧义,没有 hash,liteserver 不会把交易给你。牢牢记住:(lt, hash) 必须成对使用。只有 lt 或只有 hash 都不够。

起点从哪来?账户状态里保存着指向最新一笔交易的指针——last_transaction_id,它正是那对 lt + hash(字段 last_trans_lt / last_trans_hash)。这是链表的“头”,向后遍历历史就从这里开始。

在 TONNode 的 MCP 工具集中,这件事由 get_account_state 完成——它返回账户状态、标志位以及最后一笔交易的指针。

get_transactions 如何翻页:lt + hash 与 prev_trans 链

接下来是让历史真正能翻页的关键。每笔交易除了自身的 lt 和 hash,还包含两个字段:prev_trans_ltprev_trans_hash——指向同一账户上一笔交易的引用。也就是说,交易构成了一个沿时间向后延伸的单向链表;表头就是账户状态里的 last_transaction_id

get_transactions 接收:

  • account——账户地址;
  • 起始的 lthash——开始读取的位置;
  • count——每批的数量。

它从指定位置向后返回一批交易。分页遍历的逻辑:

  1. get_account_state 拿到 last_trans_lt / last_trans_hash——这是表头。
  2. 调用 get_transactions(account, lt, hash, count)——得到一批交易。
  3. 取这一批中最后一笔交易的 prev_trans_ltprev_trans_hash
  4. 用这对新值再次调用 get_transactions——这就是下一页。
  5. 如此重复,直到 prev_trans_lt 变为 0(账户诞生之初),或者达到你需要的深度。

TON 的分页就是这么工作的:不是“第 2 页”,而是“从这对 (lt, hash) 继续”。

最新区块与深度历史:为什么需要归档节点

大多数人就是在这里栽跟头的。普通的 liteserver 只提供最新区块和账户的近期历史:它保留的是一个有限的状态窗口——最近若干个区块——随着网络增长,旧数据会被冲刷出去。沿着 prev_trans 链走得足够深,liteserver 某一刻就会干脆不再返回交易:那些数据在它的窗口里已经物理上不存在了。刚到账的付款你能看到,半年前的交易——看不到。

这不是 bug,而是设计使然:为每个账户保留自创世以来的完整路径代价太高。深度历史归档节点保存——它不会丢弃旧区块,而是保留全网自始至终的完整状态。

实用结论:

  • 检测付款、近期流水、“过去一小时到没到账”——普通 liteserver 足够;
  • 从第一天起的完整钱包审计——需要归档节点。

还有一处单独的痛点:TON 全局配置里的公共 liteserver。它们是共享且有配额限制的:高负载时经常回 not ready 或者直接 ADNL 超时,而且深度历史同样指望不上。如果你撞上的正是 not ready,那是公共 liteserver 过载的症状——详细拆解见为什么 liteserver 返回 not ready 以及怎么修

关于 TONNode 说句实话:归档节点在路线图上,目前正在同步中——我不会把归档深度当作现成功能许诺给你。今天它提供的是通过托管端点读取最新和近期的历史,不用再在公共 liteserver 上碰运气。至于覆盖创世以来的完整归档,请等单独的公告。

如何让 AI 智能体通过 MCP 读取 TON 历史

TONNode 是面向 TON 的 hosted MCP 服务器,恰好 16 个工具,get_transactions 属于读取类。MCP(Model Context Protocol)是一套标准,让智能体自己调用工具,不需要你手工拼 ADNL 请求。不用装 SDK、不用自建 liteserver、不用解析 TL-B——智能体直接调用工具。

免费本地接入

完整的读取工具集、公共配置、@tonnode/mcp 包——开源,MIT:

{
  "mcpServers": {
    "ton": {
      "command": "npx",
      "args": ["-y", "@tonnode/mcp"]
    }
  }
}

该包走 TON 原生的 ADNL 协议,没有任何 HTTP 中间层。

使用自己密钥的 hosted 端点

要获得有保障的吞吐量,就用带自己密钥的 hosted 端点:

{
  "mcpServers": {
    "ton": {
      "type": "http",
      "url": "https://mcp.tonnode.io/mcp",
      "headers": { "Authorization": "Bearer tn_live_…" }
    }
  }
}

读取历史时会用到:

  • get_account_state——遍历的起点(last_trans_lt / last_trans_hash),账户状态与标志位;
  • get_transactions——按 (lt, hash) 分页返回交易本身;
  • parse_address——在 EQ / UQ / raw 之间离线转换地址;
  • get_jetton_infoget_jetton_balance——处理 jetton 相关的历史。

实战:检测付款与分页遍历历史

来搭一个典型场景——“付款到没到、金额多少”。

给智能体的提示词

ton 工具对 EQC…myshopget_account_state,然后从它的 last_trans_lt / last_trans_hashget_transactions,count 20。找出最近 10 分钟内一笔 5 GRAM 的入账转账。比较之前先用 parse_address 把发送方地址统一成 EQ 格式。如果需要更深的历史——取最后一笔交易的 prev_trans_lt / prev_trans_hash,重复调用 get_transactions

智能体会自己去取链表头,翻完这批交易并核对金额。

分页遍历的伪代码

同样的遍历写成伪代码(这些调用就是智能体触发的 MCP 工具):

state = get_account_state(account)
lt    = state.last_trans_lt
hash  = state.last_trans_hash

while lt != 0:
    batch = get_transactions(account, lt, hash, count=20)
    for tx in batch:
        if matches_expected_payment(tx):   # 金额/备注匹配的入站消息
            return tx
    last = batch[-1]
    lt   = last.prev_trans_lt
    hash = last.prev_trans_hash   # 没有 hash 就请求不了下一页

能省下几小时调试的细节

  • 交易字段里的地址是 raw 格式0:abcd…)。在把发送方或接收方与你熟悉的 EQ… 地址比较之前,先用 parse_address 把两边统一成同一格式——否则明明是同一个地址,字符串比较也对不上。格式详解见 TON 地址中的 EQ、UQ 与 raw
  • USDT 付款在 GRAM 钱包的交易链上是看不到的。jetton 存在于单独的 jetton 钱包上,其地址由链上计算得出(get_jetton_balance 会替你算好并给出当前余额)。查 jetton 历史时,遍历的是 jetton 钱包的交易,而不是主钱包的。
  • 用 decimals 换算 raw 金额。jetton 金额以最小单位返回:要把 5000000 变成人类可读的 5 USDT,需要取 get_jetton_info 里的 decimals——USDT 是 6,大多数 jetton 是 9。搞混了就差 1000 倍。完整拆解见如何捕获 TON 上的 USDT 入账付款
  • (lt, hash) 去重,而不是按金额。两笔一模一样的付款只有 lt+hash 这对值不同——这才是幂等键。

如果除了历史,智能体还需要读取合约的当前状态,那是 run_get_method 的活——不用 SDK 怎么调 get 方法,见不用 SDK 通过 get 方法读取 TON

小结

  • TON 没有 Ethereum 那样的区块编号——交易位置由 (lt, hash) 这对值确定:lt 管顺序,hash 管识别,两者必须成对。
  • 账户的交易是通过 prev_trans_lt / prev_trans_hash 串起来的链表;从 get_account_statelast_transaction_id 开始向后翻页。
  • 普通 liteserver 提供最新和近期的历史;完整深度是归档节点的职责。
  • 处理 jetton 别忘了 get_jetton_info 里的 decimals(USDT = 6),以及需要过一遍 parse_address 的 raw 地址。

不想再跟动不动回 not ready 的公共 liteserver 较劲?get_transactions 在免费的 Hobby 套餐上开箱即用——60 次请求/分钟,永久免费,无需绑卡,登录后立即发放密钥。

一分钟接入 TON 历史读取 → tonnode.io/dashboard?plan=hobby

除了读历史,智能体在 TON 上还能做什么——全部 16 个工具见 TONNode 工具页面

让你的智能体接入 TON

16 个 MCP 工具:读取、非托管兑换、跨链与钱包。免费档 60 次/分钟,无需银行卡。