透過 Rust 製作 Cli 介面 在 Solana 創建屬於自己的 Token


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,目前有兩種功能:

  1. 支付 Solana 的交易,智能合約的執行手續費
  2. 抵押成為節點驗證交易獲得獎勵。

備註:智能合約是一種電腦間的交易協定,區塊鏈上的所有使用者都可以看到基於區塊鏈的智能合約。

你可以在以下的網站查閱更多資訊:

環境準備

讓我們來準備一下開發環境。

首先,請按以下步驟安裝 Rust

  1. brew install rustup
  2. rustup-init
  3. 利用 rustc --version 來驗證是否安裝成功
  4. 如果沒有找到 rustc,請嘗試設置環境變數 export PATH="$HOME/.cargo/bin:$PATH"
  5. 如果不使用 Homebrew,可以直接透過 Rust 官網安裝

然後,讓我們安裝 Solana 開發環境。Solana 目前有三種網路,分別是:

  1. Mainnet:https://api.mainnet-beta.solana.com
  2. Testnet:https://api.testnet.solana.com
  3. Devnet:https://api.devnet.solana.com

我們也可以在本機上安裝自己的節點,這樣在開發測試上就會方便許多。如果只是想嚐鮮的讀者,可以考慮直接使用 Devnet 完成教學。而如果你是想深入 Solana 開發的話,就可以使用本地節點方便開發。

無論是使用本地節點或 Devnet,都需要安裝 Solana 開發環境節點與 Solana Tool。以下內容會以 Mac 為例子,其他平台的朋友請參考安裝指南。簡單來說,我們要先在 Terminal 上輸入指令:

<code>bash sh -c "$(curl -sSfL https://release.solana.com/v1.7.10/install)"</code>

安裝完成後會出現安裝位置,然後我們需要手動設置環境變數。比如說,如果安裝完後出現:

	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.jscocoapods 之於 Swift 一樣。我們將透過 cargo 來建立一個 Rust 專案:

1. 安裝專案產生器 cargo install cargo-generate

2. 創建專案 cargo new spl-token

3. 用你習慣的 IDE 開啟資料夾

我們可以看到目錄結構中有:

  • src:這是撰寫程式原始碼的地方
  • Cargo.tomlCargo.lock 如同 PodfilePodfile.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())
}

這個主要的程式碼很簡單,我們先創建 TokenAccount,然後再創建 mint 我們的 Token,最後發送交易。

這邊我們需要先釐清 SLP Token 的一些機制。這 Token 部分與其他區塊鏈有點不同。在其他區塊鏈上,比如是以太坊 ETH,它的 Token 最常見標準是 ERC20,在轉帳的時候,其實 ETH addressERC20 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 節點設定指向Devnetsolana 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 夜晚,我們下次見。當然如果你有任何問題,歡迎在任何地方與我聯絡!


自認為終身學習者,對多領域都有濃厚興趣,喜歡探討各種事物。目前專職軟體開發,系統架構設計,企業解決方案。最喜歡 iOS with Swift。

blog comments powered by Disqus
Shares
Share This