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' }Source: ccxt/ccxt