Rust Client SDK

The official Rust client SDK for Lichen. Built on reqwest and tokio with strong typing and Result<T> error handling.

Two Rust SDKs — Lichen provides two separate Rust crates:

  • lichen-client-sdkThis page. For off-chain apps, bots, and tools that talk to a node via RPC (uses tokio + reqwest).
  • lichen-contract-sdk — For on-chain WASM smart contracts (no_std). See Contract Development.

Installation

toml
# Cargo.toml
[dependencies]
lichen-client-sdk = "0.1.1"
tokio = { version = "1", features = ["full"] }

Re-exports: lichen_client_sdk::{Client, ClientBuilder, Error, Result, Keypair, Pubkey, TransactionBuilder, Balance, Block, NetworkInfo, Transaction}

The published crate version is 0.1.1. The examples below use public testnet; switch to https://rpc.lichen.network only when targeting mainnet.

Quick Start

Rust
use lichen_client_sdk::{Client, Keypair, Pubkey, Result};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new("https://testnet-rpc.lichen.network");

    let kp = Keypair::new();
    println!("Address: {}", kp.pubkey());

    let balance = client.get_balance(&kp.pubkey()).await?;
    println!("Balance: {:.9} LICN ({} spores)", balance.licn(), balance.spores());

    let recipient = Pubkey::from_base58("11111111111111111111111111111112")?;
    let sig = client.transfer(&kp, &recipient, 1_000_000_000).await?;
    println!("Signature: {sig}");

    Ok(())
}

LichenID helper: use LichenIdClient for profile and metadata reads, skills, vouches, premium-name auctions, delegate lifecycle and delegated profile writes, recovery approvals, and skill attestation flows. It resolves the YID program via the symbol registry and handles the ABI byte layout for the wrapped LichenID methods.

SporePay helper: use SporePayClient for stream creation, stream/cliff reads, withdrawable balance checks, withdrawals, cancellations, transfers, and aggregated stats. Use raw call_contract() only for admin-only or newly added contract methods that are not wrapped yet.

LichenSwap helper: use LichenSwapClient for symbol-registry resolution, pool/quote/liquidity reads, TWAP and protocol-fee reads, aggregate stats, and the common create-pool, add-liquidity, and swap flows. Flash loans, remove_liquidity, get_reserves, and admin controls still use raw call_contract().

ThallLend helper: use ThallLendClient for symbol-registry resolution, account/protocol/interest reads, counter reads, aggregate stats, and the common deposit, withdraw, borrow, repay, and liquidate flows. Flash loans and admin controls stay on raw call_contract().

SporeVault helper: use SporeVaultClient for symbol-registry resolution, vault/user/strategy reads, aggregate stats, and the common deposit, withdraw, and harvest flow. Admin-only fee, cap, risk-tier, and strategy-management paths stay on raw call_contract().

BountyBoard helper: use BountyBoardClient for symbol-registry resolution, bounty reads (single + count + platform stats), aggregate stats, and the full bounty lifecycle: create, submit work, approve, and cancel. Admin-only identity-gate, token-address, fee, and pause controls stay on raw call_contract().

Rust
use lichen_client_sdk::{
    AddSkillParams, Client, Keypair, LichenIdAvailability, LichenIdClient, LichenSwapClient,
    RegisterIdentityParams, Result, SetAvailabilityParams, SetDelegateParams,
    SetMetadataParams, SporePayClient, SporeVaultClient, ThallLendClient,
    LICHENID_DELEGATE_PERM_PROFILE,
};

#[tokio::main]
async fn main() -> Result<()> {
    let client = Client::new("https://testnet-rpc.lichen.network");
    let lichen_id = LichenIdClient::new(client.clone());
    let lichenswap = LichenSwapClient::new(client.clone());
    let spore_pay = SporePayClient::new(client.clone());
    let spore_vault = SporeVaultClient::new(client.clone());
    let thalllend = ThallLendClient::new(client.clone());
    let keypair = Keypair::new();
    let delegate = Keypair::new();

    lichen_id.register_identity(&keypair, RegisterIdentityParams {
        agent_type: 5,
        name: "Infra Agent".into(),
    }).await?;

    lichen_id.add_skill(&keypair, AddSkillParams {
        name: "infra-ops".into(),
        proficiency: 93,
    }).await?;

    lichen_id.set_availability(&keypair, SetAvailabilityParams {
        status: LichenIdAvailability::Available,
    }).await?;

    lichen_id.set_metadata(&keypair, SetMetadataParams {
        metadata_json: r#"{"region":"sea","role":"infra"}"#.into(),
    }).await?;

    let profile = lichen_id.get_profile(&keypair.pubkey()).await?;
    lichen_id.set_delegate(&keypair, SetDelegateParams {
        delegate: delegate.pubkey(),
        permissions: LICHENID_DELEGATE_PERM_PROFILE,
        expires_at_ms: 1_700_000_000_000,
    }).await?;

    let delegation = lichen_id.get_delegate(&keypair.pubkey(), &delegate.pubkey()).await?;
    let pool = lichenswap.get_pool_info().await?;
    let quote = lichenswap.get_quote(1_000_000_000, true).await?;
    let swap_stats = lichenswap.get_stats().await?;
    let stream = spore_pay.get_stream_info(0).await?;
    let vault_stats = spore_vault.get_vault_stats().await?;
    let vault_position = spore_vault.get_user_position(&keypair.pubkey()).await?;
    let vault_summary = spore_vault.get_stats().await?;
    let lending_account = thalllend.get_account_info(&keypair.pubkey()).await?;
    let lending_stats = thalllend.get_stats().await?;
    println!("{:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?} {:?}", profile, delegation, pool, quote, swap_stats, stream, vault_stats, vault_position, vault_summary, lending_account, lending_stats);
    Ok(())
}

Client

Client::new(rpc_url: impl Into<String>) -> Client

Create a client pointing at the given JSON-RPC endpoint.

Rust
let client = Client::new("https://testnet-rpc.lichen.network");

Client::builder() -> ClientBuilder

Create a client with advanced options via the builder pattern.

Rust
let client = Client::builder()
    .rpc_url("https://testnet-rpc.lichen.network")
    .timeout(std::time::Duration::from_secs(30))
    .build()?;
Builder Method Type Description
rpc_url(url) &str Set RPC endpoint URL (required)
timeout(duration) Duration Set per-request timeout
build() Result<Client> Construct the client

Chain Queries

async get_slot(&self) -> Result<u64>

Get the current slot number.

Rust
let slot = client.get_slot().await?;
println!("Slot: {slot}");

async get_latest_block(&self) -> Result<Block>

Get the most recently produced block.

async get_block(&self, slot: u64) -> Result<Block>

Get a block by slot number.

async get_recent_blockhash(&self) -> Result<String>

Get a recent blockhash for signing transactions.

async get_metrics(&self) -> Result<Value>

Get chain performance metrics (TPS, total transactions/blocks, average block time).

async get_chain_status(&self) -> Result<Value>

Get comprehensive chain status.

async health(&self) -> Result<bool>

Liveness probe. Returns true when the RPC server reports {"status": "ok"}.

async get_network_info(&self) -> Result<NetworkInfo>

Get network information — chain ID, network ID, version, slot.

Rust
let info = client.get_network_info().await?;
println!("Chain: {} v{}", info.chain_id, info.version);

async get_peers(&self) -> Result<Value>

Get connected P2P peers.

async get_total_burned(&self) -> Result<Value>

Get total burned LICN — {"spores": ..., "licn": ...}.

Account Methods

async get_balance(&self, pubkey: &Pubkey) -> Result<Balance>

Get account balance. Returns a strongly-typed Balance.

Rust
let pubkey = Pubkey::from_base58("11111111111111111111111111111112")?;
let bal = client.get_balance(&pubkey).await?;
println!("{} spores  ({} LICN)", bal.spores(), bal.licn());

async get_account_info(&self, pubkey: &Pubkey) -> Result<Value>

Get enhanced account information with metadata.

async get_transaction_history(&self, pubkey: &Pubkey, limit: Option<u64>) -> Result<Value>

Get paginated transaction history for an account.

Transaction Methods

async send_transaction(&self, tx: &Transaction) -> Result<String>

Serialize a signed transaction into the wire envelope, base64-encode it, and submit it. Returns the signature string.

Rust
use lichen_client_sdk::{Hash, Instruction, SYSTEM_PROGRAM_ID, TransactionBuilder};

let recipient = Pubkey::from_base58("11111111111111111111111111111112")?;
let blockhash = Hash::from_hex(&client.get_recent_blockhash().await?)?;

let mut data = vec![0u8];
data.extend_from_slice(&1_000_000_000_u64.to_le_bytes());

let instruction = Instruction {
    program_id: SYSTEM_PROGRAM_ID,
    accounts: vec![kp.pubkey(), recipient],
    data,
};

let tx = TransactionBuilder::new()
    .add_instruction(instruction)
    .recent_blockhash(blockhash)
    .build_and_sign(&kp)?;

let sig = client.send_transaction(&tx).await?;

async send_raw_transaction(&self, raw_tx: &str) -> Result<String>

Submit a pre-serialized base64-encoded bincode transaction string.

async get_transaction(&self, signature: &str) -> Result<Value>

Look up a transaction by its signature hash.

async transfer(&self, from: &Keypair, to: &Pubkey, amount: u64) -> Result<String>

Build, sign, and send a native LICN transfer. Amount in spores (1 LICN = 1,000,000,000 spores).

Rust
let recipient = Pubkey::from_base58("RecipientAddr...")?;
let sig = client.transfer(&kp, &recipient, 5_000_000_000).await?; // 5 LICN

Validator & Staking

async get_validators(&self) -> Result<Vec<Value>>

Get all registered validators.

async get_validator_info(&self, pubkey: &Pubkey) -> Result<Value>

Get detailed validator information.

async get_validator_performance(&self, pubkey: &Pubkey) -> Result<Value>

Get validator performance metrics.

async stake(&self, staker: &Keypair, validator: &Pubkey, amount: u64) -> Result<String>

Stake LICN to a validator. Amount in spores.

Rust
let validator = Pubkey::from_base58("11111111111111111111111111111112")?;
let sig = client.stake(
    &kp,
    &validator,
    10_000_000_000  // 10 LICN
).await?;

async unstake(&self, staker: &Keypair, validator: &Pubkey, amount: u64) -> Result<String>

Unstake LICN from a validator.

async get_staking_status(&self, pubkey: &Pubkey) -> Result<Value>

Get staking status for an account.

async get_staking_rewards(&self, pubkey: &Pubkey) -> Result<Value>

Get accumulated staking rewards.

Contract Methods

async deploy_contract(&self, deployer: &Keypair, code: Vec<u8>, init_data: Vec<u8>) -> Result<String>

Deploy a WASM smart contract. Code must start with \0asm magic header and be under 512 KB.

Rust
let wasm = std::fs::read("my_contract.wasm")?;
let sig = client.deploy_contract(&kp, wasm, vec![]).await?;

async call_contract(&self, caller: &Keypair, contract: &Pubkey, function: &str, args: Vec<u8>, value: u64) -> Result<String>

Call a function on a deployed WASM contract. Optionally send LICN (spores) with the call.

Rust
let contract = Pubkey::from_base58("ContractAddr...")?;
let args = serde_json::to_vec(&serde_json::json!({"recipient": "Addr...", "amount": 100}))?;
let sig = client.call_contract(&kp, &contract, "transfer", args, 0).await?;

async upgrade_contract(&self, owner: &Keypair, contract: &Pubkey, code: Vec<u8>) -> Result<String>

Upgrade a deployed contract with new WASM bytecode (owner only).

async call_readonly_contract(&self, contract: &Pubkey, function: &str, args: Vec<u8>, from: Option<&Pubkey>) -> Result<ReadonlyContractResult>

Execute a read-only contract call with no state change. This is the transport primitive used by LichenSwapClient and the fallback path for helper gaps.

Rust
let entry = client.get_symbol_registry("LICHENSWAP").await?;
let program = Pubkey::from_base58(entry["program"].as_str().expect("registry program"))?;
let result = client.call_readonly_contract(&program, "get_pool_info", vec![], None).await?;

println!("{:?}", result);

async get_contract_info(&self, contract_id: &Pubkey) -> Result<Value>

Get information about a deployed smart contract.

async get_contract_logs(&self, contract_id: &Pubkey) -> Result<Value>

Get execution logs emitted by a contract.

async get_all_contracts(&self) -> Result<Value>

List all deployed contracts.

Program Methods

async get_program(&self, program_id: &Pubkey) -> Result<Value>

Get program information.

async get_symbol_registry(&self, symbol: &str) -> Result<Value>

Resolve a registry symbol such as YID, SPOREPAY, or LICHENSWAP to its deployed program entry.

Rust
let entry = client.get_symbol_registry("LICHENSWAP").await?;
println!("{}", entry["program"]);

async get_programs(&self) -> Result<Value>

List all deployed programs.

async get_program_stats(&self, program_id: &Pubkey) -> Result<Value>

Get execution statistics for a program.

async get_program_calls(&self, program_id: &Pubkey) -> Result<Value>

Get recent call history for a program.

async get_program_storage(&self, program_id: &Pubkey) -> Result<Value>

Get on-chain storage summary for a program.

async get_sporepay_stats(&self) -> Result<Value> / async get_lichenswap_stats(&self) -> Result<Value> / async get_thalllend_stats(&self) -> Result<Value> / async get_sporevault_stats(&self) -> Result<Value> / async get_bountyboard_stats(&self) -> Result<Value>

Fetch aggregated stats from the public SporePay, LichenSwap, ThallLend, SporeVault, and BountyBoard RPC routes. Helper clients wrap SporeVault, ThallLend, SporePay, and LichenSwap for typed access.

Rust
let sporepay_stats = client.get_sporepay_stats().await?;
let lichenswap_stats = client.get_lichenswap_stats().await?;
let thalllend_stats = client.get_thalllend_stats().await?;
let sporevault_stats = client.get_sporevault_stats().await?;
let bountyboard_stats = client.get_bountyboard_stats().await?;

println!("{:?} {:?} {:?} {:?} {:?}", sporepay_stats, lichenswap_stats, thalllend_stats, sporevault_stats, bountyboard_stats);

NFT Methods

async get_collection(&self, collection_id: &Pubkey) -> Result<Value>

Get NFT collection metadata.

async get_nft(&self, collection_id: &Pubkey, token_id: u64) -> Result<Value>

Get a specific NFT by collection and token ID.

async get_nfts_by_owner(&self, owner: &Pubkey) -> Result<Value>

Get all NFTs owned by an address.

async get_nfts_by_collection(&self, collection_id: &Pubkey) -> Result<Value>

Get all NFTs in a collection.

async get_nft_activity(&self, collection_id: &Pubkey, token_id: u64) -> Result<Value>

Get activity history for a specific NFT.

Keypair

ML-DSA-65 keypair wrapper around lichen_core::Keypair. Thread-safe and Clone-able.

Keypair::new() -> Keypair

Generate a new random keypair.

Rust
let kp = Keypair::new();
println!("Pubkey: {}", kp.pubkey());

Keypair::from_seed(seed: &[u8; 32]) -> Keypair

Derive a deterministic keypair from a 32-byte seed.

Rust
let seed = [0u8; 32];
let kp = Keypair::from_seed(&seed);

keypair.sign(message: &[u8]) -> PqSignature

Sign an arbitrary message. Returns a self-contained PQ signature.

keypair.pubkey() -> Pubkey

Get the 32-byte versioned native address.

keypair.to_seed() -> [u8; 32]

Extract the 32-byte seed.

keypair.inner() -> &lichen_core::Keypair

Access the underlying lichen_core::Keypair for advanced use cases.

TransactionBuilder

Builder pattern for constructing and signing transactions. All methods consume and return self for chaining.

TransactionBuilder::new() -> TransactionBuilder

Create an empty builder.

.add_instruction(instruction: Instruction) -> Self

Append an Instruction. Can be called multiple times.

.recent_blockhash(hash: Hash) -> Self

Set the recent blockhash as a parsed Hash.

.build_and_sign(keypair: &Keypair) -> Result<Transaction>

Sign the instructions and return a typed transaction ready to submit.

Rust
use lichen_client_sdk::{Hash, Instruction, SYSTEM_PROGRAM_ID};

let recipient = Pubkey::from_base58("11111111111111111111111111111112")?;
let blockhash = Hash::from_hex(&client.get_recent_blockhash().await?)?;

let mut data = vec![0u8];
data.extend_from_slice(&5_000_000_000_u64.to_le_bytes());

let instruction = Instruction {
    program_id: SYSTEM_PROGRAM_ID,
    accounts: vec![kp.pubkey(), recipient],
    data,
};

let tx = TransactionBuilder::new()
    .add_instruction(instruction)
    .recent_blockhash(blockhash)
    .build_and_sign(&kp)?;

let sig = client.send_transaction(&tx).await?;
println!("Transfer complete: {sig}");

Types

Balance

Strongly-typed balance with unit conversions.

Method / Constructor Returns Description
Balance::from_spores(n: u64) Balance Create from raw spore count
Balance::from_licn(n: &str) Result<Balance, BalanceParseError> Create from a decimal LICN string without floating-point rounding
balance.spores() u64 Get balance in spores
balance.licn() f64 Get balance in LICN
Rust
let b = Balance::from_licn("25.0").unwrap();
assert_eq!(b.spores(), 25_000_000_000);
assert_eq!(b.licn(), 25.0);

Block

Deserialized block data.

Rust
#[derive(Debug, Deserialize)]
pub struct Block {
    pub hash: String,
    pub parent_hash: String,
    pub slot: u64,
    pub state_root: String,
    pub timestamp: u64,
    pub transaction_count: u64,
    pub validator: String,
}

NetworkInfo

Deserialized network information.

Rust
#[derive(Debug, Deserialize)]
pub struct NetworkInfo {
    pub chain_id: String,
    pub current_slot: u64,
    pub network_id: String,
    pub peer_count: u64,
    pub validator_count: u64,
    pub version: String,
}

Error Handling

All client methods return lichen_client_sdk::Result<T>, which aliases std::result::Result<T, lichen_client_sdk::Error>. RPC errors surface with their JSON-RPC error payloads; parse and config issues use typed SDK error variants.

Rust
let pubkey = Pubkey::from_base58("11111111111111111111111111111112")?;

match client.get_balance(&pubkey).await {
    Ok(balance) => println!("{} LICN", balance.licn()),
    Err(e) => {
        let msg = e.to_string();
        if msg.contains("Account not found") {
            println!("Account does not exist yet");
        } else {
            eprintln!("RPC error: {e}");
        }
    }
}

Common error codes:

  • -32601 — Method not found
  • -32602 — Invalid params
  • -32003 — Admin auth required
  • -32005 — Rate limit exceeded

Tip: For production services, use the ClientBuilder with a custom timeout and wrap calls in a retry loop with exponential backoff for transient network errors.

WebSocket Coverage

The Rust client crate currently wraps HTTP JSON-RPC only. There is no WsClient implementation in sdk/rust/src today.

For real-time subscriptions, connect directly to wss://testnet-rpc.lichen.network/ws or wss://rpc.lichen.network/ws, or use the JavaScript and Python SDKs, which already expose high-level subscription helpers.