Appearance
TEP: 85 - SBT 合约
- TEP: 85
- 标题: SBT 合约
- 状态: 活跃
- 类型: 合约接口
- 作者: Oleg Baranov, Narek Abovyan, Kirill Emelyanenko, Oleg Andreev
- 创建日期: 2022年8月9日
- 取代: -
- 被取代: -
摘要
灵魂绑定代币(SBT)是一种特殊类型的 NFT,不能被转移。它包括可选的证书机制,由授权机构撤销,并提供链上所有权证明。持有人可以随时销毁其 SBT。
动机
有一种有用的代币类型,可以给予用户社会权限/角色或证书。例如,它可以被市场用来给 SBT 持有者提供折扣,或被大学用来颁发以 SBT 形式的认证证书。所有权证明机制允许轻松地向任何合约证明你是某个 SBT 的所有者。
规范
SBT 实现了 NFT 标准接口,但 transfer
应始终被拒绝。
1. prove_ownership
入站消息的 TL-B 方案:
prove_ownership#04ded148 query_id:uint64 dest:MsgAddress
forward_payload:^Cell with_content:Bool = InternalMsgBody;
query_id
- 任意请求号。
dest
- 目标合约的地址,SBT 的所有权应向其证明。
forward_payload
- 目标合约所需的任意数据。
with_content
- 如果为真,SBT 的内容单元将包含在发送给合约的消息中。
应被拒绝如果:
- 发送地址不是所有者地址。
否则应: 发送消息给 dest
合约,TL-B 方案如下:
ownership_proof#0524c7ae query_id:uint64 item_id:uint256 owner:MsgAddress
data:^Cell revoked_at:uint64 content:(Maybe ^Cell) = InternalMsgBody;
query_id
- 在 prove_ownership
中传递的请求号。
item_id
- NFT 的 ID。
owner
- 当前所有者地址。
data
- 在 prove_ownership
中传递的数据单元。
revoked_at
- SBT 被撤销的 Unix 时间,如果未被撤销则为 0。
content
- NFT 的内容,如果 prove_ownership
中的 with_content
为真,则传递。
2. request_owner
入站消息的 TL-B 方案:
request_owner#d0c3bfea query_id:uint64 dest:MsgAddress
forward_payload:^Cell with_content:Bool = InternalMsgBody;
query_id
- 任意请求号。
dest
- 目标合约的地址,SBT 的所有权应向其证明。
forward_payload
- 目标合约所需的任意数据。
with_content
- 如果为真,SBT 的内容单元将包含在发送给合约的消息中。
应:
发送消息给 dest
合约,TL-B 方案如下:
owner_info#0dd607e3 query_id:uint64 item_id:uint256 initiator:MsgAddress owner:MsgAddress
data:^Cell revoked_at:uint64 content:(Maybe ^Cell) = InternalMsgBody;
query_id
- 在 prove_ownership
中传递的请求号。
item_id
- NFT 的 ID。
initiator
- 请求发起者的地址。
owner
- 当前所有者地址。
data
- 在 prove_ownership
中传递的数据单元。
revoked_at
- SBT 被撤销的 Unix 时间,如果未被撤销则为 0。
content
- SBT 的内容,如果 request_owner
中的 with_content
为真,则传递。
3. destroy
内部消息的 TL-B 方案:
destroy#1f04537a query_id:uint64 = InternalMsgBody;
query_id
- 任意请求号。
应被拒绝如果:
- 发送地址不是所有者地址。
否则应:
- 将所有者地址和授权地址设为空。
- 向发送者发送消息,TL-B 方案为
excesses#d53276db query_id:uint64 = InternalMsgBody;
,将传递合约的余额。
4. revoke
入站消息的 TL-B 方案:
revoke#6f89f5e3 query_id:uint64 = InternalMsgBody;
query_id
- 任意请求号。
应被拒绝如果:
- 发送地址不是授权地址。
- 已经被撤销。
否则应: 将 revoked_at
设置为当前 Unix 时间。
GET 方法
get_nft_data()
- 与 NFT 标准 相同。get_authority_address()
- 返回授权地址的slice
。授权可以撤销 SBT。 此方法对于 SBT 是强制性的,如果没有授权应返回addr_none
(2 个零位)get_revoked_time()
- 返回撤销时间的int
,即被撤销的 Unix 时间。如果未被撤销则为 0。
实现示例
https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/sbt-item.fc
指南
铸造
可以使用基本的 NFT 集合进行铸造,SBT 应作为一个项目。在铸造消息中应传递授权地址,在内容之后。
在铸造之前,建议发行者检查钱包代码并确认它是标准化钱包,而不是可以转让给第三方的合约。
向合约证明所有权
SBT 合约具有一种功能,允许通过链上证明实现与合约的有趣机制。
你可以向 SBT 发送消息,它会将消息代理到目标合约,并在消息体中包含其索引、所有者地址和发起者地址,以及对合约有用的任何有效负载, 这样目标合约可以知道你是与预期集合相关的 SBT 的所有者。合约可以通过使用代码和索引计算 SBT 的地址,并将其与发送者进行比较来知道 SBT 是否与集合相关。
有两种方法可以使用此功能,所有权证明和所有权信息。 区别在于所有权证明只能由 SBT 所有者调用,因此在需要仅接受来自所有者的消息时(例如在 DAO 中的投票)优先使用。
所有权证明
SBT 所有者可以使用以下方案向 SBT 发送消息:
prove_ownership#04ded148 query_id:uint64 dest:MsgAddress
forward_payload:^Cell with_content:Bool = InternalMsgBody;
之后,SBT 会向 dest
发送转移消息,方案如下:
ownership_proof#0524c7ae query_id:uint64 item_id:uint256 owner:MsgAddress
data:^Cell revoked_at:uint64 content:(Maybe ^Cell)
所有权信息
任何人可以使用以下方案向 SBT 发送消息:
request_owner#d0c3bfea query_id:uint64 dest:MsgAddress
forward_payload:^Cell with_content:Bool = InternalMsgBody;
之后,SBT 会向 dest
发送转移消息,方案如下:
owner_info#0dd607e3 query_id:uint64 item_id:uint256 initiator:MsgAddress owner:MsgAddress
data:^Cell revoked_at:uint64 content:(Maybe ^Cell)
验证 SBT 合约示例
C
int op::ownership_proof() asm "0x0524c7ae PUSHINT";
int equal_slices (slice a, slice b) asm "SDEQ";
_ load_data() {
slice ds = get_data().begin_parse();
return (
ds~load_msg_addr(), ;; collection_addr
ds~load_ref() ;; sbt_code
);
}
slice calculate_sbt_address(slice collection_addr, cell sbt_item_code, int wc, int index) {
cell data = begin_cell().store_uint(index, 64).store_slice(collection_addr).end_cell();
cell state_init = begin_cell().store_uint(0, 2).store_dict(sbt_item_code).store_dict(data).store_uint(0, 1).end_cell();
return begin_cell().store_uint(4, 3)
.store_int(wc, 8)
.store_uint(cell_hash(state_init), 256)
.end_cell()
.begin_parse();
}
() recv_internal(int balance, int msg_value, cell in_msg_full, slice in_msg) impure {
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();
int op = in_msg~load_uint(32);
int query_id = in_msg~load_uint(64);
if (op == op::ownership_proof()) {
int id = in_msg~load_uint(256);
(slice collection_addr, cell sbt_code) = load_data();
throw_unless(403, equal_slices(sender_address, collection_addr.calculate_sbt_address(sbt_code, 0, id)));
slice owner_addr = in_msg~load_msg_addr();
cell payload = in_msg~load_ref();
int revoked_at = in_msg~load_uint(64);
throw_if(403, revoked_at > 0);
int with_content = in_msg~load_uint(1);
if (with_content != 0) {
cell sbt_content = in_msg~load_ref();
}
;;
;; sbt verified, do something
;;
return ();
}
throw(0xffff);
}
理由和替代方案
- 为什么这是在可能设计中最好的设计?
这个设计允许我们将 SBT 用作证书,具有撤销和链上证明功能,同时如果没有设置授权,也可以作为真正的 SBT。
- 考虑了哪些其他设计,为什么不选择它们?
最初考虑了类似 ETH 的设计,即地址绑定的代币,但它被扩展为有用的链上证明和撤销选项。
- 不这样做的影响是什么?
目前,TON 没有所有者绑定的代币标准,因此发行不能转让给第三方的代币是一个问题。因此,如果我们忽略这个或任何引入这种机制的类似标准,TON 可能会错过一些有趣和有前途的产品。
先前的工作
在 ETH 中(EIP-4973 ABT) - SBT 被做成一种完全不能在账户之间转移的 NFT。我们做了同样的事情,但扩展了逻辑,增加了链上证明和授权可以撤销的功能,因此 SBT 可以用作功能齐全的证书。
缺点
EIP-4973 ABT 具有装备/卸装机制,允许暂时显示/隐藏 SBT。在当前提案中,我们只能销毁 SBT。实际上不确定我们是否需要显示/隐藏逻辑。
未来的可能性
标准看起来已经定型。