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.

JavaScript
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);
Python
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())
wscat
# 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

JSON
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "subscribeSlots",
  "params": null
}

Subscribe Response

Returns the subscription ID (integer) used to identify future notifications and to unsubscribe.

JSON
{"jsonrpc": "2.0", "id": 1, "result": 1}

Notification

JSON
{
  "jsonrpc": "2.0",
  "method": "subscription",
  "params": {
    "subscription": 1,
    "result": { /* event-specific payload */ }
  }
}

Unsubscribe Request

JSON
{
  "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

JSON
{ "slot": 43 }

SDK Usage

JavaScript
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

JSON
{
  "slot": 43,
  "hash": "a1b2c3...",
  "parent_hash": "d4e5f6...",
  "transactions": 5,
  "timestamp": 1738800000
}

SDK Usage

JavaScript
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

JSON
{
  "signatures": ["a1b2c3..."],
  "instructions": 2,
  "recent_blockhash": "d4e5f6..."
}

SDK Usage

JavaScript
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

JSON
{
  "pubkey": "7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZRuJosgAsU",
  "balance": 5000000000,
  "licn": 5
}

SDK Usage

JavaScript
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

JSON
{
  "contract": "ContractPubkey...",
  "message": "Transfer completed: 100 LICN"
}

SDK Usage

JavaScript
// 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

JSON
{ "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

JSON
{ "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

JSON
{ "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

JSON
{ "collection": "CollectionPubkey..." }

subscribeMarketListings / unsubscribeMarketListings

Receive a notification each time an item is listed on the marketplace.

Parameters

None

Notification Payload

JSON
{
  "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

JavaScript
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

JSON
{
  "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

JavaScript
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

JSON
{
  "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

JSON
{
  "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

JSON
{
  "event": "SignatureStatus",
  "signature": "abc123...",
  "status": "finalized",
  "slot": 1234,
  "error": null
}

SDK Usage

JavaScript
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

JSON
{
  "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

JSON
{
  "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

JSON
{
  "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

JSON
{
  "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.

JSON
{ "slot": 43 }

Block Event

Emitted on each new block.

JSON
{
  "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.

JSON
{
  "signatures": ["a1b2c3d4..."],
  "instructions": 2,
  "recent_blockhash": "d4e5f6..."
}

AccountChange Event

Emitted when a watched account's balance changes.

JSON
{
  "pubkey": "7xKXtg2CW87d...",
  "balance": 5000000000,
  "licn": 5
}

Log Event

Emitted when a contract emits a log message.

JSON
{
  "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.

JSON
{
  "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>
JSON-RPC
{
  "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

  • all or markets — all markets
  • market:<market_id>
  • <market_id>
JSON-RPC
{
  "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:

JavaScript
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., subscribeLogs with 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.