> ## Documentation Index
> Fetch the complete documentation index at: https://docs.bison.markets/llms.txt
> Use this file to discover all available pages before exploring further.

# Event Streaming

> Real-time event streaming with WebSockets

## Overview

The SDK provides three WebSocket-based event streaming methods:

* `listen` - Subscribe to user-specific events (orders, positions, deposits, withdrawals)
* `listenToKalshiEvent` - Subscribe to market ticker updates for a specific Kalshi event
* `listenToOrderbook` - Subscribe to real-time orderbook snapshots and deltas for a specific market

All methods support automatic reconnection with exponential backoff.

<Info>
  To avoid ambiguity, we denote the smallest possible multiple of USDC (0.000001 USDC) as one `uusdc`,
  which stands for µUSDC (micro-USDC), and denote the smallest possible multiple of a contract (0.01 contract)
  as one `ccontract` (centicontract).

  User-facing USDC balances are specified as fixed-point strings (e.g. `"1.2625"` for USDC). Contract quantities
  in the API and SDK are specified as integer `ccontracts` strings (e.g. `"1050"` for 10.50 contracts at precision 2).
</Info>

## User Events

Subscribe to events for a specific user address.

### `listen`

```typescript theme={null}
listen(
  userAddress: string,
  onEvent: (event: BisonEvent) => void,
  options?: {
    onError?: (error: Error) => void;
    onConnect?: () => void;
    onDisconnect?: () => void;
    reconnect?: boolean;
  }
): () => void
```

#### Parameters

<ParamField path="userAddress" type="string" required>
  User's Ethereum address to listen for events
</ParamField>

<ParamField path="onEvent" type="function" required>
  Callback function that receives events as they occur
</ParamField>

<ParamField path="options.onError" type="function">
  Optional callback for handling WebSocket errors
</ParamField>

<ParamField path="options.onConnect" type="function">
  Optional callback invoked when WebSocket connection is established
</ParamField>

<ParamField path="options.onDisconnect" type="function">
  Optional callback invoked when WebSocket connection closes
</ParamField>

<ParamField path="options.reconnect" type="boolean">
  Enable automatic reconnection (default: `true`). Uses exponential backoff with max 5 attempts.
</ParamField>

#### Returns

A disconnect function to stop listening and close the WebSocket connection.

#### Event Types

The callback receives one of the following event types:

**Order Events:**

* `order_placed` - New order submitted
* `order_filled` - Order matched and executed
* `order_cancelled` - Order cancelled

**Position Events:**

* `position_minted` - Position tokens withdrawn from exchange to wallet
* `position_burned` - Position tokens deposited from wallet back to exchange

**USDC Events:**

* `usdc_deposited` - USDC deposited to vault
* `usdc_withdrawn` - USDC withdrawn from vault

**Market Events:**

* `market_opened` - Market opened for trading
* `market_closed` - Market closed for trading
* `market_settled` - Market outcome determined

#### Example

```typescript theme={null}
import { createBisonClient, ccontractsToContracts, uusdcToUsdc } from '@bison-markets/sdk-ts';

const client = createBisonClient({ 
  baseUrl: 'https://api.bison.markets' 
});

const disconnect = client.listen(
  '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  (event) => {
    switch (event.type) {
      case 'order_placed':
        console.log(`Order placed: ${event.orderId}`, {
          market: event.marketId,
          action: event.action,
          side: event.side,
          contracts: ccontractsToContracts(event.ccontracts, 2),
          price: Number(event.priceUusdc) / 1_000_000,
        });
        break;
      
      case 'order_filled':
        console.log(`Order filled: ${event.orderId}`);
        break;
      
      case 'usdc_deposited':
        console.log(`Deposited ${uusdcToUsdc(event.uusdcAmount)} USDC`);
        break;
      
      case 'position_minted':
        console.log(
          `Withdrew ${ccontractsToContracts(event.ccontracts, 2)} ${event.side} tokens for ${event.marketId} to wallet`
        );
        break;
      
      case 'market_settled':
        console.log(`Market ${event.marketId} settled:`, event.result);
        break;
      
      default:
        console.log('Received event:', event);
    }
  },
  {
    onConnect: () => console.log('Connected to event stream'),
    onDisconnect: () => console.log('Disconnected from event stream'),
    onError: (error) => console.error('WebSocket error:', error),
    reconnect: true,
  }
);

// Later, when you want to stop listening:
// disconnect();
```

## Market Ticker Updates

Subscribe to real-time price and volume updates for a specific Kalshi event.

### `listenToKalshiEvent`

```typescript theme={null}
listenToKalshiEvent(
  eventTicker: string,
  onTicker: (update: KalshiTickerUpdate) => void,
  options?: {
    onError?: (error: Error) => void;
    onConnect?: () => void;
    onDisconnect?: () => void;
    reconnect?: boolean;
  }
): () => void
```

#### Parameters

<ParamField path="eventTicker" type="string" required>
  Kalshi event ticker (e.g., `"KXFEDDECISION-26JAN"`)
</ParamField>

<ParamField path="onTicker" type="function" required>
  Callback function that receives ticker updates for markets in the event
</ParamField>

<ParamField path="options.onError" type="function">
  Optional callback for handling WebSocket errors
</ParamField>

<ParamField path="options.onConnect" type="function">
  Optional callback invoked when WebSocket connection is established
</ParamField>

<ParamField path="options.onDisconnect" type="function">
  Optional callback invoked when WebSocket connection closes
</ParamField>

<ParamField path="options.reconnect" type="boolean">
  Enable automatic reconnection (default: `true`). Uses exponential backoff with max 5 attempts.
</ParamField>

#### Returns

A disconnect function to stop listening and close the WebSocket connection.

#### Ticker Update Fields

<ParamField path="market_ticker" type="string">
  Market ticker identifier
</ParamField>

<ParamField path="yes_bid_uusdc" type="string">
  Current best bid price for YES side in µUSDC (integer string)
</ParamField>

<ParamField path="yes_ask_uusdc" type="string">
  Current best ask price for YES side in µUSDC (integer string)
</ParamField>

<ParamField path="no_bid_uusdc" type="string">
  Current best bid price for NO side in µUSDC (integer string)
</ParamField>

<ParamField path="no_ask_uusdc" type="string">
  Current best ask price for NO side in µUSDC (integer string)
</ParamField>

<ParamField path="last_price_uusdc" type="string">
  Last traded price in µUSDC (integer string)
</ParamField>

<ParamField path="volume" type="number">
  Total trading volume
</ParamField>

<ParamField path="open_interest" type="number">
  Current open interest (outstanding contracts)
</ParamField>

#### Example

```typescript theme={null}
const disconnect = client.listenToKalshiEvent(
  'KXFEDDECISION-26JAN',
  (update) => {
    console.log(`Market: ${update.market_ticker}`);
    
    if (update.yes_bid_uusdc && update.yes_ask_uusdc) {
      const yesBid = Number(update.yes_bid_uusdc) / 1_000_000;
      const yesAsk = Number(update.yes_ask_uusdc) / 1_000_000;
      console.log(`YES: $${yesBid} bid / $${yesAsk} ask`);
    }
    
    if (update.no_bid_uusdc && update.no_ask_uusdc) {
      const noBid = Number(update.no_bid_uusdc) / 1_000_000;
      const noAsk = Number(update.no_ask_uusdc) / 1_000_000;
      console.log(`NO: $${noBid} bid / $${noAsk} ask`);
    }
    
    if (update.last_price_uusdc) {
      console.log(`Last: $${Number(update.last_price_uusdc) / 1_000_000}`);
    }
    
    if (update.volume !== undefined) {
      console.log(`Volume: ${update.volume}`);
    }
    
    if (update.open_interest !== undefined) {
      console.log(`Open Interest: ${update.open_interest}`);
    }
  },
  {
    onConnect: () => console.log('Connected to Kalshi event stream'),
    reconnect: true,
  }
);

// Stop listening when done
// disconnect();
```

## Orderbook Updates

Subscribe to real-time orderbook data for a specific market, including initial snapshots and incremental updates.

### `listenToOrderbook`

```typescript theme={null}
listenToOrderbook(
  marketTicker: string,
  onUpdate: (update: OrderbookUpdate) => void,
  options?: {
    onError?: (error: Error) => void;
    onConnect?: () => void;
    onDisconnect?: () => void;
    reconnect?: boolean;
  }
): () => void
```

#### Parameters

<ParamField path="marketTicker" type="string" required>
  Kalshi market ticker (e.g., `"KXBTC-100K-DEC31"`)
</ParamField>

<ParamField path="onUpdate" type="function" required>
  Callback function that receives orderbook updates (snapshots and deltas)
</ParamField>

<ParamField path="options.onError" type="function">
  Optional callback for handling WebSocket errors
</ParamField>

<ParamField path="options.onConnect" type="function">
  Optional callback invoked when WebSocket connection is established
</ParamField>

<ParamField path="options.onDisconnect" type="function">
  Optional callback invoked when WebSocket connection closes
</ParamField>

<ParamField path="options.reconnect" type="boolean">
  Enable automatic reconnection (default: `true`). Uses exponential backoff with max 5 attempts.
</ParamField>

#### Returns

A disconnect function to stop listening and close the WebSocket connection.

#### Update Types

The callback receives one of two update types:

**Snapshot Update (`OrderbookSnapshot`):**

* Sent immediately upon connection
* Contains the complete current orderbook state
* Fields: `type`, `market_ticker`, `yes`, `no`

**Delta Update (`OrderbookDelta`):**

* Sent for each orderbook change
* Contains incremental updates to specific price levels
* Fields: `type`, `market_ticker`, `side`, `price_uusdc`, `delta`

#### Example

```typescript theme={null}
import { createBisonClient } from '@bison-markets/sdk-ts';

const client = createBisonClient({ 
  baseUrl: 'https://api.bison.markets' 
});

// Track orderbook state
const orderbook = {
  yes: new Map<string, bigint>(),  // price_uusdc -> ccontracts
  no: new Map<string, bigint>()     // price_uusdc -> ccontracts
};

const disconnect = client.listenToOrderbook(
  'KXBTC-100K-DEC31',
  (update) => {
    if (update.type === 'orderbook_snapshot') {
      // Initialize orderbook with full snapshot
      orderbook.yes.clear();
      orderbook.no.clear();
      
      update.yes?.forEach(level => {
        orderbook.yes.set(level.price_uusdc, BigInt(level.ccontracts));
      });
      
      update.no?.forEach(level => {
        orderbook.no.set(level.price_uusdc, BigInt(level.ccontracts));
      });
      
      console.log('Orderbook initialized');
      console.log(`YES levels: ${orderbook.yes.size}`);
      console.log(`NO levels: ${orderbook.no.size}`);
      
    } else if (update.type === 'orderbook_delta') {
      // Apply incremental update
      const book = update.side === 'yes' ? orderbook.yes : orderbook.no;
      const currentCcontracts = book.get(update.price_uusdc) || 0n;
      const newCcontracts = currentCcontracts + BigInt(update.delta);
      
      if (newCcontracts <= 0n) {
        book.delete(update.price_uusdc);
      } else {
        book.set(update.price_uusdc, newCcontracts);
      }
      
      const priceUSDC = (Number(update.price_uusdc) / 1_000_000).toFixed(2);
      console.log(`${update.side.toUpperCase()} @ $${priceUSDC}: ${newCcontracts.toString()} ccontracts`);
    }
  },
  {
    onConnect: () => console.log('Connected to orderbook'),
    onError: (error) => console.error('Orderbook error:', error),
    reconnect: true,
  }
);

// Helper function to get best bid/ask
function getBestPrices() {
  const yesPrices = Array.from(orderbook.yes.keys());
  const noPrices = Array.from(orderbook.no.keys());
  
  return {
    yesBid: yesPrices.length > 0 ? Math.max(...yesPrices) : null,
    yesAsk: yesPrices.length > 0 ? Math.min(...yesPrices) : null,
    noBid: noPrices.length > 0 ? Math.max(...noPrices) : null,
    noAsk: noPrices.length > 0 ? Math.min(...noPrices) : null,
  };
}

// Use after receiving updates
setTimeout(() => {
  const prices = getBestPrices();
  console.log('Best prices:', {
    yes: prices.yesBid && prices.yesAsk 
      ? `${prices.yesBid / 1_000_000} / ${prices.yesAsk / 1_000_000}`
      : 'N/A',
    no: prices.noBid && prices.noAsk 
      ? `${prices.noBid / 1_000_000} / ${prices.noAsk / 1_000_000}`
      : 'N/A',
  });
}, 1000);

// Stop listening when done
// disconnect();
```

## Connection Management

All three methods return a disconnect function that should be called to properly clean up the WebSocket connection:

```typescript theme={null}
const disconnectUser = client.listen(userAddress, onEvent);
const disconnectEvent = client.listenToKalshiEvent(eventTicker, onTicker);
const disconnectOrderbook = client.listenToOrderbook(marketTicker, onUpdate);

// Later, when component unmounts or user logs out:
disconnectUser();
disconnectEvent();
disconnectOrderbook();
```

## Automatic Reconnection

The SDK automatically handles connection failures with exponential backoff:

* Initial reconnect delay: 1 second
* Maximum reconnect delay: 30 seconds
* Maximum reconnect attempts: 5
* Heartbeat interval: 30 seconds

When the maximum number of reconnect attempts is reached, the connection will stop trying to reconnect. You can manually reconnect by calling the listen method again.

## Error Handling

```typescript theme={null}
const disconnect = client.listen(
  userAddress,
  (event) => {
    console.log('Event:', event);
  },
  {
    onError: (error) => {
      // Handle WebSocket errors
      console.error('Connection error:', error.message);
      
      // Optionally notify user or trigger fallback behavior
      showErrorNotification('Lost connection to event stream');
    },
    onDisconnect: () => {
      // Connection closed
      console.log('Disconnected. Will attempt to reconnect...');
    }
  }
);
```

## Important Notes

* WebSocket connections are automatically kept alive with periodic heartbeat messages
* Events are delivered in real-time as they occur on the blockchain or Kalshi platform
* Multiple subscriptions can be active simultaneously (user events, market tickers, orderbooks)
* Reconnection is enabled by default but can be disabled by setting `reconnect: false`
* Always call the disconnect function to avoid memory leaks when cleaning up
* For orderbook subscriptions, always process the initial snapshot before applying deltas
* Orderbook delta updates are cumulative - add them to existing ccontracts, don't replace
