# WebSocket examples

> Copy-paste WebSocket subscription examples for slot, account, and program updates in TypeScript, Rust, and Python.

Copy-paste working examples for connecting to Supanode WebSocket.

## Prerequisites

- An active Supanode Bundle subscription or free trial. Don't have one? Contact [@supanode_tgs](https://t.me/supanode_tgs) on Telegram.
- Your server's public IP registered in the Bundle [IP allowlist](https://supanode.xyz/docs/solana/authentication#bundle-ip-allowlist).
- The WebSocket endpoint URL (default: `ws://fra.supanode.xyz:8900`).

Authorization is by IP - no tokens to pass during the WebSocket handshake.

## Subscribe to slot updates

The lightest possible WebSocket subscription - useful as a smoke test.

<Tabs>
  <Tab title="TypeScript">
    ```typescript
    import { Connection } from "@solana/web3.js";

    const connection = new Connection("http://fra.supanode.xyz:8899", {
      wsEndpoint: "ws://fra.supanode.xyz:8900",
    });

    const subscriptionId = connection.onSlotChange((slotInfo) => {
      console.log("New slot:", slotInfo.slot);
    });

    // To unsubscribe later:
    // await connection.removeSlotChangeListener(subscriptionId);
    ```
  </Tab>
  <Tab title="Rust">
    ```rust
    use solana_client::nonblocking::pubsub_client::PubsubClient;
    use futures::StreamExt;

    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = PubsubClient::new("ws://fra.supanode.xyz:8900").await?;

        let (mut stream, unsubscribe) = client.slot_subscribe().await?;

        while let Some(slot_info) = stream.next().await {
            println!("New slot: {}", slot_info.slot);
        }

        unsubscribe().await;
        Ok(())
    }
    ```
  </Tab>
  <Tab title="Python">
    ```python
    import asyncio
    from solana.rpc.websocket_api import connect

    async def main():
        url = "ws://fra.supanode.xyz:8900"
        async with connect(url) as websocket:
            await websocket.slot_subscribe()
            first_resp = await websocket.recv()
            subscription_id = first_resp[0].result

            async for msg in websocket:
                print("New slot:", msg[0].result.slot)

    asyncio.run(main())
    ```
  </Tab>
  <Tab title="Raw JSON-RPC">
    Send the subscribe request as JSON over the WebSocket connection:

    ```json
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "slotSubscribe",
      "params": []
    }
    ```

    Server responds with a numeric subscription ID, then notifications stream:

    ```json
    {
      "jsonrpc": "2.0",
      "method": "slotNotification",
      "params": {
        "result": { "slot": 282729810, "parent": 282729809, "root": 282729778 },
        "subscription": 12345
      }
    }
    ```
  </Tab>
</Tabs>

## Subscribe to an account

Stream updates for a single account.

<Tabs>
  <Tab title="TypeScript">
    ```typescript
    import { Connection, PublicKey } from "@solana/web3.js";

    const connection = new Connection("http://fra.supanode.xyz:8899", {
      wsEndpoint: "ws://fra.supanode.xyz:8900",
    });

    const accountAddress = new PublicKey("YOUR_ACCOUNT_PUBKEY");

    const subscriptionId = connection.onAccountChange(
      accountAddress,
      (accountInfo) => {
        console.log("Balance:", accountInfo.lamports);
        console.log("Data:", accountInfo.data.toString("base64"));
      },
      "confirmed"
    );
    ```
  </Tab>
  <Tab title="Rust">
    ```rust
    use solana_client::{
        nonblocking::pubsub_client::PubsubClient,
        rpc_config::RpcAccountInfoConfig,
    };
    use solana_account_decoder::UiAccountEncoding;
    use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
    use futures::StreamExt;
    use std::str::FromStr;

    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = PubsubClient::new("ws://fra.supanode.xyz:8900").await?;

        let pubkey = Pubkey::from_str("YOUR_ACCOUNT_PUBKEY")?;
        let config = RpcAccountInfoConfig {
            encoding: Some(UiAccountEncoding::Base64),
            commitment: Some(CommitmentConfig::confirmed()),
            ..Default::default()
        };

        let (mut stream, unsubscribe) = client
            .account_subscribe(&pubkey, Some(config))
            .await?;

        while let Some(update) = stream.next().await {
            println!("Lamports: {}", update.value.lamports);
        }

        unsubscribe().await;
        Ok(())
    }
    ```
  </Tab>
  <Tab title="Python">
    ```python
    import asyncio
    from solana.rpc.websocket_api import connect
    from solders.pubkey import Pubkey

    async def main():
        url = "ws://fra.supanode.xyz:8900"
        account = Pubkey.from_string("YOUR_ACCOUNT_PUBKEY")

        async with connect(url) as websocket:
            await websocket.account_subscribe(
                account,
                commitment="confirmed",
                encoding="base64",
            )
            first_resp = await websocket.recv()
            subscription_id = first_resp[0].result

            async for msg in websocket:
                value = msg[0].result.value
                print("Lamports:", value.lamports)

    asyncio.run(main())
    ```
  </Tab>
</Tabs>

## Subscribe to a program (with required filter)

`programSubscribe` requires either a `dataSize` or `memcmp` filter on shared plans. Whole-program subscriptions are blocked - see [Restrictions](https://supanode.xyz/docs/solana/websocket/restrictions).

<Tabs>
  <Tab title="TypeScript">
    ```typescript
    import { Connection, PublicKey } from "@solana/web3.js";

    const connection = new Connection("http://fra.supanode.xyz:8899", {
      wsEndpoint: "ws://fra.supanode.xyz:8900",
    });

    const programId = new PublicKey("YOUR_PROGRAM_ID");

    const subscriptionId = connection.onProgramAccountChange(
      programId,
      (keyedAccountInfo) => {
        console.log("Account:", keyedAccountInfo.accountId.toBase58());
        console.log("Lamports:", keyedAccountInfo.accountInfo.lamports);
      },
      "confirmed",
      [{ dataSize: 165 }]   // required filter
    );
    ```
  </Tab>
  <Tab title="Rust">
    ```rust
    use solana_client::{
        nonblocking::pubsub_client::PubsubClient,
        rpc_config::{RpcProgramAccountsConfig, RpcAccountInfoConfig},
        rpc_filter::RpcFilterType,
    };
    use solana_account_decoder::UiAccountEncoding;
    use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey};
    use futures::StreamExt;
    use std::str::FromStr;

    #[tokio::main]
    async fn main() -> anyhow::Result<()> {
        let client = PubsubClient::new("ws://fra.supanode.xyz:8900").await?;

        let program_id = Pubkey::from_str("YOUR_PROGRAM_ID")?;
        let config = RpcProgramAccountsConfig {
            filters: Some(vec![RpcFilterType::DataSize(165)]),  // required
            account_config: RpcAccountInfoConfig {
                encoding: Some(UiAccountEncoding::Base64),
                commitment: Some(CommitmentConfig::confirmed()),
                ..Default::default()
            },
            ..Default::default()
        };

        let (mut stream, unsubscribe) = client
            .program_subscribe(&program_id, Some(config))
            .await?;

        while let Some(update) = stream.next().await {
            println!("Account: {}", update.value.pubkey);
        }

        unsubscribe().await;
        Ok(())
    }
    ```
  </Tab>
  <Tab title="Python">
    ```python
    import asyncio
    from solana.rpc.websocket_api import connect
    from solders.pubkey import Pubkey

    async def main():
        url = "ws://fra.supanode.xyz:8900"
        program = Pubkey.from_string("YOUR_PROGRAM_ID")

        async with connect(url) as websocket:
            await websocket.program_subscribe(
                program,
                commitment="confirmed",
                encoding="base64",
                filters=[{"dataSize": 165}],   # required filter
            )
            first_resp = await websocket.recv()
            subscription_id = first_resp[0].result

            async for msg in websocket:
                value = msg[0].result.value
                print("Account:", value.pubkey, "lamports:", value.account.lamports)

    asyncio.run(main())
    ```
  </Tab>
  <Tab title="Raw JSON-RPC">
    ```json
    {
      "jsonrpc": "2.0",
      "id": 1,
      "method": "programSubscribe",
      "params": [
        "YOUR_PROGRAM_ID",
        {
          "encoding": "base64",
          "commitment": "confirmed",
          "filters": [{ "dataSize": 165 }]
        }
      ]
    }
    ```
  </Tab>
</Tabs>

## Production tips

1. **Reconnect with exponential backoff.** WebSocket connections can drop. Implement retry starting at 1 second, capped at 30 seconds.

2. **Re-establish subscriptions on reconnect.** Subscription IDs are connection-scoped. After reconnect, all subscriptions are gone - resubscribe to what you need.

3. **Send a ping every 30-60 seconds.** Idle timeout is 10 minutes, but a periodic ping keeps the connection healthy and detects half-open sockets.

4. **Watch your subscription budget.** Total subs per plan is a hard cap. See [Limits](https://supanode.xyz/docs/solana/websocket/limits).

5. **Switch to gRPC for high throughput.** If you need many account / transaction streams at once, [gRPC](https://supanode.xyz/docs/solana/grpc/overview) is the right tool.

## Where to go next

<CardGroup cols={2}>
  <Card title="Limits" icon="gauge" href="https://supanode.xyz/docs/solana/websocket/limits">Connection caps and subscription totals.</Card>
  <Card title="Restrictions" icon="ban" href="https://supanode.xyz/docs/solana/websocket/restrictions">Blocked subscriptions.</Card>
  <Card title="Free Trials" icon="bolt" href="https://supanode.xyz/docs/solana/pricing/free-trials">48-hour trial.</Card>
  <Card title="Switch to gRPC" icon="bolt-lightning" href="https://supanode.xyz/docs/solana/grpc/overview">For high-throughput streams.</Card>
</CardGroup>
