处理 TON Jetton
Jetton实践最佳做法
Jettons 是 TON 区块链上的代币--可以将其视为类似于以太坊上的 ERC-20 代币。
TON 交易只需确认一次就不可逆转。为获得最佳用户体验/用户界面,请避免额外等待。
提款
Highload Wallet v3 - 这是 TON 区块链的最新解决方案,是 jetton 提款的黄金标准。它允许您利用分批提款的优势。
分批提款 - 指分批发送多笔提款,从而实现快速、廉价的提款。
存款
建议设置多个 MEMO 存款钱包,以提高性能。
Memo Deposits - 这可以让你保留一个存款钱包,用户添加 memo 以便被你的系统识别。这意味着您不需要扫描整个区块链,但对用户来说稍显不便。
Memo-less deposits - 这种解决方案也存在,但整合起来比较困难。不过,如果您希望采用这种方法,我们可以提供协助。请在决定采用这种方法之前通知我们。
其他信息
在进行 jetton 提取时,生态系统中的每项服务都应将 forward_ton_amount
设置为 0.000000001 TON (1 nanotons ),以便在成功转账 时发送 Jetton 通知,否则转账将不符合标准,其他 CEX 和服务将无法处理。
-
请参见 JS 库示例 - tonweb - 这是 TON 基金会的官方 JS 库。
-
如果您想使用 Java,可以参考 ton4j。
-
对于 Go ,应考虑 tonutils-go。目前,我们推荐使用 JS lib.
内容列表
以下文档详细介绍了 Jettons 架构的总体情况,以及 TON 的核心概念,这些概念可能与 EVM 类区块链和其他区块链不同。要想很好地理解 TON,阅读这些文档至关重要,会对你有很大帮助。
本文件依次介绍了以下内容:
- 概述
- 架构
- Jetton 主合约 (Token Minter)
- Jetton 钱包合约 (User Wallet)
- 信息布局
- Jetton 处理(链下)
- Jetton 处理(链上)
- 钱包处理
- 最佳做法
概述
为获得最佳用户体验,建议在 TON 区块链上完成交易后避免等待其他区块。更多信息请参阅 Catchain.pdf。
快速跳转到 jetton 处理的核心描述:
集中处理
链上处理
TON 区块链及其底层生态系统将可替代代币(FT)归类为 jetton 。由于分片应用于 TON 区块链,与类似的区块链模型相比,我们对可替代代币的实现是独一无二的。
在本分析中,我们将深入探讨详细说明 jetton 行为 和 元数据 的正式标准。 关于 jetton 架构不那么正式的分片概述,请参阅我们的 anatomy of jettons 博客文章。
我们还提供了讨论我们的第三方开源 TON 支付处理器(bicycle)的具体细节,该处理器允许用户使用单独的存款地址存取 Toncoin 和 Jettons,而无需使用文本 memo 。
Jetton 架构
TON 上的标准化代币是通过一套智能合约实现的,其中包括
- Jetton master 智能合约
- Jetton wallet 智能合约
Jetton 主智能合约
jetton 主智能合约存储有关 jetton 的一般信息(包括总供应量、元数据链接或元数据本身)。
具有 symbol
等于 TON
的 Jetton,或者包含系统通知消息(如 ERROR
、SYSTEM
等)的 Jetton,务必确保这些 Jetton 在界面中以明确的方式显示,以避免它们与 TON 转账、系统通知等混淆。有时,甚至 symbol
、name
和 image
都会被设计得与原版极为相似,试图误导用户。
为了消除 TON 用户被骗的可能性,请查询特定 Jetton 类型的原始 Jetton 地址(Jetton 主合约地址),或者关注项目的官方社交媒体渠道或网站以获取正确信息。您还可以通过 Tonkeeper ton-assets 列表 检查资产,进一步降低被骗的风险。
检索 Jetton 数据
要检索更具体的 Jetton 数据,请使用合约的 get 方法 get_jetton_data()
。
该方法返回以下 数据:
名称 | 类型 | 说明 |
---|---|---|
total_supply | int | 以不可分割单位计量的已发行净 TON 总数。 |
mintable | int | 详细说明是否可以铸造新 jetton。该值为-1(可以铸造)或 0(不能铸造)。 |
admin_address | slice | |
jetton_content | cell | 根据 TEP-64,您可以查看 jetton 元数据解析页面 获取更多信息。 |
jetton_wallet_code | cell |
还可以使用 Toncenter API 中的 /jetton/masters
方法来检索已解码的 Jetton 数据和元数据。我们还为 (js) tonweb 和 (js) ton-core/ton, (go) tongo 还有 (go) tonutils-go, (python) pytonlib 以及许多其他 SDKs 开发了方法。
使用 Tonweb 运行获取方法和获取链外元数据的 URL 的示例:
import TonWeb from "tonweb";
const tonweb = new TonWeb();
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: "<JETTON_MASTER_ADDRESS>"});
const data = await jettonMinter.getJettonData();
console.log('Total supply:', data.totalSupply.toString());
console.log('URI to off-chain metadata:', data.jettonContentUri);
Jetton minter
如前所述,jettons 既可以是 可铸 (minable)
也可以是 不可铸 (non-mintable)
。
如果它们是不可铸币的,逻辑就变得很简单--没有办法铸入更多代币。要首次铸造代币,请参阅铸造第一个代币 页面。
如果是可铸币,铸币者合约 中会有一个特殊函数来铸造额外的铸币。可以通过从管理员地址发送带有指定操作码的 "内部信息 "来调用该函数。
如果 jetton 管理员希望限制 jetton 的创建,有三种方法:
- 如果您不能或不想更新合约代码,则需要将当前管理员的所有权转移到零地址。这将使合约失去一个有效的管理员,从而阻止任何人铸币。不过,这也会阻止对 jetton 元数据的任何更改。
- 如果您可以访问源代码并对其进行修改,您可以在合约中创建一个方法,设置一个标志,在调用该方法后中止任何造币过程,并在造币函数中添加一条语句来检查该标志。
- 如果可以更新合约的代码,就可以通过更新已部署合约的代码来添加限制。
Jetton 钱包智能合约
Jetton wallet
合约用于发送、接收 和 销毁 jetton 。每个 Jetton wallet
合约都存储了特定用户的钱包余额信息。
在特定情况下, jetton TON 钱包用于每种 jetton TON 类型的单个 jetton TON 持有者。
Jetton wallets
不应该与钱包混淆,钱包是为了区块链交互和存储
Toncoin 资产(例如,v3R2钱包,高负载钱包和其他),它只负责支持和管理特定的 jetton 类型。
部署 Jetton 钱包
在钱包之间 传输 jettons
时,交易(消息)需要一定数量的 TON
作为网络 gas fees 和根据 jetton 钱包合约代码执行操作的付款。
这意味着接收方在接收 jetton 之前无需部署 jetton 钱包。
只要发送方的 Jetton 钱包中持有足够的 TON
来支付所需的 gas 费,接收方的 Jetton 钱包就会自动部署。
检索指定用户的 Jetton 钱包地址
要使用 "所有者地址"(TON 钱包地址)检索 "jetton 钱包 "的 "地址",
,Jetton master contract
提供了获取方法 get_wallet_address(slice owner_address)
。
- API
- js
通过 Toncenter API 中的
/runGetMethod
方法运行get_wallet_address(slice owner_address)
。在实际情况下(而非测试情况下),必须始终检查钱包是否确实归属于所需的 Jetton Master。请查看代码示例了解更多信息。
import TonWeb from 'tonweb';
const tonweb = new TonWeb();
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, { address: '<JETTON_MASTER_ADDRESS>' });
const jettonWalletAddress = await jettonMinter.getJettonWalletAddress(new TonWeb.utils.Address('<OWNER_WALLET_ADDRESS>'));
// It is important to always check that wallet indeed is attributed to desired Jetton Master:
const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider, {
address: jettonWalletAddress
});
const jettonData = await jettonWallet.getData();
if (jettonData.jettonMinterAddress.toString(false) !== jettonMinter.address.toString(false)) {
throw new Error('jetton minter address from jetton wallet doesnt match config');
}
console.log('Jetton wallet address:', jettonWalletAddress.toString(true, true, true));
更多示例请阅读 TON Cookbook。
检索特定 Jetton 钱包的数据
要检索钱包的账户余额、所有者身份信息以及与特定 jetton 钱包合约相关的其他信息,请使用 jetton 钱包合约中的 get_wallet_data()
获取方法。
该方法返回以下数据:
名称 | 类型 |
---|---|
balance | int |
owner | slice |
jetton | slice |
jetton_wallet_code | cell |
- API
- js
使用 Toncenter API 中的
/jetton/wallets
获取方法,检索先前解码的 jetton 钱包数据。
import TonWeb from "tonweb";
const tonweb = new TonWeb();
const walletAddress = "EQBYc3DSi36qur7-DLDYd-AmRRb4-zk6VkzX0etv5Pa-Bq4Y";
const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider,{address: walletAddress});
const data = await jettonWallet.getData();
console.log('Jetton balance:', data.balance.toString());
console.log('Jetton owner address:', data.ownerAddress.toString(true, true, true));
// It is important to always check that Jetton Master indeed recognize wallet
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: data.jettonMinterAddress.toString(false)});
const expectedJettonWalletAddress = await jettonMinter.getJettonWalletAddress(data.ownerAddress.toString(false));
if (expectedJettonWalletAddress.toString(false) !== new TonWeb.utils.Address(walletAddress).toString(false)) {
throw new Error('jetton minter does not recognize the wallet');
}
console.log('Jetton master address:', data.jettonMinterAddress.toString(true, true, true));
信息布局
点击此处 阅读更多信息。
Jetton 钱包和 TON 钱包之间通过以下通信顺序进行通信:
Message 0
发件人 -> 发件人' jetton 钱包
意味着 转移 消息体包含以下数据:
名称 | 类型 | 说明 |
---|---|---|
query_id | uint64 | 允许应用程序链接三种消息类型 Transfer , Transfer notification 和 Excesses 。 为了正确执行此进程,建议总是使用唯一的查询id。 |
amount | coins | 将与信息一起发送的 " TON coin "总量。 |
destination | address | 新所有者的地址 |
response_destination | address | 钱包地址,用于返还带有超额信息的剩余 TON 币。 |
custom_payload | maybe cell | 大小始终 >= 1 bit。自定义数据(用于发送方或接收方 jetton 钱包的内部逻辑)。 |
forward_ton_amount | coins | 如果您想要发送 transfer notification message 与 forward payload ,则必须大于0。 它是 一部分amount 值 和 必须小于 amount |
forward_payload | maybe cell | 大小总是 >= 1 位。如果前 32 位 = 0x0,这只是一条简单的信息。 |
Message 2'
收款人的 jetton 钱包 -> 收款人
。 转账通知信息 (Transfer notification message)。仅在forward_ton_amount
不为零时发送。包含以下数据:
名称 | 类型 |
---|---|
query_id | uint64 |
amount | coins |
sender | address |
forward_payload | cell |
这里的 发送者
地址是Alice的Jeton wallet
的地址。
Message 2''
收款人的 jetton 钱包 -> 发件人
。多余信息正文 (Excess message body)。仅在支付费用后剩余 TON 币时发送。包含以下数据:
名称 | 类型 |
---|---|
query_id | uint64 |
关于 jetton 钱包合约字段的详细说明,请参阅 TEP-74 Jetton 标准
接口说明。
如何 发送附带评论和通知的 Jetton 转账
这次转账需要一些 TON 币作为 费用 和 转账通知信息。
要发送评论,您需要设置 "转发有效载荷"。将 前 32 位设置为 0x0,并附加 您的文本,"前向有效载荷 "将在 jetton notify 0x7362d09c
内部信息中发送。只有当 forward_ton_amount
> 0 时才会生成。
建议带注释的 jetton 传输的 forward_ton_amount
为 1 nanotons 。
最后,要获取 Excess 0xd53276db
信息,必须设置 response destination
。
有时,您在发送 jetton 时可能会遇到 709
错误。该错误表示信息所附的 Toncoin 数量不足以发送信息。请确保 Toncoin > to_nano(TRANSFER_CONSUMPTION) + forward_ton_amount
,这通常>0.04,除非转发的有效载荷非常大。佣金取决于多种因素,包括 Jetton 代码详情以及是否需要为收款人部署新的 Jetton 钱包。
建议在消息中添加一定数量的 Toncoin 作为余量,并将您的地址设置为 response_destination
,以便接收 Excess 0xd53276db
消息。例如,您可以向消 息中添加 0.05 TON,同时将 forward_ton_amount
设置为 1 nanoton(此 TON 数量将附加到 jetton notify 0x7362d09c
消息中)。
你也可能会遇到 cskip_no_gas
错误,它表示成功转移了 jetton,但没有执行其他计算。当 forward_ton_amount
的值等于 1 nanotons 时,这种情况很常见。
查看 最佳实践 中的 "发送带注释的 jettons" 示例。
Jetton 链下处理
TON 交易只需确认一次就不可逆转。为获得最佳用户体验,建议在 TON 区块链上完成交易后避免等待其他区块。更多信息请参见 Catchain.pdf。
接受 jetton 有两种方式:
- 在集中式热钱包内。
- 使用为每个用户独立地址的钱包。
出于安全考虑,最好为个独立的 jetton 拥有个独立的热钱包(每种资产都有许多钱包)。
在处理资金时,还建议提供一个冷钱包,用于存储不参与自动存取款流程的多余资金。
为资产处理和初步核实添加新的 jetton
接收转账通知信息时识别未知 Jetton
如果您的钱包中收到有关未知 Jetton 的转账通知消息,则表示您的钱包 已创建用于保存特定 Jetton。
包含 "转账通知" 正文的内部信息的发件人地址是新 Jetton 钱包的地址。 它不应与 "转账通知"正文 中的 "发件人 "字段混淆。
- 通过 获取钱包数据,读取新 Jetton 钱包的 Jetton 主地址。
- 使用 Jetton 主合约为您的钱包地址(作为所有者)找回 Jetton 钱包地址:如何为指定用户找回 Jetton 钱包地址
- 比较主合约返回的地址和钱包令牌的实际地址。 如果它们匹配,那就很理想。如果不匹配,则很可能收到的是伪造的诈骗令牌。
- 检索 Jetton 元数据:如何接收 Jetton 元数据。
- 检查
symbol
和name
字段是否有欺诈迹象。必要时警告用户。添加新的 jetton 进行处理和初始检查。
通过中央钱包接收用户的 jetton
为防止单个钱包的入账交易出现瓶颈,建议通过多个钱包接受存款,并根据需要扩大这些钱包的数量。
在进行 jetton 提取时,生态系统中的每项服务都应将 forward_ton_amount
设置为 0.000000001 TON (1 nanotons ),以便在成功转账 时发送 Jetton 通知,否则转账将不符合标准,其他 CEX 和服务将无法处理。
在这种情况下,支付服务会为每个发件人创建一个唯一的 memo 标识符,披露集中钱包的地址和发送的金额。发送方将代币 发送到指定的集中地址,并在注释中附上必须的 memo 。
这种方法的优点: 这种方法非常简单,因为在接受代币时无需支付额外费用,而且代币可以直接在热钱包中找回。
这种方法的缺点: 这种方法要求所有用户在转账时附上注释,这可能会导致更多的存款错误(忘记 memo 、 memo 错误等),意味着支持人员的工作量增加。
Tonweb 示例:
准备工作
- 准备已接受的 jetton 列表 ( jetton 主地址)。
- 部署热钱包(如果不需要 Jetton 取款,则使用 v3R2;如果需要 Jetton 取款,则使用高负载 v3)。钱包部署。
- 使用热钱包地址执行 Jetton 传输测试,初始化钱包。
处理收到的 Jettons
- 加载已接受的 jetton 列表。
- 为已部署的热钱包获取 Jetton 钱包地址。
- 使用 获取钱包数据,为每个 Jetton 钱包读取 Jetton 主地址。
- 比较步骤 1 和步骤 3(正上方)中的 Jetton 主合约地址。 如果地址不匹配,必须报告 Jetton 地址验证错误。
- 读取使用热钱包账户的最新未处理交易列表,并 进行迭代(对每笔交易逐一排序)。参见: 检查合约交易。
- 检查输入信息 (in_msg) 中的事务,并从输入信息中检索源地址。Tonweb示例
- 如果源地址与 Jetton 钱包内的地址一致,则需要继续处理交易。 如果不匹配,则跳过该交易,检查下一笔交易。
- 确保报文正文不是空的,且报文的前 32 位与 "转移通知 "操作码 "0x7362d09c "匹配。 Tonweb示例 如果报文体为空或操作码无效,则跳过该事务。
- 读取报文正文的其他数据,包括
query_id
、amount
、sender
、forward_payload
。 Jetton合约消息布局,Tonweb示例 - 尝试从
forward_payload
数据中检索文本注释。前 32 位必须与 文本注释操作码0x00000000
匹配,其余为 UTF-8 编码文本。 Tonweb示例 - 如果
forward_payload
数据为空或操作码无效,则跳过该事务。 - 将收到的注释与保存的 memo 进行比较。如果匹配(始终可以识别用户),则存入转账。
- 从第 5 步重新开始,重复该过程,直到完成整个交易列表。
从用户存款 地址接收 jetton
为了接受来自用户存款地址的 Jettons,支付服务必须为每个发送资金的参与者创建 自己的独立地址(存款)。在这种情况下,服务提供涉及 多个并行流程的执行,包括创建新存款、扫描交易区块、 从存款中提取资金到热钱包等。
由于热钱包可以为每种 Jetton 类型使用一个 Jetton 钱包,因此有必要创建多个
钱包来启动存款。为了创建大量钱包,同时用
一个种子短语(或私钥)来管理它们,有必要在创建钱包时指定不同的 subwallet_id
。
在 TON 上,v3 及更高版本的钱包支持创建子钱包所需的功能。
在 Tonweb 中创建子钱包
const WalletClass = tonweb.wallet.all['v3R2'];
const wallet = new WalletClass(tonweb.provider, {
publicKey: keyPair.publicKey,
wc: 0,
walletId: <SUBWALLET_ID>,
});
准备工作
- 准 备一份已接受的 jetton 清单。
- 部署热钱包(如果不需要 Jetton 取款,则使用 v3R2;如果需要 Jetton 取款,则使用高负载 v3)。钱包部署。
创建存款
- 接受为用户创建新存款的请求。
- 根据热钱包种子生成新的子钱包 (/v3R2) 地址。在 Tonweb 中创建子钱包
- 接收地址可作为 Jetton 存款使用的地址提供给用户(这是存款 Jetton 钱包所有者 的地址)。无需进行钱包初始化,这一步可以在从存款中提取 jetton 时完成 。
- 要获取该地址,必须通过 Jetton 主合约计算 Jetton 钱包的地址。 如何获取指定用户的 Jetton 钱包地址。
- 将 Jetton 钱包地址添加到地址池,用于交易监控,并保存子钱包地址。
处理交易
TON 交易只需确认一次就不可逆转。为获得最佳用户体验,建议在 TON 区块链上完成交易后避免 等待其他区块。更多信息请参见 Catchain.pdf。
由于 Jetton
钱包可能不会发送 transfer notification
、excesses
和 internal transfer
消息,因此并非总能确定从消息中收到的 Jettons 的确切数量。它们没有标准化。这意味着
无法保证 internal transfer
消息可以被解码。
因此,要确定钱包中收到的金额,需要使用 get 方法请求余额。 请求余额时,根据账户在链上特定区块的状态,使用区块来检索关键数据。 使用 Tonweb 接受区块的准备工作。
这一过程如下:
- 准备接受区块(使系统做好接受新区块的准备)。
- 读取新区块并保存前一个区块 ID。
- 接收来自区块的交易。
- 过滤仅用于 Jetton 钱包存款池地址的交易。
- 使用
transfer notification
正文对信息进行解码,以接收更详细的数据,包括sender
地址、Jettonamount
和注释。(请参阅:处理收到的 jetton ) - 如果在
账户内至少有一笔交易有不可解码的转出信息(信息体不包含
transfer notification
的操作码和excesses
的操作码)或没有转出信息,则必须使用当前区块的 get 方法请求 Jetton 余额,同时使用上一个 区块计算余额差额。现在,由于 区块内正在进行的交易,总余额存款的变化就会显现出来。 - 作为未识别Jetton转账的标识符(没有
transfer notification
),如果有这样一个交易或区块数据存在(如果一个区块内有几个存在),则可以使用交易数据。 - 现在需要检查以确保存款余额是正确的。如果存款余额足够发起热钱包和现有Jetton钱包之间的转账,则需要提取Jettons以确保钱包余额减少。
- 从步骤 2 重新启动,重复整个过程。
存款提款
不应从每次存款充值时都将存款转至热钱包,因为转账操作会收取TON手续费(以网络gas费支付)。 重要的是确定一定数量的Jettons,这些Jettons是必需的,才能使转账变得划算(从而存入)。
默认情况下,Jetton 存款钱包的钱包所有者不会被初始化。这是因为
没有支付存储费用的预定要求。Jetton 存款钱包可以在发送带有
transfer
主体的消息时部署,然后立即销毁。为此,工程师必须使用一种特殊的
机制来发送信息:128 + 32。
- 检索标记为将提取到热钱包的存款清单
- 检索已保存的每笔存款的所有者地址
- 然后,从高负载
钱包向每个所有者地址发送信息(通过将若干此类信息合并为一个批次),并附加 TON Jetton 金额。这由以下因素决定:v3R2 钱包
初始化的费用 + 发送带有
transfer
主体的信息的费用 + 与forward_ton_amount
相关的任意 TON 金额(如有必要)。所附的 TON 金额由 v3R2 钱包初始化费用(值) + 与transfer
主体一起发送信息的费用(值) + 与forward_ton_amount
相关的任意 TON 金额 (如有必要)相加得出。 - 当地址上的余额变为非零时,账户状态就会改变。等待几秒钟并查看账户的状态 ,它很快就会从 "不存在 "状态变为 "未启动 "状态。
- 对于每个所有者地址(具有
uninit
状态),必须发送外部消息,其中包含 v3R2 钱包 init 和transfer
消息正文,以便存入 Jetton 钱包 = 128 + 32。对于transfer
,用户必须指定热钱包地址作为destination
和response destination
。 可添加文本注释,以便更简单地识别转账。 - 可以通过 验证使用存款地址向热钱包地址发送的 Jetton,同时考虑到在此处找到的接收 Jettons 信息处理。
jetton 提款
以下是处理 Jetton 提现的分步指南。
要提取 Jettons,钱包会向相应的 Jetton 钱包发送带有 transfer
主体的信息。
然后,Jetton 钱包会将 Jettons 发送给收件人。必须附加一些 TON (至少 1 nanotons )
作为forward_ton_amount
(以及forward_payload
的可选注释),以触发transfer notification
(转账通知)。
请参阅:Jetton 合约信息布局