SPL
的全稱是 Solana Program Library
,它是在 Solana 鏈上運行的一系列程式。你可以把它想像成官方寫好的一些合約,讓開發者可以輕鬆調用功能。你可以參考這篇文章詳細了解這個概念。
今天我們會來體驗一下這個系列中的 Token Program
,我們將會透過 Rust
製作 Cli 介面,讓我們可以發行自己的 Token
。你可以在這裡參考這篇文章的範例程式碼。
初步認識 Solana
Solana 是一個開源的區塊鏈項目,它實現了高性能的抗審查區塊鏈,提供各種去中心化金融 (DeFi) 的解決方案。Solana 每秒交易量超過 5 萬筆,平均出塊時間約 2 秒(相較於以太坊每秒 15 筆,幣安智能幣約 100 筆)。由於金融交易的速度與安全需求,這些創新的速度讓 Solana 鏈快速被許多 Defi 以及區塊鏈項目喜愛。
Solana 帶來的重要創新之一,就是由 Anatoly Yakovenko 開發的歷史證明 (PoH) 共識機制。這個概念讓該 Solana 擁有極大的可規模性。
Solana 協定的目的,在於同時服務小型用戶與企業顧客。該協定的設計目標,就是要在降低交易成本的同時,仍然確保可規模性與高速處理。
SOL 是 Solana 的原生 Token,目前有兩種功能:
- 支付 Solana 的交易,智能合約的執行手續費
- 抵押成為節點驗證交易獲得獎勵。
你可以在以下的網站查閱更多資訊:
環境準備
讓我們來準備一下開發環境。
首先,請按以下步驟安裝 Rust
:
brew install rustup
rustup-init
- 利用
rustc --version
來驗證是否安裝成功 - 如果沒有找到
rustc
,請嘗試設置環境變數export PATH="$HOME/.cargo/bin:$PATH"
- 如果不使用
Homebrew
,可以直接透過Rust
官網安裝
然後,讓我們安裝 Solana 開發環境。Solana 目前有三種網路,分別是:
- Mainnet:
https://api.mainnet-beta.solana.com
- Testnet:
https://api.testnet.solana.com
- Devnet:
https://api.devnet.solana.com
我們也可以在本機上安裝自己的節點,這樣在開發測試上就會方便許多。如果只是想嚐鮮的讀者,可以考慮直接使用 Devnet
完成教學。而如果你是想深入 Solana
開發的話,就可以使用本地節點方便開發。
無論是使用本地節點或 Devnet
,都需要安裝 Solana 開發環境節點與 Solana Tool。以下內容會以 Mac
為例子,其他平台的朋友請參考安裝指南。簡單來說,我們要先在 Terminal
上輸入指令:
bash sh -c "$(curl -sSfL https://release.solana.com/v1.7.10/install)"
安裝完成後會出現安裝位置,然後我們需要手動設置環境變數。比如說,如果安裝完後出現:
downloading v1.7.10 installer
Configuration: /home/solana/.config/solana/install/config.yml
Active release directory: /home/solana/.local/share/solana/install/active_release
* Release version: v1.7.10
* Release URL: https://github.com/solana-labs/solana/releases/download/v1.7.10/solana-release-x86_64-unknown-linux-gnu.tar.bz2
Update successful
我們就要設置 PATH="/home/solana/.local/share/solana/install/active_release/bin:${PATH}"
這樣的環境變數。
設置完成後,可以輸入 solana --version
來驗證是否安裝成功。
接下來,讓我們檢查一下機器上的錢包。我們應該會得到一個錢包地址 solana address
,這就是我們本地機器上的錢包,所有在機器上進行的區塊鏈交易,我們都會使用本地機器錢包來進行交易。
solana address
發生錯誤,請使用 solana config get
確認設定,其中 Keypair Path
就是本地錢包位置。如果沒有此設定,可以使用 solana-keygen new
建立。最後是 cargo
,如同所有語言平台一樣,Rust
也有自己的套件依賴管理。cargo
之於 Rust
,就如同 npm
之於 node.js
、cocoapods
之於 Swift
一樣。我們將透過 cargo
來建立一個 Rust
專案:
1. 安裝專案產生器 cargo install cargo-generate
2. 創建專案 cargo new spl-token
3. 用你習慣的 IDE
開啟資料夾
我們可以看到目錄結構中有:
src
:這是撰寫程式原始碼的地方Cargo.toml
,Cargo.lock
如同Podfile
,Podfile.lock
守護著我們的套件依賴管理。
安裝依賴
在開始之前,我們需要先安裝將會使用到的依賴。讓我們打開 Cargo.toml
,在當中加入以下的 dependencies
:
[dependencies]
structopt = "0.3.21"
indicatif = "0.16.2"
log = "0.4.14"
env_logger = "0.9.0"
solana-sdk = "1.6.10"
solana-client = "1.6.10"
solana-cli-config = "1.6.10"
spl-token = { version = "3.1.0", features = ["no-entrypoint"] }
[dev-dependencies]
assert_cmd = "2.0.0"
predicates = "2.0.1"
tempfile = "3.2.0"
如下所示:
來介紹一下這些依賴的用途:
structopt
:可以方便地將命令行解析成一個Struct
,這對於我們Cli
的結構上很便利。indicatif
:可以幫助Cli
顯示進度條資訊,你可以在這裡看看範例。log
:可以印出日誌env_logger
:可以加入環境設定來顯示日誌solana_sdk
:可以操作Solana
區塊鏈的API
接口solana-client
solana-cli-config
spl-token
:可以操作Solana Program Library
撰寫原始碼
接著,讓我們在 src
內創建一個名為 create_spl_token
的資料夾,並且新增兩個檔案:
// /src/create_spl_token/mod.rs
use solana_sdk::{
message::Message,
program_pack::Pack,
pubkey::Pubkey,
signature::{Keypair, Signer},
system_instruction::create_account,
transaction::Transaction,
};
use spl_token::instruction::initialize_account;
use spl_token::instruction::initialize_mint;
mod utils;
// If you want to use spl-token library and rust code to create SPL Token, please refer to the following code.
pub fn main(decimals: u8) {
let my_keypair = utils::load_config_keypair();
let my_pubkey = my_keypair.pubkey();
let token_account_size = spl_token::state::Mint::LEN;
let rpc_client = utils::new_rpc_client();
let token_balance = rpc_client
.get_minimum_balance_for_rent_exemption(token_account_size)
.expect("failed to get min balance");
let new_account_keypair = Keypair::new(); // New random keypair
let new_account_pubkey = new_account_keypair.pubkey();
// 創建帳戶交易
let create_account_instruction = create_account(
&my_pubkey,
&new_account_pubkey,
token_balance,
token_account_size as u64,
&spl_token::ID,
);
// mint token 交易
let initialize_mint_instruction = initialize_mint(
&spl_token::ID,
&new_account_pubkey,
&my_pubkey,
None,
decimals,
)
.unwrap();
// 發送交易
let rpc_client = utils::new_rpc_client();
let (recent_blockhash, _fee_calculator) = rpc_client
.get_recent_blockhash()
.expect("failed to get recent blockhash");
let tx = Transaction::new(
&[&my_keypair, &new_account_keypair],
Message::new(
&[create_account_instruction, initialize_mint_instruction],
Some(&my_pubkey),
),
recent_blockhash,
);
rpc_client
.send_and_confirm_transaction_with_spinner(&tx)
.expect("tx failed");
println!("Created Token Mint: {}", new_account_pubkey);
println!("Transaction Signature: {}", utils::tx_signature(&tx));
}
// /src/create_spl_token/utils.rs
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig,
signature::{read_keypair_file, Keypair},
transaction::Transaction,
};
pub fn tx_signature(tx: &Transaction) -> String {
tx.signatures
.first()
.expect("transaction not signed")
.to_string()
}
pub fn load_config_keypair() -> Keypair {
let config_path = solana_cli_config::CONFIG_FILE.as_ref().unwrap();
let cli_config =
solana_cli_config::Config::load(config_path).expect("failed to load config file");
read_keypair_file(cli_config.keypair_path).expect("failed to load keypair")
}
pub fn new_rpc_client() -> RpcClient {
let config_path = solana_cli_config::CONFIG_FILE.as_ref().unwrap();
let cli_config =
solana_cli_config::Config::load(config_path).expect("failed to load config file");
RpcClient::new_with_commitment(cli_config.json_rpc_url, CommitmentConfig::confirmed())
}
這個主要的程式碼很簡單,我們先創建 Token
的 Account
,然後再創建 mint
我們的 Token
,最後發送交易。
這邊我們需要先釐清 SLP Token
的一些機制。這 Token
部分與其他區塊鏈有點不同。在其他區塊鏈上,比如是以太坊 ETH
,它的 Token
最常見標準是 ERC20
,在轉帳的時候,其實 ETH address
與 ERC20 Token
的地址基本上是一樣的,就如下圖所示:
但在 Solana
鏈上,每個 SPL Token
都有自己的 Token Account
,每個地址底下可以有多個 SPL Token Account
,有點像是在銀行中又開立了外幣存款那種感覺:
接下來,我們要了解一些 Token 的基本操作,大概可以分成下列幾類:
transfer
:Token 的轉帳操作mint
:Token 的鑄造burn
:Token 的銷毀authority
:操作 Token 的授權者
如果想深入了解 SPL Token 的機制,可以參考這篇文章。
要創建一個 Token,流程就會是:創建 Token -> 鑄造 Token。感覺有點像是發行美元(定義),然後印製美元(供應量)一樣。
好的!這邊完成之後,我們要到 src/main
來修改一下程式碼,讓我們可以呼叫指令。
// /src/main.rs #[macro_use] extern crate log; extern crate env_logger; use env_logger::{Builder, Target}; use log::{debug, error, info, warn}; use std::env; use structopt::StructOpt; use std::num::ParseIntError; mod create_spl_token; // 入口 fn main() { let mut builder = Builder::from_default_env(); builder.target(Target::Stdout); builder.init(); let args = Cli::from_args(); // 分析指令,並且執行 match args.cmd { Command::SPL(value) => match value.spl_operating { SPLOperating::CreateToken(info) => create_spl_token::main(info.decimals), }, } info!("success!"); } #[derive(Debug, StructOpt)] struct Cli { #[structopt(subcommand)] // Note that we mark a field as a subcommand cmd: Command, } #[derive(Debug, StructOpt)] enum Command { /// SPL SPL(SPL), } #[derive(Debug, StructOpt)] struct SPL { /// SPL Operating #[structopt(subcommand)] // Note that we mark a field as a subcommand spl_operating: SPLOperating, } // subsubcommand! #[derive(Debug, StructOpt)] enum SPLOperating { /// Create Token CreateToken(CreateToken), } #[derive(Debug, StructOpt)] struct CreateToken { /// decimals #[structopt(short, default_value = "6", parse(try_from_str = parse_hex))] decimals: u8, } fn parse_hex(src: &str) -> Result<u8, ParseIntError> { u8::from_str_radix(src, 16) }
這邊的程式碼很簡單,我們製作了一個 Cli 的入口
,然後分析指令,並且執行我們剛剛寫好的 create_spl_token
。
我們可以在terminal
嘗試執行一下指令。讓我們邊把 Solana
節點設定指向Devnet
。solana config set --url https://api.devnet.solana.com
應該會看到一段輸出,顯示目前的 RPC URL: https://devnet.solana.com
。
接著我們在專案內執行。RUST_LOG=debug cargo run spl create-token
接著應該會看到一段輸出。
然後我們把 Created Token Mint: 6uze7gNUwRSEARX49k8oiF3FdZXXXXXXXXXXX
中的地址,貼到區塊鏈遊覽器查詢。記得我們要使用Devnet
。
恭喜!到這裡我們已經完成透過 Rust
製作一個 Cli
介面,並且發行 SPL Token
的工作了!
總結
我們透過了Rust
製作一個 Cli
介面,並且發行 SPL Token
,在其中我們了解了 Solana
鏈上的 Token
機制。了解 Token
的基本生命週期,以及 Token Account
的機制。所有的程式碼都可以在這邊找到,祝你有個美好的 Coding
夜晚,我們下次見。當然如果你有任何問題,歡迎在任何地方與我聯絡!