文章目录
- 前言
- 一、什么是TON地址?
- 二、原始地址和用户友好地址
- 三、关于弹跳标致
- 四、小故事分享
- 艾米的搬迁
- 用户友好地址的发明
- 地址的状态和艾米的挑战
- 鲍勃的转账尝试
- 五、代码转化举例
前言
在TON中存在着两种地址,一种是用户友好地址,一种是原始地址。我们来详细的讲解两种地址。
一、什么是TON地址?
在 TON 区块链中,智能合约地址具有特定的特点,本节将描述这些特点,并解释在 TON 中,参与者(actors)与智能合约是同义词。
一切都是智能合约
在 TON 上,智能合约是使用参与者模型构建的。实际上,TON 中的参与者在技术上被表示为智能合约。这意味着即使是你的钱包也是一个简单的参与者(和智能合约)。
通常,参与者会处理传入的消息,改变其内部状态,并生成出站消息作为结果。这就是为什么 TON 区块链上的每个参与者(即智能合约)都必须有一个地址,以便它能够从其他参与者接收消息。
智能合约地址
在 TON 上运行的智能合约地址通常由两个主要部分组成:
- 工作链 ID (workchain_id):表示工作链 ID(一个带符号的 32 位整数)。
- 账户 ID (account_id):表示账户地址(根据工作链不同,可能是 64-512 位)。
在本文档的原始地址概览部分,我们将讨论(workchain_id, account_id)对如何呈现。
工作链 ID 和账户 ID
-
工作链 ID:如前所述,TON 区块链上可以创建多达 2^32 个工作链。我们还注意到 32 位前缀智能合约地址如何识别并与不同工作链上的智能合约地址相关联。这允许智能合约在 TON 区块链上发送和接收来自不同工作链的消息。
目前,TON 区块链上通常只运行主链(workchain_id=-1)和偶尔的基础工作链(workchain_id=0)。
它们都有 256 位的地址,因此,我们假设 workchain_id 要么是 0 要么是 -1,工作链内的地址恰好是 256 位。
-
账户 ID:所有 TON 上的主链和基础链(或基础工作链)的账户 ID 都使用 256 位地址。
实际上,账户 ID(account_id)被定义为智能合约对象的哈希函数(特别是 SHA-256)。在 TON 区块链上运行的每个智能合约都存储两个主要组件:编译后的代码(智能合约逻辑编译成字节码的形式)和初始状态(合约在链上部署时的值)。
最后,为了准确推导出合约的地址,需要计算对应于(初始代码,初始状态)对象对的哈希。目前,我们不会深入探讨 TVM 的工作原理,但重要的是要理解,TON 上的账户 ID 是使用这个公式确定的:account_id = hash(初始代码,初始状态)。
地址状态
每个地址可能处于以下可能状态之一:
- nonexist - 此地址没有被接受的交易,因此没有任何数据(或者合约已被删除)。我们可以说最初所有的 2^256 地址都处于这种状态。
- uninit - 地址有一些数据,包含余额和元信息。在这种状态下,地址还没有智能合约代码/持久数据。例如,当一个非存在的地址收到了一些代币,它就会进入这种状态。
- active - 地址具有智能合约代码、持久数据和余额。在这种状态下,它可以在交易期间执行一些逻辑并改变其持久数据。当一个 uninit 状态的地址收到带有 state_init 参数的传入消息时,它就会进入这种状态(注意,要部署这个地址,state_init 和代码的哈希必须与地址相等)。
- frozen - 地址无法执行任何操作,这种状态只包含前一个状态的两个哈希值(代码和状态单元分别)。当一个地址的存储费用超过其余额时,它会进入这种状态。要解冻它,你可以发送一个带有 state_init 和代码的内部消息,这些代码存储了前面描述的哈希,并带有一些 TON 币。恢复可能很困难,所以你应该避免这种情况。有一个项目可以解冻地址,你可以在这里找到。
二、原始地址和用户友好地址
在提供了 TON 上智能合约地址如何利用工作链和账户 ID(特别是主链和基础链)的简要概述之后,重要的是要理解这些地址以两种主要格式表达:
- 原始地址:智能合约地址的原始完整表示形式。
- 用户友好地址:用户友好地址是原始地址的增强格式,使用更好的安全性和易用性。
下面,我们将解释这两种地址类型之间的区别,并深入了解为什么在 TON 上使用用户友好地址。
原始地址
原始智能合约地址由工作链 ID 和账户 ID(workchain_id, account_id)组成,以以下格式显示:
[十进制工作链 ID]:[64 十六进制数字与账户 ID]
下面是一个使用工作链 ID 和账户 ID(表示为 workchain_id 和 account_id)的原始智能合约地址的例子:
-1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
注意地址字符串开头的 -1,它表示属于主链的工作链 ID。
注意
地址字符串中可能使用大写字母(如 ‘A’, ‘B’, ‘C’, ‘D’ 等),而不是它们的小写对应物(如 ‘a’, ‘b’, ‘c’, ‘d’ 等)。
原始地址的问题
使用原始地址格式存在两个主要问题:
- 使用原始地址格式时,无法在发送交易前验证地址以消除错误。这意味着如果你在发送交易前不小心添加或删除了地址字符串中的字符,你的交易将被发送到错误的目的地,导致资金损失。
- 使用原始地址格式时,无法添加特殊标志,比如在使用用户友好地址发送交易时使用的那些标志。为了帮助你更好地理解这个概念,我们将在下面解释可以使用哪些标志。
用户友好地址
用户友好地址是 TON 区块链上使用的地址格式,它们提供了更好的安全性和易用性。这些地址通常包括一个人类可读的前缀和一个地址哈希,例如:
<prefix>:<hash>
前缀可以是字母和数字的组合,而哈希是地址的加密表示。用户友好地址的设计使得在发送交易之前更容易验证地址的正确性,并且可以包含特殊标志,例如弹跳标志,这些标志在交易中用于指示如何处理无法交付的消息。
例如,一个用户友好的 TON 地址可能看起来像这样:
EQA2B1C3D4E-ACBD
在这个例子中,EQA2B1C3D4E
是前缀,而 -ACBD
是地址的哈希部分。这种格式使得地址更易于阅读和分享,同时也减少了因拼写错误而导致的资金损失风险。
三、关于弹跳标致
在区块链和智能合约的背景下,消息弹回(bounceable)机制是一个重要的特性,它允许在特定条件下自动返回消息和剩余的资金。以下是对您提供的信息的详细解释:
- 内部消息的弹回性:在智能合约之间发送的几乎所有内部消息都应该是可弹回的,即它们的“弹回”位应该被设置。这样,如果目标智能合约不存在,或者在处理消息时抛出了未处理的异常,消息将被“弹回”给发送者。
- 消息弹回的处理:当消息被弹回时,它会携带原始价值的余额(减去所有消息传输和gas费用)。被弹回的消息体将包含32位的
0xffffffff
,后跟原始消息的256位内容,但“弹回”标志被清除,“已弹回”标志被设置。 - 智能合约的处理:所有智能合约都应该检查所有传入消息的“已弹回”标志,并相应地进行处理。如果消息已被弹回,智能合约应该静默接受(通过立即以零退出码终止)或者执行一些特殊处理来检测哪个外出查询失败了。被弹回消息体中的查询内容绝不应该被执行。
在某些情况下,必须使用非弹回(non-bounceable)的内部消息。例如,要创建新账户,至少需要发送一个非弹回的内部消息给它们。除非这个消息包含新智能合约的代码和数据的StateInit
,否则非弹回的内部消息不应该包含非空的内容体。
-
大额非弹回消息的风险:不建议允许最终用户(例如,钱包用户)发送包含大量价值的非弹回消息(例如,超过五个Toncoins)。如果用户这样做,应该警告他们。
-
分阶段发送:更好的做法是首先发送一小笔金额,然后初始化新的智能合约,然后再发送较大金额。这种方法可以减少因智能合约错误或未初始化而导致的资金损失风险。智能合约开发者和用户都应该对弹回和非弹回消息有清晰的理解,并在发送和处理这些消息时采取适当的预防措施。这包括正确设置消息的标志位,以及在智能合约中实现适当的错误处理和查询失败检测逻辑。通过这种方式,可以确保资金的安全和智能合约交互的可靠性。
四、小故事分享
在 TON 区块链的多彩世界里,有一个被称为“智能合约镇”的地方。这个镇子上的居民都是智能合约,他们每个人都有一个独特的地址,就像门牌号一样,确保他们能够接收到来自其他居民的消息和资产。
艾米的搬迁
艾米是一个新来的智能合约,她是一个多功能钱包,准备在智能合约镇上安家。为了开始新生活,她需要一个地址。镇上的长老给了她一个特殊的地址,由两部分组成:
- 工作链 ID (workchain_id):这是她所在小镇的编号,对于主链来说是 -1,表明她是在最重要的小镇上。
- 账户 ID (account_id):这是她在小镇上的房子的门牌号,是一个256位的数字,确保她能够被准确地找到。
她的完整地址看起来像这样:-1:fcb91a3a3816d0f7b8c2c76108b8a9bc5a6b7a55bd79f8ab101c52db29232260
。
用户友好地址的发明
艾米知道,她的地址可能会在互联网上分享,所以她决定使用一个用户友好的地址,这样人们在发送消息时就不容易出错。她找到了镇上的智者,智者使用了他的魔法工具,将原始地址转换成了一个更易于阅读和分享的格式,比如 kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny
。
这个地址不仅更易于分享,还包含了一些特殊的标志,比如“弹跳标志”,以确保如果有任何问题,消息和资产可以安全返回给发送者。
地址的状态和艾米的挑战
艾米了解到,她的地址可以处于几种状态之一:
- nonexist:如果她的地址从未接收过任何消息,它就不存在。
- uninit:如果有人向她的地址发送了消息,但还没有部署智能合约,它就是未初始化的。
- active:一旦部署了智能合约,她的地址就活跃起来,可以处理逻辑和存储数据。
- frozen:如果艾米没有足够的资金支付存储费用,她的地址就会被冻结。
艾米希望她的地址始终保持活跃状态,这样她就可以自由地接收和发送 TON 币。
鲍勃的转账尝试
在 TON 区块链的智能合约镇上,艾米和鲍勃的故事继续展开。鲍勃想要给艾米发送一些 TON 币,但是他知道艾米的钱包地址目前是未初始化(uninit)状态。这意味着艾米的钱包还不能接受交易,因为它还没有部署智能合约的代码和初始状态。鲍勃决定采取正确的步骤来帮助艾米初始化她的钱包。他准备了一个特殊的交易,这个交易包含了 state_init
参数。state_init
是一个包含了智能合约代码和初始数据的参数,它对于初始化钱包至关重要。
- 包含
state_init
的交易:鲍勃的交易不仅仅是发送 TON 币,它还包含了艾米钱包的智能合约代码和初始状态。这样,当交易到达时,艾米的钱包将会被部署,并且 TON 币将会被存入她的新钱包。
在 TON 区块链中,交易可以包含一个弹跳标志(bounce flag),这个标志决定了如果交易不能被成功处理,资金将如何被处理。
-
弹跳标志设置:如果交易中的弹跳标志被设置,那么如果交易不能被接收方处理(比如接收方地址是 uninit 状态),整个交易将会被退回给发送者,发送者将会收回他们的 TON 币,减去一些网络费用。
-
弹跳标志未设置:如果弹跳标志未被设置,那么即使交易不能被处理,发送的资金也不会被退回。资金将会被冻结在接收方的地址上,直到接收方的钱包被初始化并且能够接收资金。
在 TON 区块链中,弹跳标志通常用一个特定的值来表示。例如,用户友好地址中的弹跳标志可以是: -
0x11:表示地址是可弹跳的(bounceable)。
-
0x51:表示地址是不可弹跳的(non-bounceable)。
鲍勃在发送交易时,决定不设置弹跳标志,因为他希望即使艾米的钱包目前还不能接收资金,资金也能保留在艾米的地址上。他知道艾米很快就会初始化她的钱包,并且能够访问这些资金。
鲍勃构造了他的交易,包含了state_init
参数,并且确保弹跳标志未被设置。他通过智能合约镇的区块链网络发送了交易,交易中包含了足够的 TON 币和艾米钱包的智能合约代码。几天后,艾米准备好了她的钱包,她初始化了钱包,并且立即能够接收鲍勃发送的 TON 币。鲍勃的明智决策和对 TON 区块链的理解确保了交易的成功,艾米对鲍勃的帮助感激不尽。
五、代码转化举例
在 TON 区块链中,地址转换是一个常见的操作,尤其是将用户友好的地址(user-friendly addresses)转换为原始地址(raw addresses),或者反之。用户友好地址是为了方便用户识别和使用而设计的,而原始地址则是区块链上实际使用的地址格式。
以下是一些可以用来转换 TON 地址的工具和 API:
- ton.org/address - 这是一个在线工具,可以用来转换 TON 地址。
- dton.io API - 提供了一系列 API 方法来处理 TON 地址。
- toncenter API - 在主网和测试网上都提供了 API 方法来转换地址。
此外,如果你在使用 JavaScript 开发钱包或应用,可以使用以下库来转换地址:
- ton.js - 这是一个 JavaScript 库,可以用来在用户友好的地址和原始地址之间进行转换。
- tonweb - 这是另一个 JavaScript 库,提供了类似的功能。
转换地址的示例代码可能如下:
使用 ton.js
:
const { TonClient, Address } = require('@tonclient/core');
const { Account } = require('@tonclient/account');async function convertAddress() {const tonClient = new TonClient({ network: { endpoints: ['https://toncenter.com/api/v2/jsonRPC'] } });const userFriendlyAddress = '0:123...';const rawAddress = Address.parse(userFriendlyAddress).toString();console.log('Raw address:', rawAddress);// 也可以从原始地址转换回用户友好地址const friendlyAddress = Address.fromRaw(rawAddress).toString();console.log('User-friendly address:', friendlyAddress);
}convertAddress().catch(console.error);
使用 tonweb
:
const { Ton, account } = require('@tonweb/core');
const { signer } = require('@tonweb/signer');async function convertAddress() {const ton = new Ton({endpoint: 'https://toncenter.com/api/v2/jsonRPC',signer: signer.keys(...),});const userFriendlyAddress = '0:123...';const rawAddress = account.getAddress(userFriendlyAddress);console.log('Raw address:', rawAddress);// 从原始地址转换回用户友好地址const friendlyAddress = account.parseAddress(rawAddress);console.log('User-friendly address:', friendlyAddress);
}convertAddress().catch(console.error);
在处理 TON 区块链中的地址时,了解如何正确地将 TON 币发送到未初始化的钱包地址是很重要的。以下是一些可能的情况及其结果:
-
包含 state_init 的交易:如果交易中包含了 state_init(包含钱包或智能合约的代码和数据),智能合约将首先使用提供的 state_init 进行部署。部署完成后,传入的消息将被处理,类似于发送到已经初始化的账户。
-
没有 state_init,且设置了弹跳标志的交易:消息无法被传递到未初始化的智能合约,它将被弹回到发送者。在扣除消耗的 gas 费用后,剩余的金额将返回给发送者的地址。
-
没有 state_init,且未设置弹跳标志的交易:消息无法被传递,但不会弹回到发送者。相反,发送的金额将被记入接收地址的余额中,即使钱包尚未初始化。这些资金将存放在那里,直到地址持有者部署并初始化智能钱包合约,然后他们就可以访问余额。
正确的做法是先向钱包地址(尚未初始化)发送一些 TON,且不设置弹跳标志。完成这一步后,所有者可以使用当前未初始化地址的资金来部署和初始化钱包。这通常是在第一次钱包操作时发生的。
TON 区块链实现了对错误交易的保护,标准钱包和应用程序会自动管理向未初始化地址发送交易的复杂性,使用可弹跳和不可弹跳地址,这些地址在这里有描述。通常的做法是,当钱包向未初始化的地址发送币时,会同时向可弹跳和不可弹跳地址发送币,而不会返回。
如果你正在 TON 区块链上开发自定义产品,确保你的应用程序在发送资金之前验证接收方地址是否已初始化,并根据地址状态使用可弹跳地址和不可弹跳地址,以确保资金能够正确返回。