TEP: 74
概要
Jettons(TON 可替代代币)标准接口。
标准接口将极大简化不同代币化资产的交互和显示。
Jetton 标准描述了以下内容:
- Jetton 转账的方式。
- 检索给定 Jetton 资产的常见信息(名称、流通供应量等)的方式。
规范
在本标准中,用“Jetton”表示同类型代币的整体,用“jetton”表示某类型代币的数量。
Jettons 组织结构如下:每个 Jetton 都有一个主智能合约用于铸造新 jettons,记录流通供应量并提供常见信息。
同时,每个用户持有的 jettons 数量信息是以去中心化方式存储在各个用户的智能合约中,这些合约称为“jetton-wallets”(jetton 钱包)。
例如:如果你发行一个流通供应量为 200 jetton 的 Jetton,并且这些 jetton 被 3 个人持有,那么将部署 4 个合约:1 个 Jetton-master(Jetton 主合约)和 3 个 jetton-wallets(Jetton 钱包合约)。
Jetton 钱包智能合约
必须实现:
内部消息处理器
- transfer(转账)
- 请求
- TL-B(Telegram Language-Bitstream)架构的入站消息:
query_id
:任意请求编号。amount
:以基本单位表示的转账 jetton 数量。destination
:jetton 新持有者的地址。response_destination
:发送确认成功转账的响应地址,并返回剩余的 Toncoins。custom_payload
:可选的自定义数据(由发送方或接收方 jetton 钱包用于内部逻辑)。forward_ton_amount
:要发送到目标地址的 nanotons 数量。forward_payload
:要发送到目标地址的可选自定义数据。- 消息不是来自所有者。
- 发送方钱包中没有足够的 jettons。
- 没有足够的 TON(根据 jetton 自身存储费指南和操作成本)来处理操作,部署接收方的钱包并发送
forward_ton_amount
。 - 处理请求后,接收方的钱包必须至少发送
in_msg_value - forward_ton_amount - 2 * max_tx_gas_price - 2 * fwd_fee
到response_destination
地址。如果发送方的钱包无法保证这一点,必须立即停止执行请求并抛出错误。max_tx_gas_price
是 FT habitat 工作链的最大交易 gas 限制的 Toncoins 价格。在 basechain 中,它可以从 ConfigParam 21 中的gas_limit
字段获取。fwd_fee
是转账请求的转发费用,可以通过解析转账请求消息获得。 - 从发送方钱包中减少
amount
数量的 jetton 并发送消息以增加接收方钱包中的 jetton 数量(并可选地部署它)。 - 如果
forward_amount > 0
,确保接收方钱包发送带有forward_amount
nanotons 的消息到目标地址,并具有以下布局:TL-B 架构: query_id
应与请求的query_id
相同。amount
表示转账的 jetton 数量。sender
是转账 jetton 的前所有者地址。forward_payload
应与请求的forward_payload
相同。query_id
应与请求的query_id
相同。- 如果你想在
forward_payload
中发送简单的评论,则forward_payload
必须以0x00000000
(32 位无符号整数,等于零)开头,并且评论内容包含在forward_payload
的其余部分中。 - 如果评论不是以字节
0xff
开头,则评论为文本,可以“按原样”显示给钱包的最终用户(在过滤无效和控制字符并确保它是有效的 UTF-8 字符串后)。例如,用户可以在从他们的钱包转账到其他用户的钱包的简单转账中,在此文本字段中指出用途(“用于咖啡”)。 - 另一方面,如果评论以字节
0xff
开头,则剩余部分为“二进制评论”,不应作为文本显示给最终用户(仅在必要时显示为十六进制转储)。“二进制评论”的预期用途是,例如,包含商店支付的购买标识符,由商店软件自动生成和处理。 - 如果
forward_payload
包含与目标智能合约交互的二进制消息(例如与 DEX 的交互),则不需要前缀。
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;
应拒绝的情况:
操作要求:
否则应该:
transfer_notification#7362d09c query_id:uint64 amount:(VarUInteger 16) sender:MsgAddress forward_payload:(Either Cell ^Cell) = InternalMsgBody;
如果
forward_amount
为零,则不应发送通知消息。接收方的钱包应将所有多余的入站消息中的 coins 发送到
response_destination
,其布局如下:TL-B 架构:excesses#d53276db query_id:uint64 = InternalMsgBody;
forward_payload 格式
这些规则与从常规钱包发送 Toncoins 时的有效载荷格式相同(智能合约指南:内部消息,3)。
- burn(销毁)
- 请求
- TL-B 架构的入站消息:
query_id
:任意请求编号。amount
:销毁的 jetton 数量。response_destination
:发送确认成功销毁的响应地址,并返回剩余的 coins。custom_payload
:可选的自定义数据。- 消息不是来自所有者。
- 发送方钱包中没有足够的 jettons。
- 没有足够的 TONs 在处理请求后至少发送
in_msg_value - max_tx_gas_price
到response_destination
地址。如果发送方的钱包无法保证这一点,必须立即停止执行请求并抛出错误。 - 从销毁者钱包中减少
amount
数量的 jetton 并向 Jetton 主合约发送通知,告知销毁信息。 - Jetton 主合约应将所有多余的入站消息中的 coins 发送到
response_destination
,其布局如下:TL-B 架构: query_id
应与请求的query_id
相同。
burn#595f07bc query_id:uint64 amount:(VarUInteger 16) response_destination:MsgAddress custom_payload:(Maybe ^Cell) = InternalMsgBody;
应拒绝的情况:
否则应该:
excesses#d53276db query_id:uint64 = InternalMsgBody;
Get-methods(查询方法)
get_wallet_data()
返回(int balance, slice owner, slice jetton, cell jetton_wallet_code)
:balance
:(uint256)钱包中的 jetton 数量。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
-(整数)- 发行的 jetton 总数量。mintable
-(-1/0)- 标志,指示 jetton 的数量是否可以增加。admin_address
-(MsgAddressInt)- 控制 Jetton 的智能合约地址。jetton_content
- cell - 根据 Token Data Standard #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
缺点
无法在链上获取实际钱包余额,因为当余额消息到达时,钱包余额可能不再是最新的。