JavaScript / TypeScript SDK
The official JavaScript SDK for Lichen. Full TypeScript types, async/await API, and integrated WebSocket subscriptions.
Installation
npm install @lobstercove/lichen-sdk
The SDK requires Node.js 20.19+ or a browser environment with fetch support. WebSocket
features require the ws package in Node.js (included as a dependency).
The published package name is @lobstercove/lichen-sdk. Monorepo contributors can
still install from the local sdk/js/ directory when iterating in-tree. The
examples below use public testnet endpoints; swap
to https://rpc.lichen.network and wss://rpc.lichen.network/ws only
when targeting mainnet.
Quick Start
import { Connection, Keypair, PublicKey, TransactionBuilder } from '@lobstercove/lichen-sdk';
// Connect to public testnet
const conn = new Connection('https://testnet-rpc.lichen.network', 'wss://testnet-rpc.lichen.network/ws');
// Switch to https://rpc.lichen.network and wss://rpc.lichen.network/ws only when targeting mainnet.
// Generate a keypair
const keypair = Keypair.generate();
console.log('Address:', keypair.pubkey().toBase58());
// Check balance
const balance = await conn.getBalance(keypair.pubkey());
console.log(`Balance: ${balance.licn} LICN (${balance.spores} spores)`);
// Build and send a transfer
const recipient = new PublicKey('RecipientBase58Address...');
const blockhash = await conn.getRecentBlockhash();
const tx = new TransactionBuilder()
.add(TransactionBuilder.transfer(keypair.pubkey(), recipient, 1_000_000_000))
.setRecentBlockhash(blockhash)
.buildAndSign(keypair);
const signature = await conn.sendTransaction(tx);
console.log('TX Signature:', signature);
LichenID helper: import 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: import SporePayClient for stream creation,
stream/cliff reads, withdrawable balance checks, withdrawals, cancellations, transfers, and
aggregated stats. Raw callContract() remains the escape hatch for admin-only or
newly added contract methods that are not wrapped yet.
LichenSwap helper: import LichenSwapClient for symbol-registry
program discovery, 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
callContract() when you need them.
ThallLend helper: import 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-only controls
still use raw callContract().
SporeVault helper: import 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 still use raw
callContract().
BountyBoard helper: import 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 still use raw callContract().
import { Connection, Keypair, LichenIdClient, LichenSwapClient, SporePayClient, SporeVaultClient, ThallLendClient } from '@lobstercove/lichen-sdk';
const conn = new Connection('https://testnet-rpc.lichen.network', 'wss://testnet-rpc.lichen.network/ws');
const lichenId = new LichenIdClient(conn);
const lichenSwap = new LichenSwapClient(conn);
const sporePay = new SporePayClient(conn);
const sporeVault = new SporeVaultClient(conn);
const thallLend = new ThallLendClient(conn);
const keypair = Keypair.generate();
const delegateKey = Keypair.generate();
await lichenId.registerIdentity(keypair, { agentType: 1, name: 'Market Maker Alpha' });
await lichenId.addSkill(keypair, { name: 'market-making', proficiency: 92 });
await lichenId.setMetadata(keypair, { metadata: { region: 'us-east', role: 'maker' } });
await lichenId.setDelegate(keypair, {
delegate: delegateKey.pubkey(),
permissions: 1,
expiresAtMs: Date.now() + 86_400_000,
});
await lichenId.setAvailability(keypair, { status: 'available' });
const profile = await lichenId.getProfile(keypair.pubkey());
const delegation = await lichenId.getDelegate(keypair.pubkey(), delegateKey.pubkey());
const poolInfo = await lichenSwap.getPoolInfo();
const quote = await lichenSwap.getQuote(1_000_000_000n, true);
const swapStats = await lichenSwap.getStats();
const stream = await sporePay.getStreamInfo(0n);
const vaultStats = await sporeVault.getVaultStats();
const vaultPosition = await sporeVault.getUserPosition(keypair.pubkey());
const vaultSummary = await sporeVault.getStats();
const lendingAccount = await thallLend.getAccountInfo(keypair.pubkey());
const lendingStats = await thallLend.getStats();
console.log({ profile, delegation, poolInfo, quote, swapStats, stream, vaultStats, vaultPosition, vaultSummary, lendingAccount, lendingStats });
Connection Class
new Connection(rpcUrl, wsUrl?, options?)
Create a connection to a Lichen validator node.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| rpcUrl | string | Yes | HTTP JSON-RPC endpoint URL |
| wsUrl | string | No | WebSocket endpoint URL (required for subscriptions) |
| options.timeoutMs | number | No | Request timeout in milliseconds (default: 30000) |
const conn = new Connection(
'https://testnet-rpc.lichen.network',
'wss://testnet-rpc.lichen.network/ws'
);
Chain Queries
getSlot(): Promise<number>
Get the current slot number.
const slot = await conn.getSlot();
// 42
getLatestBlock(): Promise<Block>
Get the most recently produced block. Returns
{ slot, hash, parentHash, transactions, timestamp }.
const block = await conn.getLatestBlock();
console.log(`Slot ${block.slot}, ${block.transactions} txs`);
getBlock(slot: number): Promise<Block>
Get a block by slot number.
const block = await conn.getBlock(42);
getRecentBlockhash(): Promise<string>
Get a recent blockhash for signing transactions.
const blockhash = await conn.getRecentBlockhash();
getMetrics(): Promise<Metrics>
Get chain performance metrics:
{ tps, totalTransactions, totalBlocks, averageBlockTime }.
getChainStatus(): Promise<ChainStatus>
Get comprehensive status:
{ currentSlot, validatorCount, totalStake, tps, totalTransactions, totalBlocks, averageBlockTime, isHealthy }.
health(): Promise<{ status: string }>
Liveness probe. Returns { status: "ok" }.
getNetworkInfo(): Promise<NetworkInfo>
Get network info:
{ chainId, networkId, version, currentSlot, validatorCount, peerCount }.
getPeers(): Promise<any[]>
Get connected P2P peers.
getTotalBurned(): Promise<Balance>
Get total burned LICN. Returns { spores, licn }.
Account Methods
getBalance(pubkey: PublicKey): Promise<Balance>
Get account balance in spores and LICN.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| pubkey | PublicKey | Yes | Account public key |
const balance = await conn.getBalance(new PublicKey('7xKXtg2CW87d...'));
console.log(balance.spores); // 5000000000
console.log(balance.licn); // 5.0
getAccount(pubkey: PublicKey): Promise<Account>
Get raw account data: { spores, owner, executable, data }.
getAccountInfo(pubkey: PublicKey): Promise<any>
Get enhanced account information with additional metadata.
getTransactionHistory(pubkey: PublicKey, limit?: number): Promise<any>
Get paginated transaction history for an account. Default limit is 10.
Transaction Methods
sendTransaction(transaction: Transaction): Promise<string>
Serialize and submit a signed transaction to the mempool. Returns the transaction signature.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| transaction | Transaction | Yes | A signed Transaction object |
const blockhash = await conn.getRecentBlockhash();
const tx = new TransactionBuilder()
.add(TransactionBuilder.transfer(from.pubkey(), to, 1_000_000_000))
.setRecentBlockhash(blockhash)
.buildAndSign(from);
const sig = await conn.sendTransaction(tx);
console.log('Signature:', sig);
getTransaction(signature: string): Promise<any>
Look up a transaction by its hex-encoded signature.
getTransactionProof(signature: string): Promise<TransactionProof>
Get a binary Merkle inclusion proof for a confirmed transaction. The proof
verifies that the transaction is included in the block's tx_root.
Returns
TransactionProof — Object with slot, tx_index,
tx_hash, root, and proof (array of
ProofStep).
const proof = await conn.getTransactionProof(signature);
console.log('Merkle root:', proof.root);
console.log('Proof depth:', proof.proof.length);
// Verify the proof locally
const valid = Connection.verifyTransactionProof(proof.root, proof.tx_hash, proof.proof);
console.log('Valid:', valid); // true
static verifyTransactionProof(root: string, txHash: string, proof: ProofStep[]): boolean
Verify a Merkle inclusion proof locally using SHA-256 with domain separation.
Returns true if the proof is valid. This is a static method — no RPC connection needed.
Types
interface ProofStep { hash: string; direction: 'left' | 'right'; }
interface TransactionProof {
slot: number; tx_index: number; tx_hash: string;
root: string; proof: ProofStep[];
}
simulateTransaction(transaction: Transaction): Promise<any>
Dry-run a transaction without committing it. Useful for fee estimation and validation.
transfer(from: Keypair, to: PublicKey, amount: number | bigint): Promise<string>
Build, sign, and send a native LICN transfer. Amount in spores (1 LICN = 1,000,000,000 spores).
const recipient = new PublicKey('RecipientAddr...');
const sig = await conn.transfer(keypair, recipient, 5_000_000_000); // 5 LICN
console.log('Transfer:', sig);
Validator & Staking
getValidators(): Promise<Validator[]>
Get all registered validators with stake, reputation, blocks proposed, votes cast.
getValidatorInfo(pubkey: PublicKey): Promise<Validator>
Get detailed information for a specific validator.
getValidatorPerformance(pubkey: PublicKey): Promise<any>
Get performance metrics for a validator.
stake(from: Keypair, validator: PublicKey, amount: number): Promise<string>
Build, sign, and send a stake transaction. Amount is in spores.
const validator = new PublicKey('ValidatorPubkey...');
const sig = await conn.stake(keypair, validator, 10_000_000_000); // 10 LICN
unstake(from: Keypair, validator: PublicKey, amount: number): Promise<string>
Build, sign, and send an unstake request transaction.
getStakingStatus(pubkey: PublicKey): Promise<any>
Get staking status for an account.
getStakingRewards(pubkey: PublicKey): Promise<any>
Get accumulated staking rewards.
Contract Methods
deployContract(deployer: Keypair, code: Uint8Array, initData?: Uint8Array): Promise<string>
Deploy a WASM smart contract. Code must start with the \0asm magic
header and be under 512 KB. Returns the transaction signature.
import { readFile } from 'fs/promises';
const wasm = new Uint8Array(await readFile('my_contract.wasm'));
const sig = await conn.deployContract(keypair, wasm);
console.log('Deploy tx:', sig);
callContract(caller: Keypair, contract: PublicKey, functionName: string, args?: Uint8Array, value?: number | bigint): Promise<string>
Call a function on a deployed WASM contract. Optionally send LICN (spores) with the call. Returns the transaction signature.
const contract = new PublicKey('ContractAddr...');
const args = new TextEncoder().encode(JSON.stringify({ recipient: 'Addr...', amount: 100 }));
const sig = await conn.callContract(keypair, contract, 'transfer', args);
callReadonlyContract(contract: PublicKey, functionName: string, args?: Uint8Array, from?: PublicKey): Promise<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.
const entry = await conn.getSymbolRegistry('LICHENSWAP');
const program = new PublicKey(entry.program);
const result = await conn.callReadonlyContract(program, 'get_pool_info');
console.log(result.returnCode, result.returnData);
upgradeContract(owner: Keypair, contract: PublicKey, code: Uint8Array): Promise<string>
Upgrade a deployed contract with new WASM bytecode (owner only).
getContractInfo(contractId: PublicKey): Promise<any>
Get information about a deployed smart contract. Returns
contract_id,
owner, code_size, is_executable, has_abi,
abi_functions, code_hash, version.
For tokens, includes a token_metadata object with total_supply,
decimals, token_symbol, token_name,
mintable, burnable.
Registry metadata (from --metadata at deploy) may also include
description, website, logo_url,
twitter, telegram, discord.
getContractLogs(contractId: PublicKey): Promise<any>
Get execution logs emitted by a contract.
getContractAbi(contractId: PublicKey): Promise<any>
Get the ABI/IDL for a contract.
setContractAbi(contractId: PublicKey, abi: any): Promise<any>
Set or update a contract's ABI (owner only).
getAllContracts(): Promise<any>
List all deployed contracts.
Program Methods
getProgram(programId: PublicKey): Promise<any>
Get program information.
getSymbolRegistry(symbol: string): Promise<any>
Resolve a registry symbol such as YID, SPOREPAY, or
LICHENSWAP to its deployed program entry.
const entry = await conn.getSymbolRegistry('LICHENSWAP');
console.log(entry.program);
getProgramAccounts(programId: PublicKey): Promise<any[]>
List all accounts currently owned by a program.
getPrograms(): Promise<any>
List all deployed programs.
getProgramStats(programId: PublicKey): Promise<any>
Get execution statistics for a program.
getProgramCalls(programId: PublicKey): Promise<any>
Get recent call history for a program.
getProgramStorage(programId: PublicKey): Promise<any>
Get on-chain storage summary for a program.
getSporePayStats(): Promise<any> / getLichenSwapStats(): Promise<any> / getThallLendStats(): Promise<any> / getSporeVaultStats(): Promise<any> / getBountyBoardStats(): Promise<any>
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.
const sporePayStats = await conn.getSporePayStats();
const lichenSwapStats = await conn.getLichenSwapStats();
const thallLendStats = await conn.getThallLendStats();
const sporeVaultStats = await conn.getSporeVaultStats();
const bountyBoardStats = await conn.getBountyBoardStats();
console.log({ sporePayStats, lichenSwapStats, thallLendStats, sporeVaultStats, bountyBoardStats });
NFT Methods
getCollection(collectionId: PublicKey): Promise<any>
Get NFT collection metadata.
getNFT(collectionId: PublicKey, tokenId: number): Promise<any>
Get a specific NFT by collection and token ID.
getNFTsByOwner(owner: PublicKey): Promise<any>
Get all NFTs owned by an address.
getNFTsByCollection(collectionId: PublicKey): Promise<any>
Get all NFTs in a collection.
getNFTActivity(collectionId: PublicKey, tokenId: number): Promise<any>
Get activity history for a specific NFT.
WebSocket Subscriptions
All subscription methods require a wsUrl to be set in the Connection constructor. Each
on* method returns a subscription ID for later unsubscribing with the corresponding
off* method.
onSlot(callback: (slot: number) => void): Promise<number>
Subscribe to slot updates. Unsubscribe with offSlot(subId).
onBlock(callback: (block: Block) => void): Promise<number>
Subscribe to new blocks. Unsubscribe with offBlock(subId).
onTransaction(callback: (tx: any) => void): Promise<number>
Subscribe to transactions observed in canonical blocks. Use signature status
subscriptions when you need explicit processed / confirmed / finalized commitment tracking.
Unsubscribe with
offTransaction(subId).
onAccountChange(pubkey: PublicKey, callback: (account: any) => void): Promise<number>
Watch a specific account for balance changes. Unsubscribe with
offAccountChange(subId).
onLogs(callback: (log: any) => void, contractId?: PublicKey): Promise<number>
Stream contract logs. Pass a contractId to filter to one contract,
or omit for all. Unsubscribe with offLogs(subId).
onProgramUpdates(callback) / onProgramCalls(callback, programId?)
Subscribe to program deploy/upgrade events and program invocations. Unsubscribe
with offProgramUpdates(subId) / offProgramCalls(subId).
onNftMints(callback, collectionId?) / onNftTransfers(callback, collectionId?)
Subscribe to NFT mint and transfer events. Optionally filter by collection.
Unsubscribe with offNftMints(subId) / offNftTransfers(subId).
onMarketListings(callback) / onMarketSales(callback)
Subscribe to marketplace listing and sale events. Unsubscribe with
offMarketListings(subId) / offMarketSales(subId).
close(): void
Close the WebSocket connection and clear all subscriptions.
Keypair Class
ML-DSA-65 keypair for signing transactions. Uses the SDK's native PQ runtime.
Keypair.generate(): Keypair
Generate a new random keypair.
const kp = Keypair.generate();
console.log('Address:', kp.pubkey().toBase58());
console.log('Seed length:', kp.toSeed().length); // 32 bytes
Keypair.fromSeed(seed: Uint8Array): Keypair
Derive a keypair from a 32-byte seed. Throws if seed is not exactly 32 bytes.
const seed = new Uint8Array(32); // deterministic seed
const kp = Keypair.fromSeed(seed);
keypair.pubkey(): PublicKey
Get the public key as a PublicKey object.
keypair.sign(message: Uint8Array): PqSignature
Sign an arbitrary message. Returns a self-contained PQ signature.
Properties
| Property | Type | Description |
|---|---|---|
| publicKey | Uint8Array | 32-byte public key |
| privateKey | Uint8Array | 32-byte ML-DSA-65 seed |
PublicKey Class
A 32-byte public key with base58 encoding/decoding. Uses bs58 under the hood.
new PublicKey(value: string | Uint8Array | number[])
Create a PublicKey from a base58 string, Uint8Array, or number array. Throws if the resulting bytes are not exactly 32 bytes.
const pk1 = new PublicKey('7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU');
const pk2 = new PublicKey(new Uint8Array(32));
const pk3 = new PublicKey([0, 1, 2, /* ... 32 bytes */]);
Methods
| Method | Returns | Description |
|---|---|---|
| toBase58() | string | Base58-encoded string representation |
| toBytes() | Uint8Array | Raw 32-byte array |
| toString() | string | Same as toBase58() |
| equals(other: PublicKey) | boolean | Byte-level equality check |
Static Methods
| Method | Returns | Description |
|---|---|---|
| PublicKey.fromBase58(str) | PublicKey | Create from base58 string |
| PublicKey.fromBytes(bytes) | PublicKey | Create from Uint8Array |
Transaction Types & Builder
Types
interface Instruction {
programId: PublicKey;
accounts: PublicKey[];
data: Uint8Array;
}
interface Message {
instructions: Instruction[];
recentBlockhash: string;
}
interface Transaction {
signatures: string[]; // hex-encoded
message: Message;
}
TransactionBuilder
Fluent builder for constructing and signing transactions.
| Method | Returns | Description |
|---|---|---|
| add(instruction) | this | Append an instruction |
| setRecentBlockhash(hash) | this | Set the recent blockhash |
| build() | Message | Build unsigned message |
| buildAndSign(keypair) | Transaction | Build, sign, and return a Transaction |
Static Instruction Builders
| Method | Description |
|---|---|
| TransactionBuilder.transfer(from, to, amount) | Create a LICN transfer instruction (type 0) |
| TransactionBuilder.stake(from, validator, amount) | Create a stake instruction (type 9) |
| TransactionBuilder.unstake(from, validator, amount) | Create an unstake instruction (type 10) |
| TransactionBuilder.deployContract(deployer, code, initData?) | Create a contract deploy instruction (CONTRACT_PROGRAM_ID) |
| TransactionBuilder.callContract(caller, contract, func, args?, value?) | Create a contract call instruction |
| TransactionBuilder.upgradeContract(owner, contract, code) | Create a contract upgrade instruction (owner only) |
const blockhash = await conn.getRecentBlockhash();
const tx = new TransactionBuilder()
.add(TransactionBuilder.transfer(
keypair.pubkey(),
new PublicKey('RecipientAddr...'),
5_000_000_000 // 5 LICN
))
.setRecentBlockhash(blockhash)
.buildAndSign(keypair);
const sig = await conn.sendTransaction(tx);
Error Handling
All RPC methods throw standard JavaScript Error objects with descriptive messages from the
server.
try {
const balance = await conn.getBalance(pubkey);
} catch (error) {
if (error.message.includes('Account not found')) {
console.log('Account does not exist yet');
} else if (error.message.includes('Rate limit')) {
// Back off and retry
await new Promise(r => setTimeout(r, 1000));
} else {
throw error;
}
}
Common error codes:
-32601— Method not found-32602— Invalid params-32003— Admin auth required-32005— Rate limit exceeded / Subscription limit reached