一周前,我的 Solana 代币还只是随机的字符字符串。没有名称,没有规则,也没有个性。到这周结束时,我已经构建出了能自行收取费用的代币、完全无法移动的代币,以及一种同时既能完成任务又能提供便利的代币。以下是 Token Extensions 教给我的、任何 Web2 平台都无法教给我的东西。
我的起点
来自 Web2 背景,我对“代币”的心智模型基本上就是数据库中的一行,带有一个余额列。如果你想给这个代币起名字,你需要把它和另一张表关联起来。如果你想要交易手续费,你需要编写中间件,在请求到达数据库之前拦截转账请求。如果你想要某种永远无法转售的东西,你需要在应用程序代码中写一条 if 语句,并希望每个客户端都遵守它。
Solana 的 Token Extensions Program 彻底抛弃了这种模式。一个 mint 可以直接在账户上携带自己的名称和符号。它可以在没有任何自定义后端代码的情况下,对每笔转账强制收取手续费。它可以完全拒绝移动,而且这种拒绝发生在程序内部,而不是我自己写的代码里。我过去需要手动构建的每一条规则,现在都只是一个标志位。
实践过程
赋予代币身份
我的第一个真正的 mint 根本没有任何名称。它只是一个地址:GscCeGeQhmN92SLaXAHoWee78KKb5PBkTrkMiYdy2Nc8,在浏览器中查看它就像打开一个没有列标题的电子表格。添加元数据只需一条命令就能解决:
spl-token create-token \
--program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
--enable-metadata \
--decimals 9
spl-token initialize-metadata GscCeGeQhmN92SLaXAHoWee78KKb5PBkTrkMiYdy2Nc8 \
"ReinforceCoin" "RFC" \
"https://raw.githubusercontent.com/solana-developers/opos-asset/main/assets/CompressedCoil/metadata.json"
Enter fullscreen mode Exit fullscreen mode
用 Web2 的术语来说,这相当于在让任何用户赚取积分之前,先定义你的忠诚度计划的显示名称和图标。不同之处在于,这里名称与余额逻辑共存于同一个账户中。没有单独的元数据服务需要同步。
让协议为你收取费用
接下来,我创建了一个在创建时就内置了转账手续费的 mint:BzJPUdX7kfiXzowvXwNMQgpqCbae5XwbDRWxp92mwAgs,设置为 1%:
spl-token create-token \
--program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
--transfer-fee-basis-points 100 \
--transfer-fee-maximum-fee 5000
Enter fullscreen mode Exit fullscreen mode
我向第二个钱包发送了 100 个代币,预期它会收到 100 个。实际上它收到了 99 个。丢失的代币并没有消失,而是直接被扣留在接收方自己的账户上,他们无法触碰,等待提款权限将其提取出来:
spl-token withdraw-withheld-tokens CftRHxRFNNwKS5LbTZ6L6sqVtQ57LbmfDqEuKHzmWLxs \
4x3o6cotxU4fqrPtetdTmKgGkMK86pb4FfZm93i1TUJt
Enter fullscreen mode Exit fullscreen mode
我的余额从 900 变为 901。没有支付处理器,没有 webhook,没有对账任务。手续费收取是 mint 本身的属性。
在一个 mint 上叠加两种扩展
在分别理解了每种扩展之后,我将它们组合到了同一个 mint 上:9LqGwgZKcjBJ3ccUv4uftRWoNdKAive2u4fueonCKNA,命名为 ReinforceCoin,同时携带元数据和 2% 的转账手续费。转账 100 个代币后,接收方可支配余额为 98 个,2 个处于扣留状态,机制与之前完全相同,只是与名称和符号并行运行。添加元数据并没有改变手续费的行为,手续费也没有影响元数据的渲染。
构建一个拒绝移动的东西
本周最有趣的 mint 也是最简单的命令。添加一个标志位 --enable-non-transferable,就生成了一个协议不允许任何人移动的代币:
spl-token create-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --enable-non-transferable
Enter fullscreen mode Exit fullscreen mode
我铸造了 10 个这样的代币,然后故意尝试发送一些到第二个钱包,只为看它失败。拒绝直接来自程序:
Program log: Instruction: TransferChecked
Program log: Transfer is disabled for this mint
Program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb failed: custom program error: 0x25
Enter fullscreen mode Exit fullscreen mode
这就是规则与建议的区别。在 Web2 中,“此凭证不可转让”是服务条款中的一行,或者最多是 API 中的一个检查。而在这里,它被强制执行的方式与重力被强制执行的方式相同。没有人能编写出绕过它的客户端,因为拒绝发生在代币程序内部,而不是应用程序代码中。
让我惊讶的地方
我原本以为 non-transferable 意味着代币被完全冻结,在所有意义上都是永久的。但事实并非如此。燃烧它们仍然可以完美工作:
spl-token burn 2joYCLK6QTfQ1FwsKijU7dMn2CDM8r6YZ3asoVxGL61j 3 \
--program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
Enter fullscreen mode Exit fullscreen mode
我的余额从 10 降到 7,没有错误,没有阻力。这个区别立刻让我明白了。Non-transferable 并不意味着所有者失去了对代币的控制,而是代币失去了易手的能力。所有者仍然可以销毁他们持有的东西,只是无法将其交给别人。这一个细节使得 soulbound 模式真正适用于凭证和完成证书等场景,你希望持有者保留对自己资产的控制权,但又不能出售或交易它。
接下来
本周的每一种扩展都单独存在于一个 mint 上,或者只与另一种配对。接下来我想进一步探索,看看在账户大小和租金成本开始产生影响之前,究竟有多少种扩展可以实际叠加在同一个代币上,并开始研究真实应用如何读取链上配置,而不是依赖 CLI 来为我解码。
这篇文章是 #100DaysOfSolana 的一部分。欢迎随时关注或加入。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.