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

# useUserBalances

> Real-time balance tracking and updates via WebSocket

<Note>
  **New in v0.5.6** - Real-time balance tracking with WebSocket updates
</Note>

Subscribe to real-time updates for all your token balances. Automatically fetches initial balances via REST API and keeps them synchronized with WebSocket events.

```tsx theme={null}
import { useUserBalances } from "@0xmonaco/react";

const {
  balances,
  loading,
  subscribed,
  error,
  clearError,
  refresh
} = useUserBalances();
```

## Return Values

<ResponseField name="balances" type="AccountBalance[]">
  Array of user token balances, automatically updated in real-time. Merged from REST API initial data and WebSocket updates.
</ResponseField>

<ResponseField name="loading" type="boolean">
  `true` while fetching initial balances from REST API, `false` once loaded
</ResponseField>

<ResponseField name="subscribed" type="boolean">
  `true` when WebSocket subscription is active, `false` otherwise
</ResponseField>

<ResponseField name="error" type="Error | null">
  Error object if something went wrong during fetch or subscription, `null` otherwise
</ResponseField>

<ResponseField name="clearError" type="() => void">
  Function to clear the current error state
</ResponseField>

<ResponseField name="refresh" type="() => Promise<void>">
  Manually refresh balances from REST API (useful for force refresh after errors or manual updates)
</ResponseField>

## Features

### Hybrid Data Loading

1. **Initial Load**: reads every page of balances via REST (`getUserBalances()`), which is paginated — a single page would leave assets past the first unreachable.
2. **WebSocket Subscription**: subscribes to the `balances` channel after the initial load.
3. **Race Condition Prevention**: the subscription only opens once the REST data is in, so an event cannot be overwritten by the response it raced.
4. **Subscribe-time Snapshot**: the channel's [snapshot](/sdk/typescript/websockets#subscribe-time-snapshots) carries every balance, and the hook applies it to the list it already has — no second REST read.

### Applying the snapshot

The snapshot is the complete balance set, which makes it different from a stream of updates in two ways:

* Its totals and `version` are taken **verbatim** from the same durable spot row as REST.
* A held asset the snapshot **omits** is zeroed rather than left showing a figure that is no longer true. The row itself stays, so a portfolio renders "0 WSEI" instead of losing the line. A held row carrying a real `version` is the exception: a versioned zero is an explicit tombstone, so omission cannot be read as "this went to zero" and the row is retained until a newer one arrives.

This matters most on reconnect: the resubscribe brings a fresh snapshot, and balances that moved while the socket was down are corrected from it without a REST round trip.

### Real-time Updates

The hook listens to balance update events triggered by:

* **Deposits** - Tokens deposited to the vault
* **Withdrawals** - Tokens withdrawn from the vault
* **Order Locking** - Balance locked when placing orders
* **Order Unlocking** - Locked balance released when orders are cancelled or expire
* **Trades** - Balance changes from order fills and fee deductions
* **Margin Transfers** - Collateral moved into or out of a margin account

<Note>
  A versioned spot event replaces the whole spot row only when its `version` is
  higher. Equal or lower versions are idempotent. A collateral transfer that
  moves the spot wallet row is versioned like any other spot event. Margin
  collateral notifications — a parent to risk-bucket allocation, a
  margin-routed deposit — remain unversioned because they are not
  authoritative `user_balances` rows; the hook applies those notifications
  fail-open without changing the held spot version. Refresh margin-account state
  separately. `updatedAt` is never used to order rows.
</Note>

### Automatic Balance Updates

When a balance update event is received:

* If the token already exists in state, the balance is updated with the new values
* If a new token appears, the hook re-reads from REST — the wire payload carries no `assetId`, `decimals` or wrapped-native flag, so a new asset cannot be built from an event alone
* Balance updates include both normalized (human-readable) and raw (wei) values

That background re-read **merges** rather than replaces. It runs with the subscription live, so its response is already potentially stale by the time it lands; it therefore adds only genuinely new assets and leaves every held row's live values untouched. Wire state for an unknown asset is buffered until the read supplies its metadata, then replayed through the same version arbitration as an ordinary event or snapshot. The read is also coalesced — a burst of frames for one unknown asset costs a single read plus one trailing read, rather than one per frame — and it does not raise `loading`, which describes the initial load. The public `refresh()` re-reads the full set and applies the same version arbitration: a fetched row supersedes the held one unless the held row carries a newer `version`, and a held versioned row the fetch omits is retained rather than dropped. So it is a version-aware refresh, not a blind overwrite — an in-flight REST read cannot roll a balance back to a state the stream has already moved past.

### Authentication Required

This hook requires an authenticated session. Log in with the [`useAuth`](/sdk/react/hooks/use-auth) hook (or `sdk.login(...)`) before using it.

## Basic Usage

```tsx theme={null}
import { useUserBalances } from "@0xmonaco/react";

function BalancesList() {
  const { balances, loading, subscribed, error } = useUserBalances();

  if (loading) {
    return <div>Loading balances...</div>;
  }

  if (error) {
    return <div>Error: {error.message}</div>;
  }

  return (
    <div>
      <p>WebSocket: {subscribed ? "Connected" : "Disconnected"}</p>
      <h2>Your Balances</h2>
      {balances.map(balance => (
        <div key={balance.assetId}>
          <strong>{balance.symbol || balance.token.slice(0, 8)}</strong>
          <div>Available: {balance.availableBalance}</div>
          <div>Locked: {balance.lockedBalance}</div>
          <div>Total: {balance.totalBalance}</div>
        </div>
      ))}
    </div>
  );
}
```

## With Refresh Button

```tsx theme={null}
function BalancesWithRefresh() {
  const { balances, loading, refresh } = useUserBalances();

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
        <h2>Your Balances</h2>
        <button onClick={refresh} disabled={loading}>
          {loading ? "Refreshing..." : "Refresh"}
        </button>
      </div>

      {balances.map(balance => (
        <div key={balance.assetId}>
          <h3>{balance.symbol || "Unknown Token"}</h3>
          <p>Available: {balance.availableBalance}</p>
          <p>Locked: {balance.lockedBalance}</p>
          <p>Total: {balance.totalBalance}</p>
        </div>
      ))}
    </div>
  );
}
```

## Filter Non-Zero Balances

```tsx theme={null}
function ActiveBalances() {
  const { balances } = useUserBalances();

  // Filter balances with non-zero total
  const activeBalances = balances.filter(
    balance => parseFloat(balance.totalBalance) > 0
  );

  return (
    <div>
      <h2>Active Balances ({activeBalances.length})</h2>
      {activeBalances.map(balance => (
        <div key={balance.assetId}>
          <div>
            <strong>{balance.symbol}</strong>
            <span>{balance.totalBalance}</span>
          </div>
          {parseFloat(balance.lockedBalance) > 0 && (
            <div style={{ fontSize: "0.9em", color: "#666" }}>
              Locked: {balance.lockedBalance}
            </div>
          )}
        </div>
      ))}
    </div>
  );
}
```

## Balance Summary with Charts

```tsx theme={null}
import { useUserBalances } from "@0xmonaco/react";

function BalanceSummary() {
  const { balances, loading } = useUserBalances();

  const totalTokens = balances.length;
  const activeTokens = balances.filter(b => parseFloat(b.totalBalance) > 0).length;
  const totalLocked = balances.reduce(
    (sum, b) => sum + parseFloat(b.lockedBalance || "0"),
    0
  );

  if (loading) {
    return <div>Loading balances...</div>;
  }

  return (
    <div>
      <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr 1fr", gap: "1rem" }}>
        <div style={{ padding: "1rem", border: "1px solid #ddd", borderRadius: "8px" }}>
          <p style={{ fontSize: "0.9em", color: "#666" }}>Total Tokens</p>
          <p style={{ fontSize: "2em", fontWeight: "bold" }}>{totalTokens}</p>
        </div>
        <div style={{ padding: "1rem", border: "1px solid #ddd", borderRadius: "8px" }}>
          <p style={{ fontSize: "0.9em", color: "#666" }}>Active Tokens</p>
          <p style={{ fontSize: "2em", fontWeight: "bold" }}>{activeTokens}</p>
        </div>
        <div style={{ padding: "1rem", border: "1px solid #ddd", borderRadius: "8px" }}>
          <p style={{ fontSize: "0.9em", color: "#666" }}>Tokens with Locked Balance</p>
          <p style={{ fontSize: "2em", fontWeight: "bold" }}>
            {balances.filter(b => parseFloat(b.lockedBalance) > 0).length}
          </p>
        </div>
      </div>

      <div style={{ marginTop: "2rem" }}>
        <h3>Balance Details</h3>
        {balances.map(balance => (
          <div
            key={balance.assetId}
            style={{
              padding: "1rem",
              border: "1px solid #eee",
              borderRadius: "4px",
              marginBottom: "0.5rem"
            }}
          >
            <div style={{ display: "flex", justifyContent: "space-between" }}>
              <strong>{balance.symbol || "Unknown"}</strong>
              <span>{balance.totalBalance}</span>
            </div>
            <div style={{ fontSize: "0.85em", color: "#666", marginTop: "0.5rem" }}>
              <span>Available: {balance.availableBalance}</span>
              {parseFloat(balance.lockedBalance) > 0 && (
                <span style={{ marginLeft: "1rem" }}>Locked: {balance.lockedBalance}</span>
              )}
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}
```

## Real-time Balance Updates

```tsx theme={null}
function LiveBalanceTracker({ tokenSymbol }: { tokenSymbol: string }) {
  const { balances, subscribed } = useUserBalances();

  const balance = balances.find(b => b.symbol === tokenSymbol);

  if (!balance) {
    return <div>Balance for {tokenSymbol} not found</div>;
  }

  const lockedPercentage = parseFloat(balance.totalBalance) > 0
    ? (parseFloat(balance.lockedBalance) / parseFloat(balance.totalBalance)) * 100
    : 0;

  return (
    <div>
      <div style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
        <div style={{
          width: "8px",
          height: "8px",
          borderRadius: "50%",
          backgroundColor: subscribed ? "#4caf50" : "#999"
        }} />
        <span style={{ fontSize: "0.9em", color: "#666" }}>
          {subscribed ? "Live Updates Active" : "Disconnected"}
        </span>
      </div>

      <h3>{balance.symbol || "Unknown Token"}</h3>

      <div style={{ marginTop: "1rem" }}>
        <div style={{ marginBottom: "0.5rem" }}>
          <strong>Total Balance:</strong> {balance.totalBalance}
        </div>
        <div style={{ marginBottom: "0.5rem" }}>
          <strong>Available:</strong> {balance.availableBalance}
        </div>
        <div style={{ marginBottom: "0.5rem" }}>
          <strong>Locked:</strong> {balance.lockedBalance}
        </div>
      </div>

      {parseFloat(balance.lockedBalance) > 0 && (
        <div style={{ marginTop: "1rem" }}>
          <p style={{ fontSize: "0.9em", marginBottom: "0.5rem" }}>
            Locked: {lockedPercentage.toFixed(2)}%
          </p>
          <div style={{ width: "100%", backgroundColor: "#e0e0e0", borderRadius: "4px", height: "8px" }}>
            <div
              style={{
                width: `${lockedPercentage}%`,
                backgroundColor: "#ff9800",
                height: "8px",
                borderRadius: "4px",
                transition: "width 0.3s ease"
              }}
            />
          </div>
        </div>
      )}

      <div style={{ marginTop: "1rem", fontSize: "0.85em", color: "#666" }}>
        <p>Decimals: {balance.decimals}</p>
        <p>Token: {balance.token.slice(0, 10)}...{balance.token.slice(-8)}</p>
      </div>
    </div>
  );
}
```

## Error Handling with Retry

```tsx theme={null}
function BalancesWithRetry() {
  const { balances, loading, error, clearError, refresh } = useUserBalances();

  if (error) {
    return (
      <div style={{ padding: "1rem", backgroundColor: "#fee", border: "1px solid #fcc", borderRadius: "8px" }}>
        <p style={{ color: "#c00", fontWeight: "bold" }}>Failed to load balances</p>
        <p style={{ color: "#666" }}>{error.message}</p>
        <button
          onClick={() => {
            clearError();
            refresh();
          }}
          style={{ marginTop: "1rem" }}
        >
          Retry
        </button>
      </div>
    );
  }

  if (loading) {
    return <div>Loading balances...</div>;
  }

  return (
    <div>
      <div style={{ display: "flex", justifyContent: "space-between", marginBottom: "1rem" }}>
        <h2>Balances</h2>
        <button onClick={refresh}>Refresh</button>
      </div>

      {balances.map(balance => (
        <div key={balance.assetId} style={{ marginBottom: "0.5rem" }}>
          <strong>{balance.symbol}:</strong> {balance.totalBalance}
        </div>
      ))}
    </div>
  );
}
```

## Complete Dashboard Example

```tsx theme={null}
import { useUserBalances } from "@0xmonaco/react";

function BalancesDashboard() {
  const { balances, loading, subscribed, error, refresh } = useUserBalances();

  const stats = {
    total: balances.length,
    active: balances.filter(b => parseFloat(b.totalBalance) > 0).length,
    withLocked: balances.filter(b => parseFloat(b.lockedBalance) > 0).length,
  };

  if (loading) {
    return (
      <div className="animate-pulse">
        <div className="h-4 bg-gray-200 rounded w-3/4 mb-2"></div>
        <div className="h-4 bg-gray-200 rounded w-1/2"></div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="bg-red-50 border border-red-200 rounded p-4">
        <p className="text-red-800">Failed to load balances</p>
        <p className="text-sm text-red-600">{error.message}</p>
        <button
          onClick={refresh}
          className="mt-2 px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
        >
          Retry
        </button>
      </div>
    );
  }

  return (
    <div className="space-y-4">
      {/* Connection Status */}
      <div className="flex items-center justify-between">
        <div className="flex items-center gap-2">
          <div className={`w-2 h-2 rounded-full ${subscribed ? "bg-green-500" : "bg-gray-400"}`} />
          <span className="text-sm text-gray-600">
            {subscribed ? "Real-time updates active" : "Disconnected"}
          </span>
        </div>
        <button
          onClick={refresh}
          className="px-3 py-1 text-sm border border-gray-300 rounded hover:bg-gray-50"
        >
          Refresh
        </button>
      </div>

      {/* Stats */}
      <div className="grid grid-cols-3 gap-4">
        <div className="bg-white border border-gray-200 rounded-lg p-4">
          <p className="text-sm text-gray-600">Total Tokens</p>
          <p className="text-2xl font-bold">{stats.total}</p>
        </div>
        <div className="bg-white border border-gray-200 rounded-lg p-4">
          <p className="text-sm text-gray-600">Active Balances</p>
          <p className="text-2xl font-bold text-green-600">{stats.active}</p>
        </div>
        <div className="bg-white border border-gray-200 rounded-lg p-4">
          <p className="text-sm text-gray-600">With Locked</p>
          <p className="text-2xl font-bold text-orange-600">{stats.withLocked}</p>
        </div>
      </div>

      {/* Balances List */}
      <div className="space-y-2">
        <h3 className="text-lg font-semibold">Token Balances</h3>

        {balances.length === 0 ? (
          <p className="text-gray-500 text-center py-8">No balances yet</p>
        ) : (
          balances.map(balance => {
            const hasBalance = parseFloat(balance.totalBalance) > 0;
            const hasLocked = parseFloat(balance.lockedBalance) > 0;

            return (
              <div
                key={balance.assetId}
                className={`bg-white border rounded-lg p-4 ${
                  hasBalance ? "border-gray-200 hover:shadow-md" : "border-gray-100 opacity-60"
                } transition-shadow`}
              >
                <div className="flex items-start justify-between">
                  <div className="flex-1">
                    <div className="flex items-center gap-2">
                      <span className="font-medium text-lg">
                        {balance.symbol || "Unknown"}
                      </span>
                      {hasLocked && (
                        <span className="px-2 py-1 text-xs bg-orange-100 text-orange-800 rounded">
                          Locked
                        </span>
                      )}
                    </div>

                    <div className="mt-2 space-y-1">
                      <div className="flex items-center gap-4 text-sm">
                        <span className="text-gray-600">Total:</span>
                        <span className="font-medium">{balance.totalBalance}</span>
                      </div>
                      <div className="flex items-center gap-4 text-sm">
                        <span className="text-gray-600">Available:</span>
                        <span className="text-green-600">{balance.availableBalance}</span>
                      </div>
                      {hasLocked && (
                        <div className="flex items-center gap-4 text-sm">
                          <span className="text-gray-600">Locked:</span>
                          <span className="text-orange-600">{balance.lockedBalance}</span>
                        </div>
                      )}
                    </div>
                  </div>

                  <div className="text-right text-xs text-gray-500">
                    <p>Decimals: {balance.decimals}</p>
                  </div>
                </div>

                {hasLocked && (
                  <div className="mt-3">
                    <div className="w-full bg-gray-200 rounded-full h-2">
                      <div
                        className="bg-orange-500 h-2 rounded-full transition-all"
                        style={{
                          width: `${(parseFloat(balance.lockedBalance) / parseFloat(balance.totalBalance)) * 100}%`
                        }}
                      />
                    </div>
                    <p className="text-xs text-gray-500 mt-1">
                      {((parseFloat(balance.lockedBalance) / parseFloat(balance.totalBalance)) * 100).toFixed(2)}% locked
                    </p>
                  </div>
                )}
              </div>
            );
          })
        )}
      </div>
    </div>
  );
}

export default BalancesDashboard;
```

## AccountBalance Type Structure

```typescript theme={null}
interface AccountBalance {
  assetId: string;                     // Asset UUID (for API calls)
  token: string;                        // Token contract address
  symbol: string | null;                // Token symbol (e.g., "USDC", "ETH")
  decimals: number;                     // Decimal places for the token
  availableBalance: string;            // Available balance (normalized, human-readable)
  lockedBalance: string;               // Locked in orders (normalized, human-readable)
  totalBalance: string;                // Total balance = available + locked (normalized)
  availableBalanceRaw: string;        // Available balance in raw format (smallest unit)
  lockedBalanceRaw: string;           // Locked balance in raw format (smallest unit)
  totalBalanceRaw: string;            // Total balance = available + locked (raw, smallest unit)
}
```

## See Also

* [useVault](/sdk/react/hooks/use-vault) - Deposit and withdraw tokens
* [useTrade](/sdk/react/hooks/use-trade) - Place and manage orders that lock/unlock balances
* [WebSocket API](/websocket#balance-updates) - Low-level balances WebSocket channel
* [TypeScript SDK](/sdk/typescript/websockets) - Core SDK WebSocket methods


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.