#26773·ccxt

kraken(+others): watchBalance: websocket client doesn't Promise.resolve on all messages. Invalid balances (+many other potential issues)

Author: boogey100Created Sep 5, 2025Updated Sep 12, 2026
Labelsbugws

Operating System

Windows

Programming Language

JavaScript

CCXT Version

4.5.1

Description

There is only one watchBalance Promise.resolve() fired for balance updates from a trade, and it does not match the last balance from the websocket. See example below.

websocket: JUNO balance: 11.5727 sequence: 2 websocket: EUR balance: 21.6368 sequence: 3

event: 2025-09-06T14:44:13.805Z BALANCE UPDATE {total: {JUNO: 11.5727,EUR: 20.5841,}}

Subsequent call to this.ccxt.fetchBalance() does produce the correct balance.

This is a much wider issue than just balances or kraken, I think it probably effects all websockets.

const CCXTPro = require('ccxt').pro;

const exchange = new CCXTPro.kraken({
  apiKey: 'xxx',
  secret: 'xxx',
  enableRateLimit: true,
  options: { defaultType: 'spot' },
});
exchange.verbose = true;

const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

async function runWatchers() {
  try {
    await exchange.loadMarkets();
    const symbol = 'JUNO/EUR';

    // Balance watcher: prints whenever the exchange pushes a balance update
    const balanceWorker = (async () => {
      while (true) {
        try {
          const balance = await exchange.watchBalance();
          // balance is an object with free, used, total etc.
          console.log(new Date().toISOString(), 'BALANCE UPDATE', {
            total: balance.total || {},
          });
        } catch (err) {
          console.error('watchBalance error:', err?.message || err);
          await sleep(2000);
        }
      }
    })();

    // MyTrades watcher: prints when your trade history changes for a symbol
    const tradesWorker = (async () => {
      while (true) {
        try {
          const myTrades = await exchange.watchMyTrades(symbol);
          // myTrades is an array of trade objects
          console.log(new Date().toISOString(), `MY TRADES (${symbol})`, myTrades);
        } catch (err) {
          console.error('watchMyTrades error:', err?.message || err);
          await sleep(2000);
        }
      }
    })();

    // keep the process alive while both watchers run
    await Promise.all([balanceWorker, tradesWorker]);
  } catch (err) {
    console.error('Fatal error:', err);
  } finally {
    try { await exchange.close(); } catch (_) {}
  }
}

runWatchers();

Log

2025-09-06T14:44:00.784Z onMessage {
  method: 'subscribe',
  req_id: 1,
  result: { channel: 'balances', snapshot: true },
  success: true,
  time_in: '2025-09-06T14:44:01.229376Z',
  time_out: '2025-09-06T14:44:01.235868Z'
}
2025-09-06T14:44:00.787Z onMessage {
  channel: 'balances',
  type: 'snapshot',
  data: [
    {
      asset: 'JUNO',
      asset_class: 'currency',
      wallets: [Array],
      balance: 30.1097
    },
    {
      asset: 'EUR',
      asset_class: 'currency',
      wallets: [Array],
      balance: 20.5841
    },
  ],
  sequence: 1
}
2025-09-06T14:44:00.791Z BALANCE UPDATE {
  total: {
    JUNO: 30.1097,
    EUR: 20.5841
  }
}
2025-09-06T14:44:01.585Z onMessage { channel: 'heartbeat' }
2025-09-06T14:44:03.830Z onUpgrade
2025-09-06T14:44:03.831Z onOpen
2025-09-06T14:44:03.831Z sending {
  event: 'subscribe',
  reqid: 3,
  subscription: {
    name: 'ownTrades',
    token: 'xxxx'
  }
}
2025-09-06T14:44:03.832Z onMessage {
  event: 'systemStatus',
  version: '1.9.6',
  status: 'online',
  connectionID: 15734650943538248000
}
2025-09-06T14:44:03.863Z onMessage {
  channelName: 'ownTrades',
  event: 'subscriptionStatus',
  reqid: 3,
  status: 'subscribed',
  subscription: { name: 'ownTrades' }
}
2025-09-06T14:44:03.884Z onMessage [
  [
    { 'xxx': [Object] }
  ],
  'ownTrades',
  { sequence: 1 }
]
2025-09-06T14:44:03.888Z MY TRADES (JUNO/EUR) [
  {...}
]
2025-09-06T14:44:04.286Z onMessage { event: 'heartbeat' }
2025-09-06T14:44:04.587Z onMessage { channel: 'heartbeat' }
2025-09-06T14:44:06.788Z onMessage {
  method: 'pong',
  time_in: '2025-09-06T14:44:07.240240Z',
  time_out: '2025-09-06T14:44:07.240252Z'
}
2025-09-06T14:44:13.803Z onMessage {
  channel: 'balances',
  type: 'update',
  data: [
    {
      ledger_id: 'LRA7GI-FJKVR-QI3TE2',
      ref_id: 'TQR6Y6-EMDB2-HIJNJI',
      timestamp: '2025-09-06T14:44:14.254488Z',
      type: 'trade',
      asset: 'JUNO',
      asset_class: 'currency',
      category: 'trade',
      wallet_type: 'spot',
      wallet_id: 'main',
      amount: -18.5,
      fee: 0.037,
      balance: 11.5727
    }
  ],
  sequence: 2
}
2025-09-06T14:44:13.804Z onMessage {
  channel: 'balances',
  type: 'update',
  data: [
    {
      ledger_id: 'LPJF54-U33RF-B7AWRA',
      ref_id: 'TQR6Y6-EMDB2-HIJNJI',
      timestamp: '2025-09-06T14:44:14.254488Z',
      type: 'trade',
      asset: 'EUR',
      asset_class: 'currency',
      category: 'trade',
      wallet_type: 'spot',
      wallet_id: 'main',
      amount: 1.0527,
      fee: 0,
      balance: 21.6368
    }
  ],
  sequence: 3
}
2025-09-06T14:44:13.805Z BALANCE UPDATE {
  total: {
    JUNO: 11.5727
    EUR: 20.5841
  }
}
2025-09-06T14:44:13.808Z onMessage [
  [ { 'TQR6Y6-EMDB2-HIJNJI': [Object] } ],
  'ownTrades',
  { sequence: 2 }
]
2025-09-06T14:44:13.809Z MY TRADES (JUNO/EUR) [
  {
    id: 'TQR6Y6-EMDB2-HIJNJI',
    order: 'ON4ZDJ-ZXCMT-MG4IXN',
    info: {
      id: 'TQR6Y6-EMDB2-HIJNJI',
      cost: '1.0526500',
      fee: '0.0021053',
      margin: '0.0000000',
      ordertxid: 'ON4ZDJ-ZXCMT-MG4IXN',
      ordertype: 'market',
      pair: 'JUNO/EUR',
      postxid: 'TKH2SE-M7IF5-CFI7LT',
      price: '0.0569000',
      time: '1757169854.254488',
      type: 'sell',
      vol: '18.50000000'
    },
    timestamp: 1757169854254,
    datetime: '2025-09-06T14:44:14.254Z',
    symbol: 'JUNO/EUR',
    type: 'market',
    side: 'sell',
    takerOrMaker: undefined,
    price: 0.0569,
    amount: 18.5,
    cost: 1.05265,
    fee: { cost: 0.0021053, currency: 'EUR' }
  }
]
2025-09-06T14:44:14.310Z onMessage { event: 'heartbeat' }
2025-09-06T14:44:14.603Z onMessage { channel: 'heartbeat' }
2025-09-06T14:44:15.310Z onMessage { event: 'heartbeat' }
2025-09-06T14:44:15.603Z onMessage { channel: 'heartbeat' }