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

# Holder Stream

> Live WebSocket stream of all tracked holders of a token with position, bag percentage, unrealized PnL, remaining value and entry market cap. Solana, BNB, Base, Ethereum and Robinhood.

The live holder table for a token. On subscribe you get a snapshot of every tracked holder with a full position, then live updates as they trade and as the market cap moves. Positions carry bag percentage, unrealized PnL, remaining value and the market cap at entry.

When you subscribe to a specific token, CabalSpy first seeds its history from the database so the initial snapshot is complete, then streams live.

This stream runs on **Solana, BNB, Base, Ethereum and Robinhood**. The event shape is identical on every chain, only the native currency changes.

## Endpoint

Connect to `wss://stream.cabalspy.xyz`.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const ws = new WebSocket("wss://stream.cabalspy.xyz?apiKey=YOUR_KEY");

  ws.onopen = () => {
    ws.send(JSON.stringify({
      op: "subscribe", stream: "holder", blockchain: "solana",
      token: "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
      wallet_types: ["kol", "smart", "whale"], mode: "full",
    }));
  };

  ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.event === "init") {
      console.log("holders", msg.data.holder_count);
    } else if (msg.event === "holder_update") {
      const h = msg.data.holder;
      console.log(h.profile.name, h.position.bag_pct, h.position.unrealized_pnl_usd);
    }
  };
  ```

  ```python Python theme={null}
  import json, websocket

  def on_open(ws):
      ws.send(json.dumps({
          "op": "subscribe", "stream": "holder", "blockchain": "solana",
          "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
          "wallet_types": ["kol", "smart", "whale"], "mode": "full",
      }))

  def on_message(ws, message):
      msg = json.loads(message)
      print(msg.get("event"))

  ws = websocket.WebSocketApp(
      "wss://stream.cabalspy.xyz?apiKey=YOUR_KEY",
      on_open=on_open, on_message=on_message,
  )
  ws.run_forever()
  ```
</CodeGroup>

## Authentication

Provide your API key as a query parameter, wss\://stream.cabalspy.xyz?apiKey=YOUR\_KEY, or as a header, Authorization: Bearer YOUR\_KEY. Invalid keys are closed with code 1008.

## Chains and wallet types

Each chain tracks a different set of holder types, and reports values in its own native currency.

| blockchain | wallet\_types     | Native currency |
| ---------- | ----------------- | --------------- |
| solana     | kol, smart, whale | SOL             |
| bnb        | kol, smart        | BNB             |
| base       | kol, smart        | ETH             |
| eth        | kol               | ETH             |
| rh         | kol, smart        | ETH             |

Leaving wallet\_types out subscribes you to every type that chain supports. Asking for a type a chain does not track is rejected with an error naming the types it does track, so ETH accepts only kol.

Leaving blockchain out defaults to solana, which keeps existing integrations working unchanged.

On EVM chains, contract and wallet addresses are matched case-insensitively and are returned lowercased.

## Subscribe

To start receiving data, send a subscribe message after the connection opens. At a minimum you choose a token to watch and which holder types you care about.

```json Subscribe to one token on Solana theme={null}
{
  "op": "subscribe",
  "stream": "holder",
  "blockchain": "solana",
  "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
  "wallet_types": ["kol", "smart", "whale"],
  "mode": "full"
}
```

```json Subscribe to one token on BNB theme={null}
{
  "op": "subscribe",
  "stream": "holder",
  "blockchain": "bnb",
  "token": "0xYourTokenContractAddress",
  "wallet_types": ["kol", "smart"],
  "mode": "full"
}
```

The token field decides what you watch. Use a token mint address (Solana) or contract address (EVM) to follow one token, or the value "\*" to follow every tracked token at once. Following one token also gives you an initial snapshot of its current holders, "\*" does not.

```json Follow every token theme={null}
{
  "op": "subscribe",
  "stream": "holder",
  "blockchain": "base",
  "token": "*",
  "wallet_types": ["kol"]
}
```

<ParamField body="op" type="string" required>
  The operation. Use subscribe to start.
</ParamField>

<ParamField body="stream" type="string" required>
  Set to holder for this stream.
</ParamField>

<ParamField body="blockchain" type="string">
  Chain. One of solana, bnb, base, eth, rh. Defaults to solana.
</ParamField>

<ParamField body="token" type="string" required>
  Which token to watch. Either a token mint or contract address for one token, or "\*" for all tracked tokens. A single address also returns a starting snapshot of its holders.
</ParamField>

<ParamField body="wallet_types" type="array">
  Which holder types to include. See the table above. Defaults to every type the chain supports.
</ParamField>

<ParamField body="mode" default="full" type="string">
  How much you want to receive. See the modes below.
</ParamField>

<ParamField body="mc_interval" type="integer">
  Optional. Only used with mode full. See the modes below.
</ParamField>

### Choosing a mode

The mode decides whether you also receive live price driven updates, not just trades. This is the main control over how many messages, and how many credits, you use.

| mode   | What you receive                                                                                                                  | Can you set mc\_interval |
| ------ | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| full   | Trades plus live position updates every time the market cap moves. This is the default.                                           | Yes                      |
| events | Only real trade events. No market cap updates, so unrealized PnL and remaining value do not tick between trades. Cheapest option. | No, it is ignored        |

With mode full you can slow down the market cap updates to save credits. Set mc\_interval to a number of seconds from 1 to 30, and you get at most one price update per token in that window. Leave mc\_interval out to get every update live.

```json Full, but at most one price update every 5 seconds theme={null}
{
  "op": "subscribe",
  "stream": "holder",
  "blockchain": "solana",
  "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
  "mode": "full",
  "mc_interval": 5
}
```

```json Events only, no price updates at all theme={null}
{
  "op": "subscribe",
  "stream": "holder",
  "blockchain": "solana",
  "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
  "mode": "events"
}
```

Setting mc\_interval together with mode events does nothing, because events never sends price updates in the first place.

## Unsubscribe and other operations

To stop receiving a token, send the same chain and token with op unsubscribe.

```json Unsubscribe theme={null}
{
  "op": "unsubscribe",
  "stream": "holder",
  "blockchain": "solana",
  "token": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump"
}
```

To see what you are currently subscribed to, send op subscriptions. To check the connection is alive, send op ping and you get a pong back.

```json List your subscriptions theme={null}
{ "op": "subscriptions", "stream": "holder" }
```

## Events

The stream emits three event types. init on subscribe, holder\_update on each trade, and position\_update when the market cap moves (subject to mode and mc\_interval).

<ResponseField name="event" type="string">
  init, holder\_update or position\_update.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="blockchain" type="string">
      The chain. solana, bnb, base, eth or rh.
    </ResponseField>

    <ResponseField name="mint" type="string">
      The token mint or contract address.
    </ResponseField>

    <ResponseField name="token" type="object">
      Token block. See the token fields below.
    </ResponseField>

    <ResponseField name="holder" type="object">
      Present on holder\_update. The single holder that traded, with profile and position. See the position fields below.
    </ResponseField>

    <ResponseField name="holders" type="array">
      Present on init and position\_update. The list of holders, each with wallet, wallet\_type, win\_rate, profile and position.
    </ResponseField>

    <ResponseField name="holder_count" type="integer">
      Present on init and position\_update. Number of holders in the list.
    </ResponseField>

    <ResponseField name="transaction" type="object">
      Present on holder\_update. The trade that triggered the update, with signature, slot, action, created\_at, token\_amount and a value object.
    </ResponseField>
  </Expandable>
</ResponseField>

### token

<ResponseField name="mint" type="string">
  Token mint or contract address.
</ResponseField>

<ResponseField name="blockchain" type="string">
  The chain this token lives on.
</ResponseField>

<ResponseField name="symbol" type="string">
  Token symbol.
</ResponseField>

<ResponseField name="name" type="string">
  Token name.
</ResponseField>

<ResponseField name="supply" type="number">
  Total supply, or null.
</ResponseField>

<ResponseField name="decimals" type="integer">
  Token decimals, or null.
</ResponseField>

<ResponseField name="market_cap" type="number">
  Market cap in the chain native currency, or null when no price is known yet.
</ResponseField>

<ResponseField name="market_cap_usd" type="number">
  Market cap in USD.
</ResponseField>

<ResponseField name="market_cap_currency" type="string">
  The native currency the market cap is denominated in. SOL, BNB or ETH.
</ResponseField>

<ResponseField name="price" type="number">
  Token price in the native currency.
</ResponseField>

<ResponseField name="price_usd" type="number">
  Token price in USD.
</ResponseField>

<ResponseField name="native_price_usd" type="number">
  Price of the chain native currency in USD, used for every conversion in this payload.
</ResponseField>

<ResponseField name="sol_price_usd" type="number">
  Same value as native\_price\_usd. Kept for backwards compatibility, despite the name it carries BNB or ETH on those chains. Prefer native\_price\_usd.
</ResponseField>

<ResponseField name="pool" type="string">
  Pool or bonding curve address, or null.
</ResponseField>

<ResponseField name="on_curve" type="boolean">
  True while the token still trades on a bonding curve, false once it has migrated to a regular AMM. Null when the launchpad is not recognised. Bonding curves exist on every chain, for example pump.fun on Solana, four.meme on BNB and moonshot on Base.
</ResponseField>

<ResponseField name="bonding_curve_progress" type="number">
  Bonding curve completion percentage. Solana only, null on EVM chains.
</ResponseField>

### position

Every holder carries a position with these fields. The fields ending in \_sol are named for historical reasons and carry the **chain native currency**, so BNB on BNB Chain and ETH on Base, Ethereum and Robinhood. Use market\_cap\_currency from the token block to label them.

<ResponseField name="held" type="number">
  Tokens currently held.
</ResponseField>

<ResponseField name="peak" type="number">
  Peak tokens held.
</ResponseField>

<ResponseField name="bought_tokens" type="number">
  Total tokens bought.
</ResponseField>

<ResponseField name="sold_tokens" type="number">
  Total tokens sold.
</ResponseField>

<ResponseField name="bag_pct" type="number">
  Percent of peak holding still held.
</ResponseField>

<ResponseField name="supply_pct" type="number">
  Percent of supply held.
</ResponseField>

<ResponseField name="remaining_sol" type="number">
  Current value of the remaining bag in the native currency.
</ResponseField>

<ResponseField name="remaining_usd" type="number">
  Current value of the remaining bag in USD.
</ResponseField>

<ResponseField name="invested" type="number">
  Total invested in the native currency.
</ResponseField>

<ResponseField name="invested_usd" type="number">
  Total invested in USD.
</ResponseField>

<ResponseField name="max_single_buy" type="number">
  Largest single buy in the native currency.
</ResponseField>

<ResponseField name="buy_txn" type="integer">
  Number of buys.
</ResponseField>

<ResponseField name="sell_txn" type="integer">
  Number of sells.
</ResponseField>

<ResponseField name="sold_value" type="number">
  Total sold value in the native currency.
</ResponseField>

<ResponseField name="sold_value_usd" type="number">
  Total sold value in USD.
</ResponseField>

<ResponseField name="sold" type="boolean">
  Whether the wallet has sold any amount.
</ResponseField>

<ResponseField name="realized_pnl_sol" type="number">
  Realized PnL in the native currency, total sold minus total bought.
</ResponseField>

<ResponseField name="realized_pnl_usd" type="number">
  Realized PnL in USD.
</ResponseField>

<ResponseField name="entry_market_cap" type="number">
  Market cap in the native currency when the wallet first bought, frozen.
</ResponseField>

<ResponseField name="entry_market_cap_usd" type="number">
  Entry market cap in USD, frozen.
</ResponseField>

<ResponseField name="unrealized_pnl_sol" type="number">
  Unrealized PnL on the remaining bag in the native currency.
</ResponseField>

<ResponseField name="unrealized_pnl_usd" type="number">
  Unrealized PnL in USD.
</ResponseField>

<ResponseField name="unrealized_pnl_pct" type="number">
  Unrealized PnL percentage.
</ResponseField>

<ResponseField name="first_buy_at" type="string">
  Timestamp of the first buy.
</ResponseField>

<ResponseField name="last_activity_at" type="string">
  ISO timestamp of the last activity.
</ResponseField>

<Note>
  unrealized\_pnl and remaining are only filled while a current market cap for the token is known. If a token has not traded since the stream started, these stay null until its first price arrives.
</Note>

<ResponseExample>
  ```json holder_update theme={null}
  {
    "success": true,
    "channel": "holder.solana",
    "event": "holder_update",
    "data": {
      "trigger": "transaction",
      "blockchain": "solana",
      "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
      "token": {
        "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
        "blockchain": "solana",
        "symbol": "invisibull",
        "name": "invisibull",
        "supply": 1000000000,
        "decimals": 6,
        "market_cap": 40.08,
        "market_cap_usd": 3301.2,
        "market_cap_currency": "SOL",
        "price": 4.008e-8,
        "price_usd": 3.3e-6,
        "sol_price_usd": 82.36,
        "native_price_usd": 82.36,
        "pool": "pump",
        "on_curve": true,
        "bonding_curve_progress": 42.5
      },
      "transaction": {
        "signature": "5xr8abc...",
        "slot": 301948220,
        "action": "buy",
        "transaction_type": "buy",
        "created_at": "2026-07-04T16:25:34Z",
        "token_amount": 22113972.03,
        "value": { "currency": "SOL", "amount": 1.66, "amount_usd": 137.44 }
      },
      "holder": {
        "wallet": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW",
        "wallet_type": "kol",
        "win_rate": 61.34,
        "profile": {
          "name": "Maze",
          "image_url": "https://cabalspy.xyz/images/7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW.png",
          "twitter": "https://x.com/MazeCCC",
          "telegram": "",
          "blockchain": "solana",
          "currency": "SOL",
          "type": "kol"
        },
        "position": {
          "held": 22113972.03,
          "peak": 22113972.03,
          "bought_tokens": 22113972.03,
          "sold_tokens": 0.0,
          "bag_pct": 100.0,
          "supply_pct": 2.2114,
          "remaining_sol": 0.886537,
          "remaining_usd": 73.02,
          "invested": 1.66,
          "invested_usd": 137.44,
          "max_single_buy": 1.66,
          "buy_txn": 1,
          "sell_txn": 0,
          "sold_value": 0.0,
          "sold_value_usd": 0.0,
          "sold": false,
          "realized_pnl_sol": -1.66,
          "realized_pnl_usd": -137.44,
          "entry_market_cap": 44.6,
          "entry_market_cap_usd": 3673.2,
          "unrealized_pnl_sol": -0.099817,
          "unrealized_pnl_usd": -8.22,
          "unrealized_pnl_pct": -10.12,
          "first_buy_at": "2026-07-04T16:25:34Z",
          "last_activity_at": "2026-07-04T16:40:10Z"
        }
      }
    },
    "meta": {
      "request_id": "req_5162c7cf7f87",
      "version": "2.0.0",
      "timestamp": "2026-07-06T11:01:57Z"
    }
  }
  ```

  ```json holder_update on BNB theme={null}
  {
    "success": true,
    "channel": "holder.bnb",
    "event": "holder_update",
    "data": {
      "trigger": "transaction",
      "blockchain": "bnb",
      "mint": "0x1a2b3c4d5e6f7890abcdef1234567890abcdef12",
      "token": {
        "mint": "0x1a2b3c4d5e6f7890abcdef1234567890abcdef12",
        "blockchain": "bnb",
        "symbol": "FOUR",
        "name": "Four Token",
        "supply": 1000000000,
        "decimals": 18,
        "market_cap": 1250.5,
        "market_cap_usd": 750300.0,
        "market_cap_currency": "BNB",
        "price": 0.00000125,
        "price_usd": 0.00075,
        "sol_price_usd": 600.0,
        "native_price_usd": 600.0,
        "pool": "0xpool1234567890abcdef1234567890abcdef1234",
        "on_curve": true,
        "bonding_curve_progress": null
      },
      "transaction": {
        "signature": "0xabc123...",
        "slot": 41255903,
        "action": "buy",
        "transaction_type": "buy",
        "created_at": "2026-07-17T09:12:04Z",
        "token_amount": 400000.0,
        "value": { "currency": "BNB", "amount": 0.5, "amount_usd": 300.0 }
      },
      "holder": {
        "wallet": "0x7a16ff8270133f063aab6c9977183d9e72835428",
        "wallet_type": "kol",
        "win_rate": 58.2,
        "profile": {
          "name": "BNB Whale",
          "image_url": null,
          "twitter": "",
          "telegram": "",
          "blockchain": "bnb",
          "currency": "BNB",
          "type": "kol"
        },
        "position": {
          "held": 400000.0,
          "peak": 400000.0,
          "bought_tokens": 400000.0,
          "sold_tokens": 0.0,
          "bag_pct": 100.0,
          "supply_pct": 0.04,
          "remaining_sol": 0.5,
          "remaining_usd": 300.0,
          "invested": 0.5,
          "invested_usd": 300.0,
          "max_single_buy": 0.5,
          "buy_txn": 1,
          "sell_txn": 0,
          "sold_value": 0.0,
          "sold_value_usd": 0.0,
          "sold": false,
          "realized_pnl_sol": -0.5,
          "realized_pnl_usd": -300.0,
          "entry_market_cap": 1250.5,
          "entry_market_cap_usd": 750300.0,
          "unrealized_pnl_sol": 0.0,
          "unrealized_pnl_usd": 0.0,
          "unrealized_pnl_pct": 0.0,
          "first_buy_at": "2026-07-17T09:12:04Z",
          "last_activity_at": "2026-07-17T09:12:04Z"
        }
      }
    },
    "meta": {
      "request_id": "req_9a1b2c3d4e5f",
      "version": "2.0.0",
      "timestamp": "2026-07-17T09:12:05Z"
    }
  }
  ```
</ResponseExample>

## Billing

Each delivered event counts against your plan. In mode full a busy token can produce frequent position\_update events as the market cap moves, use mc\_interval to throttle them or mode events to receive only trades.

## Heartbeat

The server sends WebSocket pings and expects pong, which standard clients answer automatically. You can also send op ping at any time.
