Skip to main content
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.

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
Use this stream alongside the REST portfolio routes for reconciliation. Because it is beta-only, backwards compatibility is not guaranteed yet.

Subscription payload

Send a JSON message with type: "balance" and provide the wallets you care about:
{
  "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

FieldTypeDescription
itemsArray<BalanceItem>List of wallet/token pairs to monitor. Each entry is processed independently.
walletstringWallet address in any case. The stream stores it in checksum format automatically.
tokenstringToken contract address. Use "native" to track the chain’s native asset (internally mapped to 0xeeee...).
blockchainstringMobula chain identifier (e.g., evm:1, evm:137, evm:8453). The beta release currently covers all supported EVM chains.
subscriptionIdstring (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.
subscriptionTrackingboolean (optional, default false)When true, the server acknowledges every subscribe/unsubscribe event with a friendly message.
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.

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:
{
  "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.
{
  "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

FieldTypeDescription
walletstringChecksummed wallet address.
tokenstring"native" or token contract address.
chainIdstringChain identifier (evm:1, evm:8453, …).
balancenumberHuman-readable balance with decimals applied.
rawBalancestringRaw balance (wei-style integer).
decimalsnumberERC-20 decimals used to derive balance.
symbolstringToken symbol resolved by the portfolio service.
namestringToken name.
logostring | nullCDN logo URL when available.
previousBalancenumber (update events only)Last balance before the delta was applied.
previousRawBalancestring (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

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);

Use cases

Pre-trade checks

Prevent failed swaps by verifying that a wallet still has the tokens you expect before sending the transaction.

Custody dashboards

Mirror your hot-wallet balances across chains without running your own balance indexers.

Risk controls

Trigger alerts if a whale moves funds across chains or drains a vault outside of normal trading hours.

Automation

Feed state machines or bots that must keep track of spending allowances in near real time.

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:
{
  "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.