ShainShain/docs
Shain/Docs/Monitor sessions
Integration

Monitor sessions

Poll session snapshots, render a countdown, and react to expiry. The pattern every UI uses.

React hook

use-shain-session.tstypescript
"use client";

import { useEffect, useState } from "react";
import { PublicKey } from "@solana/web3.js";
import { ShainClient, SessionSnapshot, formatRemaining } from "@shain/sdk";

export function useShainSession(client: ShainClient | null, holder?: PublicKey) {
  const [snap, setSnap] = useState<SessionSnapshot | null>(null);
  const [remaining, setRemaining] = useState<string>("—");

  useEffect(() => {
    if (!client) return;
    let cancelled = false;

    const refresh = async () => {
      const next = await client.snapshotSession(holder);
      if (!cancelled) setSnap(next);
    };

    refresh();
    const id = window.setInterval(refresh, 30_000);
    return () => {
      cancelled = true;
      window.clearInterval(id);
    };
  }, [client, holder?.toBase58()]);

  useEffect(() => {
    if (!snap) return;
    const tick = () => setRemaining(formatRemaining(snap));
    tick();
    const id = window.setInterval(tick, 1_000);
    return () => window.clearInterval(id);
  }, [snap]);

  return { snapshot: snap, remaining };
}

Usage in a component

SessionBadge.tsxtsx
export function SessionBadge() {
  const client = useShainClient();
  const { snapshot, remaining } = useShainSession(client);

  if (!snapshot) {
    return <span className="badge muted">no session</span>;
  }

  if (!snapshot.active) {
    return <span className="badge muted">expired</span>;
  }

  return (
    <span className="badge active">
      active · {remaining}
    </span>
  );
}

Server-side polling

For a Telegram bot or a Discord webhook, use the REST shape instead of the SDK to skip the bundle:

curlbash
curl -s "https://shain.fun/api/wallet/balance?address=<WALLET>" | jq

# { "ok": true, "address": "...", "lamports": 4_132_041, "sol": 0.00413, "slot": 339_182_441, "network": "solana-devnet" }

The balance endpoint is a thin RPC proxy. For session state, read the PDA directly or run a minimal SDK script in your worker.

WebSockets

If you want push updates instead of polling, subscribe to the session PDA via connection.onAccountChange:

subscribe.tstypescript
const { session } = client.pdas(holder);
if (!session) return;

const subscriptionId = connection.onAccountChange(session, (info) => {
  // info.data is the raw Buffer; parse with your SDK decoder
  refreshSnapshot();
}, "confirmed");

// Clean up later: connection.removeAccountChangeListener(subscriptionId)