@lifi/perps-sdk-provider-hyperliquid 20.0.0 → 21.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/README.md +45 -0
  2. package/dist/cjs/HyperliquidProvider.d.ts.map +1 -1
  3. package/dist/cjs/HyperliquidProvider.js +2 -2
  4. package/dist/cjs/HyperliquidProvider.js.map +1 -1
  5. package/dist/cjs/services/getAccount.d.ts.map +1 -1
  6. package/dist/cjs/services/getAccount.js +5 -7
  7. package/dist/cjs/services/getAccount.js.map +1 -1
  8. package/dist/cjs/services/getActivity.d.ts.map +1 -1
  9. package/dist/cjs/services/getActivity.js +68 -44
  10. package/dist/cjs/services/getActivity.js.map +1 -1
  11. package/dist/cjs/services/getFills.d.ts.map +1 -1
  12. package/dist/cjs/services/getFills.js +30 -38
  13. package/dist/cjs/services/getFills.js.map +1 -1
  14. package/dist/cjs/services/getWithdrawableBalances.js +1 -1
  15. package/dist/cjs/services/getWithdrawableBalances.js.map +1 -1
  16. package/dist/cjs/services/historyPagination.d.ts +22 -0
  17. package/dist/cjs/services/historyPagination.d.ts.map +1 -0
  18. package/dist/cjs/services/historyPagination.js +125 -0
  19. package/dist/cjs/services/historyPagination.js.map +1 -0
  20. package/dist/cjs/utils/index.d.ts +1 -1
  21. package/dist/cjs/utils/index.d.ts.map +1 -1
  22. package/dist/cjs/utils/index.js +3 -3
  23. package/dist/cjs/utils/index.js.map +1 -1
  24. package/dist/cjs/utils/liquidation.js +1 -1
  25. package/dist/cjs/utils/liquidation.js.map +1 -1
  26. package/dist/cjs/utils/orderFormatting.d.ts +3 -2
  27. package/dist/cjs/utils/orderFormatting.d.ts.map +1 -1
  28. package/dist/cjs/utils/orderFormatting.js +4 -4
  29. package/dist/cjs/utils/orderFormatting.js.map +1 -1
  30. package/dist/cjs/utils/spotBalance.d.ts.map +1 -1
  31. package/dist/cjs/utils/spotBalance.js +2 -2
  32. package/dist/cjs/utils/spotBalance.js.map +1 -1
  33. package/dist/cjs/utils/withdrawableBalances.d.ts +1 -1
  34. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
  35. package/dist/cjs/utils/withdrawableBalances.js +7 -4
  36. package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
  37. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts +6 -0
  38. package/dist/cjs/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  39. package/dist/cjs/websocket/HyperliquidWsProvider.js +135 -37
  40. package/dist/cjs/websocket/HyperliquidWsProvider.js.map +1 -1
  41. package/dist/esm/HyperliquidProvider.d.ts.map +1 -1
  42. package/dist/esm/HyperliquidProvider.js +3 -3
  43. package/dist/esm/HyperliquidProvider.js.map +1 -1
  44. package/dist/esm/services/getAccount.d.ts.map +1 -1
  45. package/dist/esm/services/getAccount.js +6 -5
  46. package/dist/esm/services/getAccount.js.map +1 -1
  47. package/dist/esm/services/getActivity.d.ts +3 -13
  48. package/dist/esm/services/getActivity.d.ts.map +1 -1
  49. package/dist/esm/services/getActivity.js +74 -64
  50. package/dist/esm/services/getActivity.js.map +1 -1
  51. package/dist/esm/services/getFills.d.ts +2 -14
  52. package/dist/esm/services/getFills.d.ts.map +1 -1
  53. package/dist/esm/services/getFills.js +32 -58
  54. package/dist/esm/services/getFills.js.map +1 -1
  55. package/dist/esm/services/getWithdrawableBalances.js +1 -1
  56. package/dist/esm/services/getWithdrawableBalances.js.map +1 -1
  57. package/dist/esm/services/historyPagination.d.ts +22 -0
  58. package/dist/esm/services/historyPagination.d.ts.map +1 -0
  59. package/dist/esm/services/historyPagination.js +121 -0
  60. package/dist/esm/services/historyPagination.js.map +1 -0
  61. package/dist/esm/utils/index.d.ts +1 -1
  62. package/dist/esm/utils/index.d.ts.map +1 -1
  63. package/dist/esm/utils/index.js +1 -1
  64. package/dist/esm/utils/index.js.map +1 -1
  65. package/dist/esm/utils/liquidation.js +2 -2
  66. package/dist/esm/utils/liquidation.js.map +1 -1
  67. package/dist/esm/utils/orderFormatting.d.ts +14 -12
  68. package/dist/esm/utils/orderFormatting.d.ts.map +1 -1
  69. package/dist/esm/utils/orderFormatting.js +14 -14
  70. package/dist/esm/utils/orderFormatting.js.map +1 -1
  71. package/dist/esm/utils/spotBalance.d.ts.map +1 -1
  72. package/dist/esm/utils/spotBalance.js +3 -3
  73. package/dist/esm/utils/spotBalance.js.map +1 -1
  74. package/dist/esm/utils/withdrawableBalances.d.ts +1 -1
  75. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
  76. package/dist/esm/utils/withdrawableBalances.js +7 -4
  77. package/dist/esm/utils/withdrawableBalances.js.map +1 -1
  78. package/dist/esm/websocket/HyperliquidWsProvider.d.ts +6 -0
  79. package/dist/esm/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  80. package/dist/esm/websocket/HyperliquidWsProvider.js +138 -50
  81. package/dist/esm/websocket/HyperliquidWsProvider.js.map +1 -1
  82. package/dist/types/HyperliquidProvider.d.ts.map +1 -1
  83. package/dist/types/services/getAccount.d.ts.map +1 -1
  84. package/dist/types/services/getActivity.d.ts +3 -13
  85. package/dist/types/services/getActivity.d.ts.map +1 -1
  86. package/dist/types/services/getFills.d.ts +2 -14
  87. package/dist/types/services/getFills.d.ts.map +1 -1
  88. package/dist/types/services/historyPagination.d.ts +22 -0
  89. package/dist/types/services/historyPagination.d.ts.map +1 -0
  90. package/dist/types/utils/index.d.ts +1 -1
  91. package/dist/types/utils/index.d.ts.map +1 -1
  92. package/dist/types/utils/orderFormatting.d.ts +14 -12
  93. package/dist/types/utils/orderFormatting.d.ts.map +1 -1
  94. package/dist/types/utils/spotBalance.d.ts.map +1 -1
  95. package/dist/types/utils/withdrawableBalances.d.ts +1 -1
  96. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
  97. package/dist/types/websocket/HyperliquidWsProvider.d.ts +6 -0
  98. package/dist/types/websocket/HyperliquidWsProvider.d.ts.map +1 -1
  99. package/package.json +3 -3
  100. package/src/HyperliquidProvider.ts +6 -5
  101. package/src/services/getAccount.ts +18 -18
  102. package/src/services/getActivity.ts +133 -104
  103. package/src/services/getFills.ts +42 -95
  104. package/src/services/getWithdrawableBalances.ts +1 -1
  105. package/src/services/historyPagination.ts +169 -0
  106. package/src/utils/index.ts +2 -2
  107. package/src/utils/liquidation.ts +2 -2
  108. package/src/utils/orderFormatting.ts +20 -16
  109. package/src/utils/spotBalance.ts +5 -3
  110. package/src/utils/withdrawableBalances.ts +12 -7
  111. package/src/websocket/HyperliquidWsProvider.ts +151 -63
@@ -1,4 +1,5 @@
1
1
  import {
2
+ calculateTransferable,
2
3
  DecodeChain,
3
4
  getMarketRegistry,
4
5
  isActiveMarket,
@@ -137,20 +138,21 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
137
138
  private readonly clearinghouseRefs = new Map<string, number>()
138
139
  private readonly client: PerpsSDKClient | undefined
139
140
  private readonly registry: MarketRegistry | undefined
140
- // The clearinghouse stream covers perps equity only, so its summary is
141
- // honest solely for modes whose collateral lives per-dex. The abstraction
142
- // mode is read per subscribed user (null = never set = standard) and
143
- // selects the summary source: standard/dexAbstraction emit the equity
144
- // summary straight from clearinghouse frames, unified/portfolio run the
145
- // spot-fed pipeline below. Frames arriving before the first read are held
146
- // and released on resolution (delayed, never wrong). The read refreshes on
147
- // fresh summary subscribes and once older than the TTL, and drops with
148
- // the user's last clearinghouse subscription.
141
+ // Spot-funded modes need both spot valuation and clearinghouse margin state.
149
142
  private readonly abstractionByUser = new Map<
150
143
  string,
151
144
  { mode: HlAbstractionMode | null; readAt: number } | 'pending'
152
145
  >()
146
+ private readonly summaryUsers = new Set<string>()
147
+ private readonly abstractionReadsByUser = new Map<string, symbol>()
153
148
  private readonly spotRefs = new Map<string, number>()
149
+ private spotWireEpoch = 0
150
+ private readonly latestSpotByUser = new Map<string, HlWsSpotStateData>()
151
+ private readonly revalueSpotBalances = () => {
152
+ for (const user of this.latestSpotByUser.keys()) {
153
+ this.emitSpotBalances(user)
154
+ }
155
+ }
154
156
  // Unified/portfolio summary pipeline: collateral lives in spot, margin and
155
157
  // uPnL on the positions — the summary recomputes from the latest of both
156
158
  // envelopes with the same gross calculator the REST getAccountSummary uses.
@@ -310,16 +312,15 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
310
312
  // Markets context aggregates slower asset-context feeds with fast mid/mark
311
313
  // ticks from `fastAssetCtxs`.
312
314
  if (sub.channel === 'marketsContext') {
315
+ const epoch = this.spotWireEpoch
313
316
  const entries = this.getMarketsContextSubEntries()
314
-
315
- for (const { subKey, payload } of entries) {
316
- await this.registerSub(subKey, payload)
317
- }
318
-
319
- await this.rws.ready()
320
-
321
- return () => {
322
- for (const { subKey, payload } of entries) {
317
+ let acquired = 0
318
+ const release = () => {
319
+ if (epoch !== this.spotWireEpoch) {
320
+ return
321
+ }
322
+ for (let index = 0; index < acquired; index++) {
323
+ const { subKey, payload } = entries[index]
323
324
  this.unregisterSub(subKey)
324
325
  this.rws.send(
325
326
  JSON.stringify({ method: 'unsubscribe', subscription: payload })
@@ -329,15 +330,25 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
329
330
  this.spotCtxByMarketId = {}
330
331
  this.fastCtxByMarketId = {}
331
332
  this.marketsContextByMarketId = {}
333
+ acquired = 0
334
+ }
335
+ try {
336
+ for (const { subKey, payload } of entries) {
337
+ await this.registerSub(subKey, payload)
338
+ acquired++
339
+ }
340
+ await this.rws.ready()
341
+ return release
342
+ } catch (error) {
343
+ release()
344
+ throw error
332
345
  }
333
346
  }
334
347
 
335
348
  // The spot wire is shared between the public spotBalances channel and
336
349
  // the unified-summary pipeline; refcounted like the clearinghouse sub.
337
350
  if (sub.channel === 'spotBalances') {
338
- const release = await this.acquireSpotWire(sub.address)
339
- await this.rws.ready()
340
- return release
351
+ return this.acquireSpotWire(sub.address)
341
352
  }
342
353
 
343
354
  // `positions` and `accountSummary` are two views over the same wire
@@ -348,20 +359,25 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
348
359
  const payload = this.toHlPayload(sub)
349
360
  const count = this.clearinghouseRefs.get(user) ?? 0
350
361
  this.clearinghouseRefs.set(user, count + 1)
362
+ if (sub.channel === 'accountSummary') {
363
+ this.summaryUsers.add(user)
364
+ }
351
365
  if (count === 0) {
352
366
  await this.registerSub(wireKey, payload)
353
367
  }
354
368
  await this.rws.ready()
355
369
  return () => {
370
+ if (sub.channel === 'accountSummary') {
371
+ this.summaryUsers.delete(user)
372
+ this.abstractionReadsByUser.delete(user)
373
+ this.abstractionByUser.delete(user)
374
+ this.heldSummaryByUser.delete(user)
375
+ this.syncUnifiedPipeline(user, user, null)
376
+ }
356
377
  const remaining = (this.clearinghouseRefs.get(user) ?? 1) - 1
357
378
  if (remaining <= 0) {
358
379
  this.clearinghouseRefs.delete(user)
359
- // Drop the summary gate's mode read (and its pipeline) with the
360
- // subscription so a resubscribe reflects an account-mode change.
361
- this.abstractionByUser.delete(user)
362
- this.heldSummaryByUser.delete(user)
363
380
  this.latestPerpsByUser.delete(user)
364
- this.syncUnifiedPipeline(user, user, null)
365
381
  this.unregisterSub(wireKey)
366
382
  this.rws.send(
367
383
  JSON.stringify({ method: 'unsubscribe', subscription: payload })
@@ -489,9 +505,19 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
489
505
  }
490
506
 
491
507
  protected override onClose(): void {
508
+ this.spotWireEpoch++
492
509
  this.perpCtxBySubDex.clear()
493
510
  this.spotCtxByMarketId = {}
494
511
  this.fastCtxByMarketId = {}
512
+ this.latestSpotByUser.clear()
513
+ this.spotRefs.clear()
514
+ this.clearinghouseRefs.clear()
515
+ this.summaryUsers.clear()
516
+ this.abstractionReadsByUser.clear()
517
+ this.abstractionByUser.clear()
518
+ this.heldSummaryByUser.clear()
519
+ this.latestPerpsByUser.clear()
520
+ this.unifiedSummaryByUser.clear()
495
521
  this.marketsContextByMarketId = {}
496
522
  this.orderbookKeysByMarketId.clear()
497
523
  this.latestOrderbookByMarketId.clear()
@@ -1206,22 +1232,23 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1206
1232
  // Lowercased in the payload too, so releases from either holder (public
1207
1233
  // channel or pipeline) unsubscribe with an identical payload.
1208
1234
  const user = address.toLowerCase()
1235
+ const epoch = this.spotWireEpoch
1209
1236
  const wireKey = `spot:${user}`
1210
1237
  const payload = { type: 'spotState', user }
1211
1238
  const count = this.spotRefs.get(user) ?? 0
1212
1239
  this.spotRefs.set(user, count + 1)
1213
- if (count === 0) {
1214
- await this.registerSub(wireKey, payload)
1215
- }
1240
+ let releasePrices: (() => void) | undefined
1216
1241
  let released = false
1217
- return () => {
1218
- if (released) {
1242
+ const release = () => {
1243
+ if (released || epoch !== this.spotWireEpoch) {
1219
1244
  return
1220
1245
  }
1221
1246
  released = true
1247
+ releasePrices?.()
1222
1248
  const remaining = (this.spotRefs.get(user) ?? 1) - 1
1223
1249
  if (remaining <= 0) {
1224
1250
  this.spotRefs.delete(user)
1251
+ this.latestSpotByUser.delete(user)
1225
1252
  this.unregisterSub(wireKey)
1226
1253
  this.rws.send(
1227
1254
  JSON.stringify({ method: 'unsubscribe', subscription: payload })
@@ -1230,6 +1257,21 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1230
1257
  this.spotRefs.set(user, remaining)
1231
1258
  }
1232
1259
  }
1260
+ try {
1261
+ if (count === 0) {
1262
+ await this.registerSub(wireKey, payload)
1263
+ }
1264
+ if (epoch === this.spotWireEpoch) {
1265
+ releasePrices = await super.subscribe(
1266
+ { channel: 'marketsContext', dex: this.providerKey },
1267
+ this.revalueSpotBalances
1268
+ )
1269
+ }
1270
+ return release
1271
+ } catch (error) {
1272
+ release()
1273
+ throw error
1274
+ }
1233
1275
  }
1234
1276
 
1235
1277
  /** Start or stop the spot-fed unified pipeline to match the resolved mode. */
@@ -1241,12 +1283,22 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1241
1283
  const unified = !HyperliquidWsProvider.summaryComputableFor(mode)
1242
1284
  const existing = this.unifiedSummaryByUser.get(key)
1243
1285
  if (unified && existing === undefined) {
1244
- this.unifiedSummaryByUser.set(key, {
1245
- releaseSpot: this.acquireSpotWire(address).catch((error) => {
1246
- wsLog.handlerFailure(this.providerKey, error)
1247
- return () => {}
1248
- }),
1286
+ const epoch = this.spotWireEpoch
1287
+ const releaseSpot = this.acquireSpotWire(address).catch((error) => {
1288
+ if (this.unifiedSummaryByUser.get(key)?.releaseSpot === releaseSpot) {
1289
+ this.unifiedSummaryByUser.delete(key)
1290
+ }
1291
+ wsLog.handlerFailure(this.providerKey, error)
1292
+ return () => {}
1249
1293
  })
1294
+ if (epoch !== this.spotWireEpoch) {
1295
+ return
1296
+ }
1297
+ this.unifiedSummaryByUser.set(key, { releaseSpot })
1298
+ const spot = this.latestSpotByUser.get(key)
1299
+ if (spot !== undefined) {
1300
+ this.handleSpotState(spot)
1301
+ }
1250
1302
  } else if (!unified && existing !== undefined) {
1251
1303
  this.unifiedSummaryByUser.delete(key)
1252
1304
  void existing.releaseSpot.then((release) => release())
@@ -1303,6 +1355,9 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1303
1355
  }
1304
1356
  ) {
1305
1357
  const key = user.toLowerCase()
1358
+ if (!this.summaryUsers.has(key)) {
1359
+ return
1360
+ }
1306
1361
  // Without a client there is no way to read the mode; keep the historic
1307
1362
  // always-emit behavior rather than silencing every standalone consumer.
1308
1363
  if (this.client === undefined) {
@@ -1340,7 +1395,7 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1340
1395
  refreshing?: { mode: HlAbstractionMode | null; readAt: number }
1341
1396
  ) {
1342
1397
  const client = this.client
1343
- if (client === undefined) {
1398
+ if (client === undefined || !this.summaryUsers.has(key)) {
1344
1399
  return
1345
1400
  }
1346
1401
  // A background refresh keeps the current entry live (stamped now so
@@ -1349,6 +1404,8 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1349
1404
  key,
1350
1405
  refreshing ? { ...refreshing, readAt: Date.now() } : 'pending'
1351
1406
  )
1407
+ const read = Symbol()
1408
+ this.abstractionReadsByUser.set(key, read)
1352
1409
  // "Never set abstraction" is a successful 200 `null` body — only a fetch
1353
1410
  // failure clears a first read so the next frame retries it. A failed
1354
1411
  // refresh keeps the previous mode until the TTL passes again.
@@ -1358,6 +1415,10 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1358
1415
  hlInfoOptions(client)
1359
1416
  ).then(
1360
1417
  (mode) => {
1418
+ if (this.abstractionReadsByUser.get(key) !== read) {
1419
+ return
1420
+ }
1421
+ this.abstractionReadsByUser.delete(key)
1361
1422
  this.abstractionByUser.set(key, { mode, readAt: Date.now() })
1362
1423
  this.syncUnifiedPipeline(key, user, mode)
1363
1424
  const held = this.heldSummaryByUser.get(key)
@@ -1370,6 +1431,10 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1370
1431
  }
1371
1432
  },
1372
1433
  (error) => {
1434
+ if (this.abstractionReadsByUser.get(key) !== read) {
1435
+ return
1436
+ }
1437
+ this.abstractionReadsByUser.delete(key)
1373
1438
  if (!refreshing) {
1374
1439
  this.abstractionByUser.delete(key)
1375
1440
  }
@@ -1401,35 +1466,10 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1401
1466
 
1402
1467
  private handleSpotState(data: HlWsSpotStateData) {
1403
1468
  const user = data.user.toLowerCase()
1469
+ this.latestSpotByUser.set(user, data)
1404
1470
  const markets = this.registry?.activeMarkets ?? []
1405
- const priceById = spotPriceById(markets, this.mergedMids())
1406
- const rows = data.spotState.balances
1407
- .filter(
1408
- (balance) =>
1409
- !assetIsOutcome(balance.coin) &&
1410
- toWireBig(balance.total, 'spotState.balances.total').gt(0)
1411
- )
1412
- .map((balance) => ({
1413
- balance: spotBalance(
1414
- spotAssetFromToken(balance),
1415
- balance.total,
1416
- priceById
1417
- ),
1418
- hold: balance.hold,
1419
- }))
1420
- this.emit(`spotState:${user}`, {
1421
- channel: 'spotBalances',
1422
- data: rows.map(({ balance, hold }) => ({ ...balance, locked: hold })),
1423
- })
1424
-
1425
1471
  const pipeline = this.unifiedSummaryByUser.get(user)
1426
1472
  if (pipeline !== undefined) {
1427
- const quoteAssetIds = new Set(markets.map((m) => m.quoteAsset.id))
1428
- // Same partition as getAccount so REST and WS agree on collateral.
1429
- pipeline.spot = partitionSpotBalances(
1430
- rows.map(({ balance }) => balance),
1431
- quoteAssetIds
1432
- )
1433
1473
  const settlementAssetId = markets.find(
1434
1474
  (m) => m.categoryId === this.providerKey
1435
1475
  )?.quoteAsset.id
@@ -1442,6 +1482,54 @@ export class HyperliquidWsProvider extends WsProviderBase<object> {
1442
1482
  } else {
1443
1483
  pipeline.availableMargin = available
1444
1484
  }
1485
+ }
1486
+ this.emitSpotBalances(user)
1487
+ }
1488
+
1489
+ private emitSpotBalances(user: string) {
1490
+ const data = this.latestSpotByUser.get(user)
1491
+ if (data === undefined) {
1492
+ return
1493
+ }
1494
+ const markets = this.registry?.activeMarkets ?? []
1495
+ const priceById = spotPriceById(markets, this.mergedMids())
1496
+ const balances = data.spotState.balances.filter(
1497
+ (balance) =>
1498
+ !assetIsOutcome(balance.coin) &&
1499
+ toWireBig(balance.total, 'spotState.balances.total').gt(0)
1500
+ )
1501
+ const pipeline = this.unifiedSummaryByUser.get(user)
1502
+ // Known spot markets await a price; unlisted tokens keep their unpriced balance.
1503
+ if (
1504
+ balances.some((balance) => priceById.get(String(balance.token)) === 0)
1505
+ ) {
1506
+ if (pipeline !== undefined) {
1507
+ pipeline.spot = undefined
1508
+ }
1509
+ return
1510
+ }
1511
+ const rows = balances.map((balance) => {
1512
+ const total = toWireBig(balance.total, 'spotState.balances.total')
1513
+ return {
1514
+ ...spotBalance(spotAssetFromToken(balance), balance.total, priceById),
1515
+ locked: balance.hold,
1516
+ transferable: calculateTransferable(
1517
+ total
1518
+ .minus(toWireBig(balance.hold, 'spotState.balances.hold'))
1519
+ .toFixed(),
1520
+ total.toFixed()
1521
+ ),
1522
+ }
1523
+ })
1524
+ this.emit(`spotState:${user}`, {
1525
+ channel: 'spotBalances',
1526
+ data: rows,
1527
+ })
1528
+ if (pipeline !== undefined) {
1529
+ pipeline.spot = partitionSpotBalances(
1530
+ rows,
1531
+ new Set(markets.map((market) => market.quoteAsset.id))
1532
+ )
1445
1533
  this.emitUnifiedSummary(user)
1446
1534
  }
1447
1535
  }