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

# Balance Stream

> Beta WebSocket stream for cross-chain wallet balances built on Mobula's transfer feed

<Tip>
  The Balance stream is currently in **beta**. It is not listed in the public dashboard yet and payloads may evolve without notice until the stream is fully GA.
</Tip>

## Why balance stream?

Mobula already offers two portfolio-centric streams:

* **`position` / `positions`** for mark-to-market updates with live PnL calculations
* **`balance`** (this stream) for *available balances* per wallet/token pair across all supported EVM chains

The balance stream is powered by the same curated transfers feed that fuels our internal accounting jobs. It favors **coverage over latency**—it is a few hundred milliseconds slower than the `positions` stream, but it captures *any* balance change (DEX swaps, direct transfers, NFT airdrops, contract sends, cross-chain bridges, etc.). It is the recommended source when you need to know whether a wallet can safely execute a trade right now.

## Endpoint details

* **URL**: `wss://api.mobula.io`
* **Event type**: `balance`
* **Auth**: Pass your API key inside the `authorization` field of the WebSocket message body
* **Scope**: Every wallet/token pair you include in `items` counts as one logical subscription

<Note>
  Use this stream alongside the REST portfolio routes for reconciliation. Because it is beta-only, backwards compatibility is not guaranteed yet.
</Note>

## Subscription payload

Send a JSON message with `type: "balance"` and provide the wallets you care about:

```json theme={null}
{
  "type": "balance",
  "authorization": "YOUR-API-KEY",
  "payload": {
    "items": [
      {
        "wallet": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
        "token": "native",
        "blockchain": "evm:1"
      },
      {
        "wallet": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
        "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "blockchain": "evm:1"
      },
      {
        "wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045",
        "token": "native",
        "blockchain": "evm:8453"
      }
    ],
    "subscriptionId": "wallet-balance-tracker",
    "subscriptionTracking": true
  }
}
```

### Parameters

| Field                  | Type                                  | Description                                                                                                                                                                                         |
| ---------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `items`                | `Array<BalanceItem>`                  | List of wallet/token pairs to monitor. Each entry is processed independently.                                                                                                                       |
| `wallet`               | `string`                              | Wallet address in any case. The stream stores it in checksum format automatically.                                                                                                                  |
| `token`                | `string`                              | Token contract address. Use `"native"` to track the chain’s native asset (internally mapped to `0xeeee...`).                                                                                        |
| `blockchain`           | `string`                              | Mobula chain identifier (e.g., `evm:1`, `evm:137`, `evm:8453`). The beta release currently covers all supported EVM chains.                                                                         |
| `subscriptionId`       | `string` (optional)                   | Provide one to manage the subscription yourself. If omitted, the server creates a deterministic ID. Resending the same ID with additional items will append them without duplicating existing ones. |
| `subscriptionTracking` | `boolean` (optional, default `false`) | When `true`, the server acknowledges every subscribe/unsubscribe event with a friendly message.                                                                                                     |

<Tip>
  Need to add tokens later? Re-send the same payload (same `subscriptionId`) with the extra items. The stream detects the new wallet/token pairs and only delivers fresh data for them.
</Tip>

## Initial snapshot

You immediately receive the latest on-chain balance for every requested pair. Data is fetched via the portfolio service (RPC + DB) and normalized before being sent:

```json theme={null}
{
  "subscriptionId": "wallet-balance-tracker",
  "data": [
    {
      "wallet": "0x742D35Cc6634C0532925a3b844Bc9e7595f0bEb",
      "token": "native",
      "chainId": "evm:1",
      "balance": 4.3175,
      "rawBalance": "4317500000000000000",
      "decimals": 18,
      "symbol": "ETH",
      "name": "Ethereum",
      "logo": "https://cdn.mobula.io/eth.png"
    },
    {
      "wallet": "0x742D35Cc6634C0532925a3b844Bc9e7595f0bEb",
      "token": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
      "chainId": "evm:1",
      "balance": 125000.42,
      "rawBalance": "125000420000",
      "decimals": 6,
      "symbol": "USDC",
      "name": "USD Coin",
      "logo": "https://cdn.mobula.io/usdc.png"
    }
  ]
}
```

## Update events

Every update contains a single `WalletBalanceUpdate` object along with the `subscriptionId`. Updates are triggered by two sources:

1. **Curated swap feed** — fast deltas for swaps already observed by Mobula (comparable to the `positions` stream).
2. **Transfer extractor** — replayed L1 transfers (including bridge contracts and custom token transfers). This guarantees coverage even when no swap occurred.

```json theme={null}
{
  "subscriptionId": "wallet-balance-tracker",
  "data": {
    "wallet": "0x742D35Cc6634C0532925a3b844Bc9e7595f0bEb",
    "token": "native",
    "chainId": "evm:1",
    "balance": 3.8175,
    "rawBalance": "3817500000000000000",
    "decimals": 18,
    "symbol": "ETH",
    "name": "Ethereum",
    "logo": "https://cdn.mobula.io/eth.png",
    "previousBalance": 4.3175,
    "previousRawBalance": "4317500000000000000"
  }
}
```

Expect swap-driven updates in \<1 s and transfer-driven updates in \~1-3 blocks (depending on the chain and RabbitMQ batch size). Because the stream reconciles every delta with Redis + DB snapshots, balances never drift for wallets that pause activity for a while.

## Balance fields

| Field                | Type                          | Description                                     |
| -------------------- | ----------------------------- | ----------------------------------------------- |
| `wallet`             | `string`                      | Checksummed wallet address.                     |
| `token`              | `string`                      | `"native"` or token contract address.           |
| `chainId`            | `string`                      | Chain identifier (`evm:1`, `evm:8453`, ...).    |
| `balance`            | `number`                      | Human-readable balance with decimals applied.   |
| `rawBalance`         | `string`                      | Raw balance (wei-style integer).                |
| `decimals`           | `number`                      | ERC-20 decimals used to derive `balance`.       |
| `symbol`             | `string`                      | Token symbol resolved by the portfolio service. |
| `name`               | `string`                      | Token name.                                     |
| `logo`               | `string \| null`              | CDN logo URL when available.                    |
| `previousBalance`    | `number` (update events only) | Last balance before the delta was applied.      |
| `previousRawBalance` | `string` (update events only) | Last raw balance.                               |

## Data flow & latency profile

* **Transfers-first**: Every message ultimately comes from Mobula's transfer extractor, so even non-market interactions (OTC transfers, airdrops, gas refunds) are reflected.
* **Curated swap shortcuts**: While waiting for transfer batches, swaps also emit deltas from the curated events stream to keep hot wallets responsive.
* **Redis caching (10 s TTL)**: Prevents redundant queries when several clients watch the same item; also protects downstream RPCs.
* **DB reconciliation**: Transfer batches trigger batched DB reads to re-sync balances after large bursts of activity or when exotic chains (listed in `EXOTIC_CHAINS`) are involved.

In practice you should expect:

* **Swaps**: \~300–700 ms slower than the `positions` stream
* **Pure transfers**: \~1–5 s after the block is finalized (chain-dependent)
* **Backfills**: The stream replays cached balances whenever you reconnect or re-send the payload.

## Example implementation

<CodeGroup>
  ```javascript JavaScript/TypeScript theme={null}
  const ws = new WebSocket('wss://api.mobula.io');

  const payload = {
    type: 'balance',
    authorization: process.env.MOBULA_API_KEY,
    payload: {
      subscriptionId: 'wallet-balance-tracker',
      subscriptionTracking: true,
      items: [
        { wallet: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', token: 'native', blockchain: 'evm:1' },
        { wallet: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', token: '0xA0b8...eB48', blockchain: 'evm:1' }
      ],
    },
  };

  ws.onopen = () => ws.send(JSON.stringify(payload));

  ws.onmessage = (event) => {
    const message = JSON.parse(event.data);
    if (message.event === 'subscribed') {
      console.log('Ready:', message);
      return;
    }

    const data = Array.isArray(message.data) ? message.data : [message.data];
    for (const balance of data) {
      console.log(
        `[${balance.chainId}] ${balance.wallet} → ${balance.symbol}: ${balance.balance} (prev ${balance.previousBalance ?? balance.balance})`,
      );
    }
  };

  ws.onerror = (error) => console.error('Balance stream error', error);
  ```

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

  PAYLOAD = {
      "type": "balance",
      "authorization": os.environ["MOBULA_API_KEY"],
      "payload": {
          "subscriptionId": "wallet-balance-tracker",
          "items": [
              {"wallet": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", "token": "native", "blockchain": "evm:1"},
              {"wallet": "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", "token": "native", "blockchain": "evm:8453"},
          ],
      },
  }

  def on_open(ws):
      ws.send(json.dumps(PAYLOAD))

  def on_message(ws, message):
      data = json.loads(message)
      if data.get("event") == "subscribed":
          print("Subscribed:", data)
          return

      balances = data["data"] if isinstance(data["data"], list) else [data["data"]]
      for balance in balances:
          prev = balance.get("previousBalance")
          print(f'{balance["wallet"]} {balance["symbol"]}: {balance["balance"]} (prev {prev})')

  ws = websocket.WebSocketApp("wss://api.mobula.io", on_open=on_open, on_message=on_message)
  ws.run_forever()
  ```
</CodeGroup>

## Use cases

<CardGroup cols={2}>
  <Card title="Pre-trade checks" icon="shield">
    Prevent failed swaps by verifying that a wallet still has the tokens you expect before sending the transaction.
  </Card>

  <Card title="Custody dashboards" icon="chart-simple">
    Mirror your hot-wallet balances across chains without running your own balance indexers.
  </Card>

  <Card title="Risk controls" icon="triangle-exclamation">
    Trigger alerts if a whale moves funds across chains or drains a vault outside of normal trading hours.
  </Card>

  <Card title="Automation" icon="robot">
    Feed state machines or bots that must keep track of spending allowances in near real time.
  </Card>
</CardGroup>

## Error handling & best practices

1. **Deterministic IDs**: Store the `subscriptionId` returned by the server and reuse it whenever you reconnect so the backend can deduplicate state.
2. **Per-chain batching**: Keep unrelated wallets in separate connections if you plan to monitor hundreds of balances to avoid hitting the 100-item soft limit per socket.
3. **Idempotent consumers**: Updates always include the latest absolute balance. Use them as truth instead of applying your own deltas.
4. **Native alias**: Prefer `"native"` over hardcoding `0xeeee...` in clients—the server takes care of normalization.

Common error payload:

```json theme={null}
{
  "event": "error",
  "type": "balance",
  "message": "Invalid payload for balance subscription",
  "details": [
    {
      "code": "invalid_type",
      "path": ["items", 0, "wallet"],
      "message": "Expected string, received undefined"
    }
  ],
  "subscriptionId": "wallet-balance-tracker"
}
```

## Rate limits

* **Per connection**: Up to 100 wallet/token pairs per WebSocket (same as other indexing streams).
* **Per account**: Standard Mobula WebSocket rate limits apply (burst + sustained). Contact support if you need dedicated throughput for the beta.
* **Payload size**: Keep `items` lists under 1,000 entries to avoid server-side validation failures.

## Related streams

<CardGroup cols={2}>
  <Card title="Positions Stream" icon="coins" href="/indexing-stream/stream/websocket/wss-positions-stream">
    Track every token in a wallet with live PnL & trade stats.
  </Card>

  <Card title="Position Stream (Single Token)" icon="circle-info" href="/indexing-stream/stream/websocket/wss-position-stream">
    Focus on one wallet/token pair with higher refresh frequency.
  </Card>

  <Card title="Holders Stream" icon="users" href="/indexing-stream/stream/websocket/holders-stream">
    Analyze holder cohorts and wallet labels for a token.
  </Card>
</CardGroup>
