WebSocket Reference
Lichen provides a WebSocket server for real-time event streaming. Subscribe to chain events — slots, blocks, transactions, account changes, contract logs, NFT activity, and marketplace events — and receive push notifications as they happen.
Coverage: Lichen currently exposes 20 live subscription families: 18 direct JSON-RPC subscription methods plus DEX and prediction-market multiplexed channels.
Neo X Integration: WebSocket remains the live event channel for blocks,
transactions, logs, token-balance updates, and DEX streams. Neo route status, wNEO/wGAS reserves, and
GAS rewards vault health are read through JSON-RPC methods such as
getBridgeRouteRestrictionStatus, getWneoStats,
getWgasStats, getNeoGasRewardsStats, and getDexPairs.
Clients should combine WS freshness with those indexed RPC reads rather than inferring route health
from frontend candles.
Connection
Public testnet WebSocket: wss://testnet-api.lichen.network/ws
Mainnet WebSocket target: wss://rpc.lichen.network/ws
The WebSocket server accepts standard WebSocket connections. All messages use JSON-RPC 2.0 format.
import { Connection } from '@lobstercove/lichen-sdk';
const conn = new Connection(
'https://testnet-api.lichen.network',
'wss://testnet-api.lichen.network/ws'
);
// Switch to https://rpc.lichen.network and wss://rpc.lichen.network/ws only when targeting mainnet.
// Subscribe to new slots
const subId = await conn.onSlot((slot) => {
console.log('New slot:', slot);
});
// Later: unsubscribe
await conn.offSlot(subId);
import asyncio
from lichen import Connection
async def main():
conn = Connection(
'https://testnet-api.lichen.network',
ws_url='wss://testnet-api.lichen.network/ws'
)
# Switch to https://rpc.lichen.network and wss://rpc.lichen.network/ws only when targeting mainnet.
sub_id = await conn.on_slot(lambda slot: print(f'New slot: {slot}'))
# Keep running to receive events
await asyncio.sleep(60)
await conn.off_slot(sub_id)
await conn.close()
asyncio.run(main())
# Install: npm install -g wscat
wscat -c wss://testnet-api.lichen.network/ws
# Subscribe to slots
> {"jsonrpc":"2.0","id":1,"method":"subscribeSlots","params":null}
< {"jsonrpc":"2.0","id":1,"result":1}
# Receive notifications
< {"jsonrpc":"2.0","method":"subscription","params":{"subscription":1,"result":{"slot":43}}}
Protocol Format
Subscribe Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "subscribeSlots",
"params": null
}
Subscribe Response
Returns the subscription ID (integer) used to identify future notifications and to unsubscribe.
{"jsonrpc": "2.0", "id": 1, "result": 1}
Notification
{
"jsonrpc": "2.0",
"method": "subscription",
"params": {
"subscription": 1,
"result": { /* event-specific payload */ }
}
}
Unsubscribe Request
{
"jsonrpc": "2.0",
"id": 2,
"method": "unsubscribeSlots",
"params": 1
}
Subscription Methods
subscribeSlots / unsubscribeSlots
Receive a notification each time a new slot is produced.
Parameters
None
Notification Payload
{ "slot": 43 }
SDK Usage
const subId = await conn.onSlot((slot) => {
console.log('Slot:', slot);
});
await conn.offSlot(subId);
subscribeBlocks / unsubscribeBlocks
Receive a notification for every new block with header summary.
Parameters
None
Notification Payload
{
"slot": 43,
"hash": "a1b2c3...",
"parent_hash": "d4e5f6...",
"transactions": 5,
"timestamp": 1738800000
}
SDK Usage
const subId = await conn.onBlock((block) => {
console.log(`Block #${block.slot} — ${block.transactions} txs`);
});
await conn.offBlock(subId);
subscribeTransactions / unsubscribeTransactions
Stream transactions observed in canonical blocks in real-time. Use
subscribeSignatureStatus when you need explicit processed / confirmed / finalized
commitment updates for one signature.
Parameters
None
Notification Payload
{
"signatures": ["a1b2c3..."],
"instructions": 2,
"recent_blockhash": "d4e5f6..."
}
SDK Usage
const subId = await conn.onTransaction((tx) => {
console.log('TX:', tx.signatures[0]);
});
await conn.offTransaction(subId);
subscribeAccount / unsubscribeAccount
Receive a notification whenever an account's balance changes.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| pubkey | string | Yes | Base58-encoded public key to watch |
Notification Payload
{
"pubkey": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
"balance": 5000000000,
"licn": 5
}
SDK Usage
const pubkey = new PublicKey('7xKXtg2CW87d...');
const subId = await conn.onAccountChange(pubkey, (account) => {
console.log('New balance:', account.licn, 'LICN');
});
await conn.offAccountChange(subId);
subscribeLogs / unsubscribeLogs
Stream contract execution logs. Optionally filter by a specific contract address;
pass null for all contracts.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| contractId | string | null | No | Contract pubkey to filter, or null for all |
Notification Payload
{
"contract": "ContractPubkey...",
"message": "Transfer completed: 100 LICN"
}
SDK Usage
// All logs
const subId = await conn.onLogs((log) => {
console.log(`[${log.contract}] ${log.message}`);
});
// Single contract
const contractKey = new PublicKey('ContractPub...');
const subId2 = await conn.onLogs((log) => {
console.log(log.message);
}, contractKey);
subscribeProgramUpdates / unsubscribeProgramUpdates
Receive notifications when programs are deployed or upgraded.
Parameters
None
Notification Payload
{ "program": "ProgramPubkey...", "kind": "deploy" }
subscribeProgramCalls / unsubscribeProgramCalls
Stream program invocations. Optionally filter by a specific program.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| programId | string | null | No | Program pubkey to filter, or null for all |
Notification Payload
{ "program": "ProgramPubkey..." }
subscribeNftMints / unsubscribeNftMints
Receive a notification each time an NFT is minted. Optionally filter by collection.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| collectionId | string | null | No | Collection pubkey to filter, or null for all |
Notification Payload
{ "collection": "CollectionPubkey..." }
subscribeNftTransfers / unsubscribeNftTransfers
Stream NFT transfer events. Optionally filter by collection.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| collectionId | string | null | No | Collection pubkey to filter, or null for all |
Notification Payload
{ "collection": "CollectionPubkey..." }
subscribeMarketListings / unsubscribeMarketListings
Receive a notification each time an item is listed on the marketplace.
Parameters
None
Notification Payload
{
"event": "MarketListing",
"slot": 100,
"timestamp": 1738800000,
"kind": "list",
"program": "ProgramPub...",
"collection": "CollectionPub...",
"token_id": 42,
"price": 5000000000,
"price_licn": 5.0,
"seller": "SellerPub...",
"tx_signature": "abc123..."
}
SDK Usage
const subId = await conn.onMarketListings((event) => {
console.log(`Listed: token #${event.token_id} for ${event.price_licn} LICN`);
});
await conn.offMarketListings(subId);
subscribeMarketSales / unsubscribeMarketSales
Receive a notification each time a marketplace sale is completed.
Parameters
None
Notification Payload
{
"event": "MarketSale",
"slot": 101,
"timestamp": 1738800400,
"kind": "sale",
"program": "ProgramPub...",
"collection": "CollectionPub...",
"token_id": 42,
"price": 5000000000,
"price_licn": 5.0,
"seller": "SellerPub...",
"buyer": "BuyerPub...",
"tx_signature": "def456..."
}
SDK Usage
const subId = await conn.onMarketSales((event) => {
console.log(`Sold: token #${event.token_id} for ${event.price_licn} LICN`);
});
await conn.offMarketSales(subId);
subscribeBridgeLocks / unsubscribeBridgeLocks
Receive bridge lock events when assets are locked for cross-chain transfer.
Parameters
None
Notification Payload
{
"event": "BridgeLock",
"chain": "ethereum",
"asset": "LICN",
"amount": 250000000,
"amount_display": 250.0,
"sender": "0xabc...",
"recipient": "RecipientPub..."
}
subscribeBridgeMints / unsubscribeBridgeMints
Receive bridge mint events when bridged assets are minted on Lichen.
Parameters
None
Notification Payload
{
"event": "BridgeMint",
"chain": "ethereum",
"asset": "LICN",
"amount": 250000000,
"amount_display": 250.0,
"recipient": "RecipientPub...",
"tx_hash": "0xdef..."
}
subscribeSignatureStatus
Track the confirmation status of a specific transaction signature through
processed → confirmed → finalized. finalized means the transaction's block is covered
by the node's BFT finality tracker after commit supermajority.
Parameters
| Name | Type | Description |
|---|---|---|
| signature | string | Transaction signature hex string |
Notification Payload
{
"event": "SignatureStatus",
"signature": "abc123...",
"status": "finalized",
"slot": 1234,
"error": null
}
SDK Usage
const subId = await conn.onSignatureStatus(txSig, (status) => {
console.log(`TX ${status.status} at slot ${status.slot}`);
});
await conn.offSignatureStatus(subId);
subscribeValidators
Receive notifications when the validator set changes — new validators joining, leaving, becoming delinquent, or stake changes.
Parameters
None
Notification Payload
{
"event": "ValidatorUpdate",
"pubkey": "8GCKfyj...",
"kind": "joined",
"stake": 100000000000000,
"stake_display": 100000.0,
"slot": 500
}
subscribeTokenBalance
Track token balance changes for a specific owner, optionally filtered by mint address.
Parameters
| Name | Type | Description |
|---|---|---|
| owner | string | Owner public key (required) |
| mint | string? | Token mint address (optional — omit for all tokens) |
Notification Payload
{
"event": "TokenBalanceChange",
"owner": "UserPub...",
"mint": "MintPub...",
"old_balance": 1000000000,
"new_balance": 2000000000,
"delta": 1000000000,
"slot": 789
}
subscribeEpochs
Receive a notification at each epoch boundary with epoch statistics.
Parameters
None
Notification Payload
{
"event": "EpochChange",
"epoch": 5,
"slot": 2160000,
"total_stake": 500000000000000,
"total_stake_display": 500000.0,
"validator_count": 42
}
subscribeGovernance
Receive notifications for on-chain governance events: proposal creation, approval, execution, and cancellation.
Parameters
None
Notification Payload
{
"event": "GovernanceEvent",
"proposal_id": 7,
"kind": "approved",
"action": "contract_call",
"authority": "AuthorityPub...",
"proposer": "ProposerPub...",
"actor": "ActorPub...",
"approvals": 2,
"threshold": 2,
"execute_after_epoch": 9,
"executed": false,
"cancelled": false,
"metadata": "contract=... function=... args_len=0 value_spores=0",
"slot": 1234
}
Event Payloads
Slot Event
Emitted on each new slot.
{ "slot": 43 }
Block Event
Emitted on each new block.
{
"slot": 43,
"hash": "a1b2c3d4e5f6...",
"parent_hash": "f6e5d4c3b2a1...",
"transactions": 5,
"timestamp": 1738800000
}
Transaction Event
Emitted for each transaction observed in the canonical block stream. This event
does not by itself imply finalized; use signature status subscriptions when commitment
level matters.
{
"signatures": ["a1b2c3d4..."],
"instructions": 2,
"recent_blockhash": "d4e5f6..."
}
AccountChange Event
Emitted when a watched account's balance changes.
{
"pubkey": "7xKXtg2CW87d...",
"balance": 5000000000,
"licn": 5
}
Log Event
Emitted when a contract emits a log message.
{
"contract": "ContractPubkey...",
"message": "Transfer completed: 100 LICN"
}
MarketListing / MarketSale Event
Emitted for marketplace activity. Both events share the same structure with an
event discriminator field.
{
"event": "MarketListing" | "MarketSale",
"slot": 100,
"timestamp": 1738800000,
"kind": "list" | "sale",
"program": "ProgramPub...",
"collection": "CollectionPub...",
"token": "TokenPub...",
"token_id": 42,
"price": 5000000000,
"price_licn": 5.0,
"seller": "SellerPub...",
"buyer": "BuyerPub...",
"function": "buyNow",
"tx_signature": "abc123..."
}
Additional Implemented Live Channels
These subscription families are implemented in the WebSocket server (`rpc/src/ws.rs`) and channel parsing in `rpc/src/dex_ws.rs`.
DEX Multiplexed Channels
subscribeDex / unsubscribeDex
Channel Param Format
orderbook:<pair_id>trades:<pair_id>ticker:<pair_id>candles:<pair_id>:<interval>orders:<trader_addr>positions:<trader_addr>
{
"jsonrpc": "2.0",
"id": 101,
"method": "subscribeDex",
"params": { "channel": "trades:1" }
}
{
"jsonrpc": "2.0",
"id": 102,
"method": "unsubscribeDex",
"params": { "subscription": 101 }
}
Prediction Multiplexed Channels
subscribePrediction / unsubscribePrediction
Unsubscribe with unsubscribePrediction.
Channel Param Format
allormarkets— all marketsmarket:<market_id><market_id>
{
"jsonrpc": "2.0",
"id": 201,
"method": "subscribePrediction",
"params": { "channel": "market:42" }
}
{
"jsonrpc": "2.0",
"id": 202,
"method": "unsubscribePrediction",
"params": { "subscription": 201 }
}
Keepalive (Ping/Pong)
The WebSocket server implements standard RFC 6455 ping/pong keepalive with dead-connection detection.
- The server sends a ping frame every 30 seconds.
- Your client must respond with a pong frame before the next ping (30s window). Most WebSocket libraries handle this automatically.
- If a pong is not received before the next ping fires, the server considers the connection dead and closes it immediately.
- Alternatively, clients can send
{"method":"ping"}as a JSON message. The server responds with{"result":"pong"}.
Important: Unlike some WebSocket servers that tolerate multiple missed pongs, Lichen uses strict single-pong timeout. If your client's pong response is delayed beyond 30 seconds, the connection will be terminated. Ensure your client library has pong auto-response enabled (this is the default for most libraries).
Notification wrapper difference: Core subscriptions (slots, blocks, accounts,
etc.) use "method": "subscription" in push messages. DEX and Prediction channel
notifications use "method": "notification" instead. Check both wrappers when
parsing events.
Best Practices
Reconnection Handling
WebSocket connections can drop due to network issues, server restarts, or idle timeouts. Implement automatic reconnection with exponential backoff:
class ResilientConnection {
constructor(rpcUrl, wsUrl) {
this.rpcUrl = rpcUrl;
this.wsUrl = wsUrl;
this.conn = null;
this.subscriptions = [];
this.retryDelay = 1000;
}
async connect() {
this.conn = new Connection(this.rpcUrl, this.wsUrl);
// Re-subscribe after reconnection
for (const sub of this.subscriptions) {
await sub.resubscribe(this.conn);
}
this.retryDelay = 1000; // reset on success
}
async reconnect() {
console.log(`Reconnecting in ${this.retryDelay}ms...`);
await new Promise(r => setTimeout(r, this.retryDelay));
this.retryDelay = Math.min(this.retryDelay * 2, 30000);
try {
await this.connect();
} catch (e) {
await this.reconnect();
}
}
}
Tip: Store subscription parameters so you can restore them after reconnection.
The SDK's Connection class will automatically reconnect the WebSocket when you call
an on* method.
Rate Limits & Subscription Limits
Max subscriptions per connection: 100
If you exceed this limit, the server returns error code -32005: "Subscription
limit reached".
Best practices:
- Unsubscribe from channels you no longer need.
- Use filtered subscriptions (e.g.,
subscribeLogswith a contract ID) instead of subscribing to all events and filtering client-side. - For high-throughput scenarios, consider using multiple WebSocket connections and distributing subscriptions across them.
- The broadcast channel has a buffer of 1000 events. If your consumer falls behind, events may be dropped.