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 撰寫。