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-sdk— This page. For off-chain apps, bots, and tools that talk to a node via RPC (usestokio+reqwest).lichen-contract-sdk— For on-chain WASM smart contracts (no_std). See Contract Development.
Installation
# 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
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().
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.
let client = Client::new("https://testnet-rpc.lichen.network");
Client::builder() -> ClientBuilder
Create a client with advanced options via the builder pattern.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
let kp = Keypair::new();
println!("Pubkey: {}", kp.pubkey());
Keypair::from_seed(seed: &[u8; 32]) -> Keypair
Derive a deterministic keypair from a 32-byte seed.
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.
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 |
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.
#[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.
#[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.
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.