Appearance
TEP: 74 - 同质化代币(Jettons)标准
- TEP: 74
- 标题: 同质化代币(Jettons)标准
- 状态: 活跃
- 类型: 合约接口
- 作者: EmelyanenkoK, Tolya
- 创建日期: 2022年3月12日
- 取代: -
- 被取代: -
摘要
这是一个关于 Jettons(TON 同质化代币)的标准接口。
动机
标准接口将大大简化不同代币化资产的交互和展示。
Jetton 标准描述了:
- Jetton 的转移方式。
- 获取给定 Jetton 资产的常见信息(如名称、流通供应量等)的方式。
指南
有用的链接
- Jetton 实现参考
- Jetton 部署器
- FunC Jetton 课程 (英文/俄文)
规范
在本文中,我们使用大写的 “Jetton” 作为同类型代币的整体标识,而小写的 “jetton” 作为某种类型代币数量的标识。
Jettons 组织如下:每个 Jetton 都有一个主智能合约,用于铸造新的 jettons,记录流通供应量并提供常见信息。
同时,每个用户拥有的 jettons 数量信息以去中心化的方式存储在各自(每个所有者)的智能合约中,称为 “jetton-wallets”。
例如:如果你发行了一个流通供应量为 200 jetton 的 Jetton,这些 jetton 由 3 个人拥有,那么你将部署 4 个合约:1 个 Jetton 主合约和 3 个 jetton-wallets。
Jetton 钱包智能合约
必须实现:
内部消息处理程序
1. transfer
请求
入站消息的 TL-B 方案:
transfer#0f8a7ea5 query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress
response_destination:MsgAddress custom_payload:(Maybe ^Cell)
forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
= InternalMsgBody;
query_id
- 任意请求编号。
amount
- 转移的 jettons 数量,以基本单位表示。
destination
- jettons 新所有者的地址。
response_destination
- 用于发送成功转移确认和剩余入站消息 Toncoins 的地址。
custom_payload
- 可选的自定义数据(由发送者或接收者 jetton 钱包用于内部逻辑)。
forward_ton_amount
- 发送到目标地址的 nanotons 数量。
forward_payload
- 应发送到目标地址的可选自定义数据。
应被拒绝如果:
- 消息不是来自所有者。
- 发送者钱包中没有足够的 jettons。
- 没有足够的 TON(考虑到 jetton 自己的存储费指南和操作成本)来处理操作,部署接收者的 jetton-wallet 并发送
forward_ton_amount
。 - 处理请求后,接收者的 jetton-wallet 必须 至少发送
in_msg_value - forward_ton_amount - 2 * max_tx_gas_price - 2 * fwd_fee
到response_destination
地址。 如果发送者 jetton-wallet 无法保证这一点,它必须立即停止执行请求并抛出错误。max_tx_gas_price
是 FT 栖息链的最大交易 gas 限制的 Toncoins 价格。对于基础链,可以从ConfigParam 21
的gas_limit
字段中获取。fwd_fee
是转移请求的转发费,可以通过解析转移请求消息获取。
否则应做:
- 减少发送者钱包中的 jetton 数量
amount
并发送消息以增加接收者钱包中的 jetton 数量(并可选地部署它)。 - 如果
forward_amount > 0
,确保接收者的 jetton-wallet 发送消息到destination
地址,附带forward_amount
nanotons,并具有以下布局: TL-B 方案:
transfer_notification#7362d09c query_id:uint64 amount:(VarUInteger 16)
sender:MsgAddress forward_payload:(Either Cell ^Cell)
= InternalMsgBody;
query_id
应与请求的 query_id
相等。
amount
是转移的 jettons 数量。
sender
是转移 jettons 的前所有者的地址。
forward_payload
应与请求的 forward_payload
相等。
如果 forward_amount
等于零,则不应发送通知消息。
- 接收者的钱包应将入站消息币的所有剩余部分发送到
response_destination
,具有以下布局: TL-B 方案:excesses#d53276db query_id:uint64 = InternalMsgBody;
query_id
应与请求的query_id
相等。
forward_payload
格式
如果你想在 forward_payload
中发送一个简单的评论,那么 forward_payload
必须以 0x00000000
(32 位无符号整数等于零)开头,评论包含在 forward_payload
的剩余部分中。
如果评论不以字节 0xff
开头,则评论是一个文本评论;它可以“按原样”显示给钱包的最终用户(在过滤无效和控制字符并检查其是否为有效的 UTF-8 字符串后)。 例如,用户可以在这个文本字段中指明简单转移的目的(“用于咖啡”)。
另一方面,如果评论以字节 0xff
开头,则剩余部分是一个“二进制评论”,不应作为文本显示给最终用户(仅在必要时作为十六进制转储显示)。 “二进制评论”的预期用途是,例如,包含商店中支付的购买标识符,由商店的软件自动生成和处理。
如果 forward_payload
包含与目标智能合约(例如,与 DEX)的交互的二进制消息,则没有前缀。
这些规则与从常规钱包简单发送 Toncoins 时的有效载荷格式相同(智能合约指南:内部消息,第 3 节)。
2. burn
请求
入站消息的 TL-B 方案:
burn#595f07bc query_id:uint64 amount:(VarUInteger 16)
response_destination:MsgAddress custom_payload:(Maybe ^Cell)
= InternalMsgBody;
query_id
- 任意请求编号。
amount
- 烧毁的 jettons 数量。
response_destination
- 用于发送成功烧毁确认和剩余入站消息币的地址。
custom_payload
- 可选的自定义数据。
应被拒绝如果:
- 消息不是来自所有者。
- 发送者钱包中没有足够的 jettons。
- 没有足够的 TONs 在处理请求后至少发送
in_msg_value - max_tx_gas_price
到response_destination
地址。 如果发送者 jetton-wallet 无法保证这一点,它必须立即停止执行请求并抛出错误。
否则应做:
- 减少燃烧钱包中的 jetton 数量
amount
并向 jetton 主合约发送包含燃烧信息的通知。 - Jetton 主合约应将入站消息币的所有剩余部分发送到
response_destination
,具有以下布局: TL-B 方案:excesses#d53276db query_id:uint64 = InternalMsgBody;
query_id
应与请求的query_id
相等。
Get-methods
get_wallet_data()
返回(int balance, slice owner, slice jetton, cell jetton_wallet_code)
balance
- (uint256) 钱包中的 jettons 数量。owner
- (MsgAddress) 钱包所有者的地址。jetton
- (MsgAddress) Jetton 主地址。jetton_wallet_code
- (cell) 该钱包的代码。
Jetton 主合约
Get-methods
get_jetton_data()
返回(int total_supply, int mintable, slice admin_address, cell jetton_content, cell jetton_wallet_code)
total_supply
- (integer) - 已发行的 jettons 总数。mintable
- (-1/0) - 标志,指示 jettons 数量是否可以增加。admin_address
- (MsgAddressInt) - 控制 Jetton 的智能合约地址。jetton_content
- cell - 根据 代币数据标准 #64 的数据。jetton_wallet_code
- cell - 该 jetton 的钱包代码。get_wallet_address(slice owner_address)
返回slice jetton_wallet_address
返回此所有者地址(MsgAddressInt)的 jetton 钱包地址(MsgAddressInt)。
TL-B 方案
nothing$0 {X:Type} = Maybe X;
just$1 {X:Type} value:X = Maybe X;
left$0 {X:Type} {Y:Type} value:X = Either X Y;
right$1 {X:Type} {Y:Type} value:Y = Either X Y;
var_uint$_ {n:#} len:(#< n) value:(uint (len * 8))
= VarUInteger n;
addr_none$00 = MsgAddressExt;
addr_extern$01 len:(### 9) external_address:(bits len)
= MsgAddressExt;
anycast_info$_ depth:(#<= 30) { depth >= 1 }
rewrite_pfx:(bits depth) = Anycast;
addr_std$10 anycast:(Maybe Anycast)
workchain_id:int8 address:bits256 = MsgAddressInt;
addr_var$11 anycast:(Maybe Anycast) addr_len:(### 9)
workchain_id:int32 address:(bits addr_len) = MsgAddressInt;
_ _:MsgAddressInt = MsgAddress;
_ _:MsgAddressExt = MsgAddress;
transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress
response_destination:MsgAddress custom_payload:(Maybe ^Cell)
forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
= InternalMsgBody;
transfer_notification query_id:uint64 amount:(VarUInteger 16)
sender:MsgAddress forward_payload:(Either Cell ^Cell)
= InternalMsgBody;
excesses query_id:uint64 = InternalMsgBody;
burn query_id:uint64 amount:(VarUInteger 16)
response_destination:MsgAddress custom_payload:(Maybe ^Cell)
= InternalMsgBody;
// ----- 标准未指定,但建议的内部消息格式
internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress
response_address:MsgAddress
forward_ton_amount:(VarUInteger 16)
forward_payload:(Either Cell ^Cell)
= InternalMsgBody;
burn_notification query_id:uint64 amount:(VarUInteger 16)
sender:MsgAddress response_destination:MsgAddress
= InternalMsgBody;
crc32('transfer query_id:uint64 amount:VarUInteger 16 destination:MsgAddress response_destination:MsgAddress custom_payload:Maybe ^Cell forward_ton_amount:VarUInteger 16 forward_payload:Either Cell ^Cell = InternalMsgBody') = 0x8f8a7ea5 & 0x7fffffff = 0xf8a7ea5
crc32('transfer_notification query_id:uint64 amount:VarUInteger 16 sender:MsgAddress forward_payload:Either Cell ^Cell = InternalMsgBody') = 0xf362d09c & 0x7fffffff = 0x7362d09c
crc32('excesses query_id:uint64 = InternalMsgBody') = 0x553276db | 0x80000000 = 0xd53276db
crc32('burn query_id:uint64 amount:VarUInteger 16 response_destination:MsgAddress custom_payload:Maybe ^Cell = InternalMsgBody') = 0x595f07bc & 0x7fffffff = 0x595f07bc
crc32('internal_transfer query_id:uint64 amount:VarUInteger 16 from:MsgAddress response_address:MsgAddress forward_ton_amount:VarUInteger 16 forward_payload:Either Cell ^Cell = InternalMsgBody') = 0x978d4519 & 0x7fffffff = 0x178d4519
crc32('burn_notification query_id:uint64 amount:VarUInteger 16 sender:MsgAddress response_destination:MsgAddress = InternalMsgBody') = 0x7bdd97de & 0x7fffffff = 0x7bdd97de
缺点
没有办法在链上获取实际钱包余额,因为当带有余额的消息到达时,钱包余额可能不准确。
理由和替代方案
分布式架构 “一个钱包 - 一个合约” 在 NFT 标准 中的 “理由” 段落中有详细描述。
先前的工作
未解决的问题
- 没有标准方法执行 “安全转移”,在合约执行失败的情况下回滚所有权转移。
未来的可能性
曾有一个想法实现 外部消息代币(由 EmelyanenkoK 提出)。
变更日志
2022年8月31日 - 添加了 forward_payload
格式。