一週前,我的 Solana 代幣還只是隨機的字串,沒有名稱、沒有規則、也沒有個性。到了週末,我已經打造出能自己收取手續費的代幣、不允許移動的代幣,以及一種同時兼具工作與便利性的代幣。以下是 Token Extensions 教會我的事,是任何 Web2 平台都無法做到的。
起點
出身 Web2 的我,腦中的「代幣」模型基本上就是資料庫裡帶有餘額欄位的一列。如果你想要給代幣命名,就要再 JOIN 另一張表;如果你想要收交易手續費,就要在資料庫層級之前寫 middleware 攔截請求;如果你想要讓代幣永遠無法轉售,就要在應用程式碼裡寫 if 判斷,並希望所有客戶端都遵守。
Solana 的 Token Extensions Program 把這套模型徹底拋棄。一個 mint 可以直接把名稱與符號存在帳戶本身。它可以強制每筆轉帳都收取手續費,不需要一行自訂後端程式碼。它可以拒絕轉移,而且這拒絕發生在程式內部,而不是我寫的應用程式碼裡。過去我用手打造的每條規則,現在都變成了一個旗標。
逐步實作
給代幣一個身分
我第一個真正的 mint 根本沒有名稱。它只是一個地址 GscCeGeQhmN92SLaXAHoWee78KKb5PBkTrkMiYdy2Nc8,在區塊鏈瀏覽器上看起來就像打開一份沒有欄位標題的試算表。加了 metadata 之後,只用一行指令就搞定了:
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 的說法,這就像在讓任何人累積點數之前,先定義你的會員方案顯示名稱與圖示。差別在於,這裡的名稱與餘額邏輯共存在同一個帳戶,沒有額外的 metadata 服務需要同步。
讓協議幫你收手續費
接著我建立了一個 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 上
當我分別理解每種擴充功能之後,我把 metadata 與 2% 轉帳手續費同時放在同一個 mint 上,9LqGwgZKcjBJ3ccUv4uftRWoNdKAive2u4fueonCKNA,名為 ReinforceCoin。轉 100 個代幣,收款方可動用餘額會剩 98 個,另外 2 個被扣留在保留狀態,機制與之前相同,只是現在同時擁有名稱與符號。加入 metadata 並沒有改變手續費的行為,手續費也沒有影響 metadata 的呈現。
打造一個拒絕移動的代幣
這週最有趣的 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.