Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
网络与协议指南阅读约 11 分钟

TON 域名解析:.ton 名称如何映射到钱包、站点和存储

一份实用指南,介绍 TON DNS 域名如何在链上解析、如何使用 dnsresolve get-method 查询它们,以及如何验证 .ton 名称实际指向的内容。

TL;DR

TON 域名是 .ton 一级命名空间下的链上名称,可解析为钱包地址、用于 TON Sites 的 ADNL 地址、TON Storage bag ID、文本记录或委托的子解析器。域名是由钱包拥有的 NFT 项目,解析过程是通过在相关 DNS 智能合约上调用 dnsresolve get-method 来完成,而不是查询传统 DNS 服务器。本文解释了合约树、记录类别,以及通过 RPC 端点解析 .ton 名称并验证返回记录的可复现流程。还涵盖了可能出错的地方、域名能保证什么和不能保证什么,以及如何从单次查询过渡到生产级解析。

TON 域名究竟是什么

TON 域名是在 The Open Network 上注册于 .ton 一级命名空间下的人类可读标识符。用户无需复制冗长的智能合约地址,即可向 example.ton 这样的名称发送资金,客户端可以在构建转账前将该名称解析为底层钱包合约地址。同一命名层还指向 TON Sites 使用的 ADNL 地址、TON Storage bag ID、任意文本记录和嵌套子域名。

重要的思维模型是:TON DNS 并非附加在链上的独立命名数据库。它是一棵智能合约树,根合约存储在区块链配置中,每个注册的 .ton 域名都表示为钱包拥有的 NFT 项目。因此,解析是一次合约读取:你调用 get-method 并解释返回的 cell。这一区别对构建钱包、浏览器、支付流程或站点网关的任何人来说都很重要,因为缓存、错误处理和验证都源于此。

  • .ton 是 TON DNS 树中唯一的一级域名。
  • 每个注册的域名都是一个 NFT 项目,因此所有权通过兼容 NFT 的钱包和市场转移。
  • .t.me 命名空间作为委托子解析器运行,使用相同的解析接口。
  • 记录可以指向钱包、ADNL 地址、存储 bag ID、文本或另一个解析器合约。

本演练的假设和前提条件

以下流程假设你有一个可以执行 TON get-method 的 HTTP 端点、一个你有权检查的域名,以及对 JSON 和十六进制编码 cell 的基本熟悉。你不需要运行自己的节点来跟随操作,但需要一个暴露 runGetMethod API 而不仅仅是索引账户历史的端点。

如果你仍在选择访问路径,请比较 TON RPC 端点指南 中的托管和自托管选项,并查看 API 服务概览 了解 get-method 调用是如何暴露的。对于需要可预测吞吐量或私有访问的团队,专用 TON 节点 可以消除共享端点带来的测量波动。

合约树和 dnsresolve 接口

TON 中的每个 DNS 智能合约都暴露一个签名为 (int, cell) dnsresolve(slice subdomain, int category) 的 get-method。subdomain 参数是要解析的名称的剩余部分,category 是记录类别名称的 SHA-256 哈希,或零以请求所有记录。该方法返回它消耗的位数和一个包含 DNS 记录的 cell。

返回值是大多数集成错误的来源。如果合约消耗的位数少于完整子域长度,返回的 cell 是一个 dns_next_resolver 记录,指向处理剩余部分的另一个合约。解析器必须跟随该指针,而不是将部分结果视为最终结果。这正是 .t.me 和用户定义子域等委托命名空间的工作方式:父解析器移交它不拥有的后缀。

// Conceptual resolution loop (pseudocode)
// 1. Start at the root resolver from config param #4.
// 2. Call dnsresolve(remaining_subdomain, category).
// 3. If bits_consumed < remaining_subdomain.length:
//      read dns_next_resolver from the returned cell
//      set resolver = next_resolver
//      remaining_subdomain = remaining_subdomain.slice(bits_consumed)
//      repeat from step 2
// 4. Otherwise, decode the returned record by its TL-B type.

记录类型及每种类型解析到什么

TON DNS 记录值由 TL-B 模式描述。记录类型决定客户端应如何解释有效载荷,混淆它们是不正确地址显示的常见原因。文档化的记录类型涵盖智能合约地址、ADNL 地址、存储引用、文本和解析器委托。

dns_smc_address 记录存储智能合约地址,这是钱包向 .ton 名称发送资金所需的。dns_adnl_address 记录存储 256 位 ADNL 地址以及可选协议列表,用于定位 TON Sites。存储记录携带 TON Storage 的 bag ID,文本记录携带任意 UTF-8 字符串,dns_next_resolver 将后缀委托给另一个合约。构建解析器时,首先按类型解码,然后才应用业务逻辑。

  • dns_smc_address:用于转账的钱包或合约地址。
  • dns_adnl_address:TON Sites 的网络地址,带有可选协议标志。
  • 存储记录:通过 TON Storage 寻址的内容的 bag ID。
  • 文本记录:任意 UTF-8 元数据,如个人资料字段。
  • dns_next_resolver:子域名和委托命名空间的委托指针。

通过 RPC 端点解析 .ton 域名

以下 curl 调用在解析器合约上调用 dnsresolve get-method。将 RPC_URL 替换为你的端点,将 RESOLVER_ADDRESS 替换为你正在查询的解析器。子域作为 slice 传递,类别设置为零以请求所有记录。响应包含退出码、堆栈和返回的 cell,然后你对其进行解码。

这是一个诊断调用,不是完整的解析器。它确认端点可以执行 get-method,并在你添加解码逻辑之前向你展示响应的原始形态。首先对你控制的域名运行它,以便将返回的记录与你的预期进行比较。

curl -sS "$RPC_URL" \
  -H 'Content-Type: application/json' \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "runGetMethod",
    "params": {
      "address": "RESOLVER_ADDRESS",
      "method": "dnsresolve",
      "stack": [
        ["slice", "ZXhhbXBsZQ=="],
        ["int", "0"]
      ]
    }
  }'

带验证的 JavaScript 解析辅助函数

下面的脚本包装了相同的 get-method 调用,并添加了两个在生产中很重要的检查:它在读取堆栈之前验证 JSON-RPC 响应形状,并记录原始 cell,以便失败的解码可以重放。它故意不硬编码公共端点。将 RPC_URL 指向你选择的端点,并将凭据保存在环境变量中。

解码步骤故意保留为带标签的函数,因为 TL-B 解码取决于返回的记录类型。在实际集成中,你会根据记录类型分支,解码匹配的模式,然后才将有效载荷转换为钱包地址或 ADNL 地址。将未解码的 cell 与解码值一起记录,使差异可审计。

const RPC_URL = process.env.RPC_URL;
const RESOLVER = process.env.RESOLVER_ADDRESS;

async function resolveDomain(subdomainSliceB64, category = 0) {
  const res = await fetch(RPC_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'runGetMethod',
      params: {
        address: RESOLVER,
        method: 'dnsresolve',
        stack: [
          ['slice', subdomainSliceB64],
          ['int', String(category)]
        ]
      }
    })
  });

  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const body = await res.json();
  if (body.error) throw new Error(body.error.message);

  const result = body.result;
  if (!result || !Array.isArray(result.stack)) {
    throw new Error('Unexpected runGetMethod response');
  }

  const bitsConsumed = Number(result.stack[0]?.value ?? 0);
  const recordCell = result.stack[1]?.value ?? null;

  return { bitsConsumed, recordCell, raw: result };
}

resolveDomain(process.env.SUBDOMAIN_SLICE_B64)
  .then(r => console.log(JSON.stringify(r, null, 2)))
  .catch(err => { console.error(err); process.exit(1); });

对照预期状态验证结果

成功的 get-method 调用并不等同于正确的解析。验证意味着将解码后的记录与独立的真相来源进行比较:域名的 NFT 所有权、注册时设置的记录以及第二个客户端的行为。如果解码后的钱包地址与你配置的地址不匹配,故障通常出在解码路径或你查询的解析器上,而不是链上。

使用下表作为结果模板。用你自己观察到的值填写;不要复制任何文章中的数字,包括本文。重点是使每个检查可复现,以便下次运行流程时回归可见。

  • 检查 1:bitsConsumed 等于完整子域长度,或存在 dns_next_resolver。
  • 检查 2:解码的记录类型与你请求的类别匹配。
  • 检查 3:解码的地址等于独立钱包或浏览器显示的值。
  • 检查 4:重复调用返回相同记录,确认端点一致性。

结果表流程

将每次运行记录在表格中,列包括域名、解析器地址、类别、消耗的位数、解码的记录类型、解码值以及是否与独立来源匹配。完成的行可能如下面的标题所示,替换为你自己的值。| 域名 | 解析器 | 类别 | 消耗的位数 | 记录类型 | 解码值 | 匹配 |。在运行流程之前,将数据行留空;该表是测量工具,不是对网络状态的声明。

如果你在比较端点,请依次对每个端点运行相同的表格,并将延迟和一致性与正确性分开记录。端点性能与解析正确性是不同的问题,将它们混为一谈会掩盖哪一层失败。

排查常见解析故障

大多数故障属于少数几类。返回非零退出码的 get-method 通常意味着解析器地址错误或该合约上不存在该方法。缺少堆栈的响应意味着端点返回了你未检查的错误信封。解码失败通常意味着你假设了记录类型,而不是从返回的 cell 中读取它。

委托是最微妙的情况。如果 bitsConsumed 小于子域长度,而你忽略了 dns_next_resolver,你将为一个实际在下一跳正确解析的名称报告部分或空结果。同样,查询错误的类别会返回不包含你预期类型的记录集,这看起来像是缺少记录,而不是过滤错误。

  • 非零退出码:确认解析器地址以及合约是否暴露 dnsresolve。
  • 空或格式错误的堆栈:在读取值之前验证 JSON-RPC 信封。
  • 解码错误:根据记录类型分支,而不是假设 dns_smc_address。
  • 部分解析:跟随 dns_next_resolver 直到子域被完全消耗。
  • 结果不一致:在更改解码逻辑之前重复调用并比较端点。

局限性、权衡以及域名不能保证什么

.ton 域名是一种命名便利,不是身份证明。NFT 的所有权确立了对名称的控制,但解析的钱包地址仅与所有者设置的记录一样可信。客户端应显示解析的地址并让用户确认,尤其是对于高价值转账。域名可以转移,记录可以更改,因此缓存的解析可能过时。

解析还取决于解析器树可通过你的端点访问。如果你依赖单个共享端点,瞬时故障看起来就像域名缺失。缓存提高延迟但增加过时风险;正确的平衡取决于你是在显示名称还是准备交易。对于交易准备,在签名时刻附近解析,并根据用户意图验证解码的地址。

生产集成的后续步骤

从单次诊断调用转向一个解析器,它跟随委托、按记录类型解码、使用显式过期时间缓存,并为每次失败记录原始 cell。添加第二个端点作为交叉检查,以便单个提供商中断不会表现为解析错误。如果你运营自己的基础设施,在确定容量之前,请查看 节点托管指南 和 网络目录。

具体到端点选择,RPC 定价页面 解释了请求量如何映射到计划,TON RPC 端点指南 涵盖了支持 get-method 调用的访问模式。保持解析逻辑独立于任何单一提供商,以便切换端点只是配置更改而不是重写。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始