Web3 新手系列:探索使用 Solana Token 登录
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 那样)
spl-token create-token --decimals 0
会输出下面的日志:
$ spl-token create-token --decimals 0
Creating token 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
Address: 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
Decimals: 0
Signature: Rih6V8eFd2sakwH4HKmFiT5c2KKFYv74MYf9uVrSaYgK8wm5txUjsNVqEwLvfrGUxMCeLguj16GGssLBDKW1nWC
其中的 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE 经常会被称作 Mint Address,也是我们所创建的 Token 的 ID。
Token 地址为:
https://solscan.io/token/69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE?cluster=devnet
创建 Token Account
接下来我们需要为上一步创建的 Token 创建一个 Token Account。
$ spl-token create-account 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
Creating account 5aYfSTyPL4U4cEZgoMrUWvCJwLv5XYSqVD56R6KXg3Ab
Signature: 3QQvGRmpQSPPYQDVAxurGszgorffb3WdxZ2vkapnxWRhDf9sCQ3baFi5CmAorsfZYFUnexvA9h6c37EifXDgeNSm
mint
在跟其他钱包地址 mint 新 Token 之前,让我们先尝试一下为上一步创建出的 Token Account mint 一个 Token unit。只需要输入:
$ spl-token mint 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE 1 5aYfSTyPL4U4cEZgoMrUWvCJwLv5XYSqVD56R6KXg3Ab
会输出下面的日志:
$ spl-token mint 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE 1 5aYfSTyPL4U4cEZgoMrUWvCJwLv5XYSqVD56R6KXg3Ab
Minting 1 tokens
Token: 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
Recipient: 5aYfSTyPL4U4cEZgoMrUWvCJwLv5XYSqVD56R6KXg3Ab
Signature: dgZdk963RFK1hhBJLxdk6YQ58uXa3Wd9zGJuAcbvwP7CQryTsRJKzmK7XgZFgeVd78M5Xo2HmFqrcsjBVeeg9BM
或者也可以:
$ spl-token mint 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE 1
Minting 1 tokens
Token: 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
Recipient: 5aYfSTyPL4U4cEZgoMrUWvCJwLv5XYSqVD56R6KXg3Ab
Signature: 2QYAWdq1zybgPd52ASV2RXPzoKTF9e1kaEawh5pa4MAtXAmsZTQmoxMbYmUM1ui3YNRk8s4T1fFthguhJjKE3pmK
也可以尝试一下 mint 其他数值,例如 1.9:
$ spl-token mint 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE 1.9 5aYfSTyPL4U4cEZgoMrUWvCJwLv5XYSqVD56R6KXg3Ab
Minting 1.9 tokens
Token: 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
Recipient: 5aYfSTyPL4U4cEZgoMrUWvCJwLv5XYSqVD56R6KXg3Ab
Signature: 57jShkLTWHnV4G3RENDbarFCjRkWrP3TGnQcSwyRCFEJEoR2BJYwquNHQTLuqEdDp5wtoAujWYVZupkS3TLgv9Hp
查看交易详情,会发现:由于我们在第一步创建 Token 时指定 --decimals
为 0,所以实际执行 mint 时,会舍去小数部分,于是 mint 的量将依然是 1
也可以尝试一下直接给一个钱包地址 mint token,这里使用 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t 来演示:
spl-token mint 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE 1 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t
为钱包地址 mint
上面的 mint 操作的目标是 Token Mint Address,而按照我们最初的设想,应该给其他不属于我们的钱包地址 Mint。
接下来,让我们使用 Web3 用户的钱包地址来完成上面的 mint 步骤。
- Token 我们直接使用上面的 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
- Wallet Address:使用 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t
但我们直接简单的替换参数的时,却会得到意外的结果:
$ spl-token mint 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE 1 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t
Minting 1 tokens
Token: 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE
Recipient: 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t
Process error: Client error: AccountNotFound: pubkey=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:
$ spl-token create-account 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE --owner 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t
Creating account 3JocyxV4LX4VbNU248CvNozZphgRW5JTyxn7FPWrF8bx
Signature: 2ooUu2o1GiPMPFLR7XpLVEHxvV3b4FiBDfXR48DcL2WxuUbEEudddqdNFeLdTVTqnj3jUpqXP7TpsjhGH9fKKPw5
重复创建将会报错:
$ spl-token create-account 69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE --owner 4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t
Creating account 3JocyxV4LX4VbNU248CvNozZphgRW5JTyxn7FPWrF8bx
Process error: Error: Account already exists: 3JocyxV4LX4VbNU248CvNozZphgRW5JTyxn7FPWrF8bx
在日志中也能看出,由确定的 Mint Account 和 钱包地址派生出的 Token Account 是确定的(3JocyxV4LX4VbNU248CvNozZphgRW5JTyxn7FPWrF8bx),只是由于已经存在了,所以才打印了错误信息。
获取 Token Account
我们需要通过 RPC 接口,获取某个钱包地址是否有 Mint 过我们创建的 NFT。具体来说,通过 getTokenAccountsByOwner
方法来查询数据。下面是接口需要的参数:
curl --request POST \
--url __YOUR_RPC_PROVIDER__ \
--header 'content-type: application/json' \
--header 'user-agent: vscode-restclient' \
--data '
{
"id": 1,
"jsonrpc": "2.0",
"method": "getTokenAccountsByOwner",
"params": [
__WALLET_ADDRESS__,
{
"mint": __TOKEN_MINT_ADDRESS__
},
{
"encoding": "jsonParsed",
"commitment": "recent"
}
]
}
'
你需要将 _YOUR_RPC_PROVIDER_
替换为你自己选择的 RPC 供应商提供的地址。
可以使用 Solana 官方提供的地址,或者,可以在这里找到公共的免费 RPC 网络:
https://zan.top/service/public-rpc/solana
注意:公共地址可能不稳定,如果需要稳定的 RPC 服务,建议创建自己的 API Key。
对于上面的钱包地址来说,具体就是这样:
curl --request POST \
--url 'https://zan.top/service/public-rpc/solana' \
--header 'content-type: application/json' \
--header 'user-agent: vscode-restclient' \
--data '{"id": 1,"jsonrpc": "2.0","method": "getTokenAccountsByOwner","params": ["4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t",{"mint": "69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE"},{"encoding": "jsonParsed","commitment": "recent"}]}'
除了通过代码手动填充请求参数之外,也可以使用 @solana/web3.js 中提供的 Connection 上的 getParsedTokenAccountsByOwner
方法,其内部实际上就是通过创建 Connection 时提供的 RPC 接口调用了 getParsedTokenAccountsByOwner
method。
如果是一个已经创建过 Account Token 的钱包,则会返回:
{
"result": {
"value": [
{
"account": {
"data": {
"parsed": {
"info": {
"mint": "69yXraTu3FqXZkATg6MiRnWT2qHd4tRzWsfCHHE9j2XE",
"owner": "4wztJ4CAH4GbAUopZrVk7nLvoAC3KAF6ttMMWfnBRG1t",
"tokenAmount": {
"amount": "1"
}
},
},
},
},
"pubkey": "3JocyxV4LX4VbNU248CvNozZphgRW5JTyxn7FPWrF8bx"
}
]
}
}
删掉了对我们无用的数据
实现
通过上面的尝试,可知我们能够使用现有的能力实现我们想要的功能。那么接下来,就开始编写客户端代码。
我会通过创建一个简单的 Nextjs 项目来实现它,使用 Ant Design Web3 来 Connect Wallet:
初始化 Nextjs 项目
$ npx create-next-app@latest my-sol-token-auth-example
所有选项均使用默认值:
为了快速开始,我们直接使用 @ant-design/web3-solana 来连接钱包,使用 @solana/spl-token 和 Token Program 交互。
添加相关的依赖:
npm i antd @ant-design/web3 @ant-design/web3-solana @solana/web3.js @solana/spl-token
我们需要包括首页的 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
ZAN 是蚂蚁数科旗下 Web3 科技品牌,致力于 Web3 应用优化--降低成本、增强安全和提升性能,围绕 Web3 应用全生命周期,提供可靠、稳定安全、定制化的产品和服务。依托 AntChain OpenLabs 的 TrustBase 开源开放技术体系,ZAN 拥有 Web3 领域独特的优势和创新能力,为 Web3 社区的区块链应用开发、企业和开发者的 Web3 应用提供了全面的技术产品和服务,其中包括节点服务(ZAN Node Service)、zk 加速(ZAN PowerZebra)、身份验证eKYC(ZAN Identity)以及智能合约审计(ZAN Smart Contract Review)等。
联系我们
Website | X | Discord | Telegram