NFT (non-fungible token) 作为一个正如其名字所指的“不可替代”的代币,非常适合用于作为一种身份认证工具。
接下来,让我们通过一个简单的例子,来探索一下使用 NFT 作为注册凭证的可行性。
前言
在开始之前,先让我来介绍接下来会用到的工具。
SPL Token
我们可以自己从零开始编写新的 Solana 合约,不过对于我们目前想要达到的目的来说,可以直接使用 Solana 提供的通用实现:Token Program。
Token Program 属于 Solana Program Library(SPL,https://spl.solana.com/)的一部分,SPL 中提供了包括 Token、Swap、Memo 的多个常用程序实现,并且提供了完善的客户端库、CLI 等工具,极大的方便了 Solana 开发者。
Token Program 的项目源码位于:https://github.com/solana-labs/solana-program-library/tree/master/token/program
Solana Playground
Solpy (https://beta.solpg.io/) 提供了一个线上编写和部署 Solana 合约的环境,并且默认包含了一些常用的工具,上一节介绍的 SPL Token 也在其中。可以让我们通过 spl-token-cli 方便的创建并管理 Token。
Auth Token
在这部分,我们会创建一个 NFT Token。如果用户 Mint 了 Token,那么就认为这个钱包地址已经在我们的系统中注册了,反之则提示用户先进行注册。
现在,我们先开始 On-chain 部分:
创建 Token
我们使用 spl-token 来创建一个新的 token,并且,通过「 --decimals 」来指定它是一个不可分割的 Token(就像 NFT 那样)
会输出下面的日志:
其中的 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE 经常会被称作 Mint Address,也是我们所创建的 Token 的 ID。
Token 地址为:
https://solscan.io/token/69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE?cluster=devnet
创建 Token Account
接下来我们需要为上一步创建的 Token 创建一个 Token Account。
mint
在跟其他钱包地址 mint 新 Token 之前,让我们先尝试一下为上一步创建出的 Token Account mint 一个 Token unit。只需要输入:
会输出下面的日志:
或者也可以:
也可以尝试一下 mint 其他数值,例如 1.9 :
查看交易详情,会发现:由于我们在第一步创建 Token 时指定「 --decimals 」为 0 ,所以实际执行 mint 时,会舍去小数部分,于是 mint 的量将依然是 1 。
也可以尝试一下直接给一个钱包地址 mint token,这里使用 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t 来演示:
为钱包地址 mint
上面的 mint 操作的目标是 Token Mint Address,而按照我们最初的设想,应该给其他不属于我们的钱包地址 Mint。
接下来,让我们使用 Web3 用户的钱包地址来完成上面的 mint 步骤。
Token 我们直接使用上面的 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
Wallet Address:使用 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t
但我们直接简单的替换参数的时,却会得到意外的结果:
地址是存在的,只是 Mint 所需要的地址并不是原始的钱包地址,而是需要与之关联的 Token Account。
我们需要进行与上面相同的流程:给钱包地址创建 Token Account,然后使用创建出的 Token Account mint 新的 Token unit。
换句话说,如果我们想要为某个钱包地址铸造一个 Token unit,那么我们就必须先为这个钱包地址创建一个 Token Account。至于为什么需要这样做,其中一个原因是我们并没有权限直接修改某个地址的数据。
在 Solana 的文档中,有时候你会分别看到两个相似的概念:代币账户(Token Account)和 关联代币账户(Associated Token Account,ATA)。文档中看起来两者似乎有些关联,但是却并没有说明这一点,这非常让人困惑。
不过如果你查看 Metaplex 的文档,就会发现它很明确的指出:“关联代币账户,有时会被简称为代币账户”。
我们这里不对这两者进行深入研究,只需要想象代币账户是代币和钱包地址之间的媒介。
我们使用下面的命令,为钱包地址创建一个 Token Account:
重复创建将会报错:
在日志中也能看出,由确定的 Mint Account 和 钱包地址派生出的 Token Account 是确定的(3JocyxV4LX4VbNU248CvNozZphgRW5JTyxn7FPWrF8bx),只是由于已经存在了,所以才打印了错误信息。
获取 Token Account
我们需要通过 RPC 接口,获取某个钱包地址是否有 Mint 过我们创建的 NFT。具体来说,通过「 getTokenAccountsByOwner 」方法来查询数据。下面是接口需要的参数:
你需要将「 _YOUR_RPC_PROVIDER_ 」替换为你自己选择的 RPC 供应商提供的地址。
可以使用 Solana 官方提供的地址,或者,可以在这里找到公共的免费 RPC 网络:https://zan.top/service/public-rpc/solana
注意:公共地址可能不稳定,如果需要稳定的 RPC 服务,建议创建自己的 API Key。
对于上面的钱包地址来说,具体就是这样:
除了通过代码手动填充请求参数之外,也可以使用 @solana/web3.js 中提供的 Connection 上的「 getParsedTokenAccountsByOwner 」方法,其内部实际上就是通过创建 Connection 时提供的 RPC 接口调用了「 getParsedTokenAccountsByOwner 」method。
如果是一个已经创建过 Account Token 的钱包,则会返回:
删掉了对我们无用的数据
实现
通过上面的尝试,可知我们能够使用现有的能力实现我们想要的功能。那么接下来,就开始编写客户端代码。
以下代码都在 https://github.com/gin-lsl/my-sol-token-auth-example
可以在这里预览:https://my-sol-token-auth-example.vercel.app/
我会通过创建一个简单的 Nextjs 项目来实现它,使用 Ant Design Web3 来 Connect Wallet:
初始化 Nextjs 项目
所有选项均使用默认值:
为了快速开始,我们直接使用 @ant-design/web3-solana 来连接钱包,使用 @solana/spl-token 和 Token Program 交互。
添加相关的依赖:
我们需要包括首页的 3 个页面,创建 app/sign-in/page.tsx 和 app/sign-on/page.tsx。它们分别用于连接钱包并检查用户是否已经注册(是否 mint NFT),以及让用户进行注册流程(mint NFT)。
打开演示页面后,首先看到的是欢迎语,以及前往 Sign in 页面的链接:
进入页面后,您需要先去 Sign in:
点击「Continue with Solana」,将会唤起钱包
而如果你之前并没有注册,则会提示你先去注册:
这是因为在 /api/sign-in 的逻辑中,会根据连接的钱包地址查找关联的 Token Account。由于我们之前并没有使用过,所以自然找不到数据,于是系统就会认为这个钱包地址并没有注册过。
然后我们按照提示,来到 Sign on 页面,注册页面与登录页面大体上类似,只是在服务端的处理逻辑不同:
其实可以将两个逻辑合并,这里分开只是为了便于演示。
无论如何,让我们点击「Start with Solana」,连接钱包。然后,如果顺利的话,会看到成功的提示:
让我们来到 Solscan 中,看看发生了什么。进入 https://solscan.io/?cluster=devnet,查询你的钱包地址。或者,也可以查看这个地址:79reVF46NyuuH7PADR3i6RpQ7hmBZgYkiieXNYPM1oLF
有一条交易数据:
注意在 Instructions 中,能看出交易内部执行了 CreateAccount 指令,点击链接进入详情,会发现它所创建的就是一个 TokenAccount:EXfDYkHw3UQw2VqiSLsRAfLMsxkgqnd3nhxbB4V5HAvA。其中的 isOnCurve 值为 False,表明是一个没有私钥的关联账户。
回到之前的页面,转到 Portfolio -> NFTs,就能看到我们刚才在 sign-on 内部所做的 Mint 操作,以及 Mint 的那个 NFT:
总结
让我们来总结一下整个流程。我们使用 spl-token-cli 创建了一个 NFT,然后,把一个钱包地址是否有 Token Account 并且 Mint 过 Token 来判断是否在我们的网站注册过。
当 Web3 用户连接钱包时,我们会自动往后端发送 sign-on,在内部会创建 Token Account,并且 Mint 一个 Token unit,作为用户已注册的凭证。
在以后,用户就可以拿同样的钱包地址再次登录我们的网站了。
本文由 ZAN Team(X 账号 @zan_team)的 gin-lsl 撰写。