@lifi/perps-sdk-provider-ondo 22.0.0 → 23.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 (59) hide show
  1. package/README.md +14 -0
  2. package/dist/cjs/OndoProvider.d.ts.map +1 -1
  3. package/dist/cjs/OndoProvider.js +3 -7
  4. package/dist/cjs/OndoProvider.js.map +1 -1
  5. package/dist/cjs/utils/index.d.ts +1 -1
  6. package/dist/cjs/utils/index.d.ts.map +1 -1
  7. package/dist/cjs/utils/index.js +3 -3
  8. package/dist/cjs/utils/index.js.map +1 -1
  9. package/dist/cjs/utils/liquidation.js +1 -1
  10. package/dist/cjs/utils/liquidation.js.map +1 -1
  11. package/dist/cjs/utils/orderFormatting.d.ts +3 -3
  12. package/dist/cjs/utils/orderFormatting.d.ts.map +1 -1
  13. package/dist/cjs/utils/orderFormatting.js +5 -5
  14. package/dist/cjs/utils/orderFormatting.js.map +1 -1
  15. package/dist/cjs/utils/withdrawableBalances.d.ts +1 -1
  16. package/dist/cjs/utils/withdrawableBalances.d.ts.map +1 -1
  17. package/dist/cjs/utils/withdrawableBalances.js +13 -13
  18. package/dist/cjs/utils/withdrawableBalances.js.map +1 -1
  19. package/dist/cjs/websocket/OndoWsProvider.d.ts +6 -0
  20. package/dist/cjs/websocket/OndoWsProvider.d.ts.map +1 -1
  21. package/dist/cjs/websocket/OndoWsProvider.js +125 -33
  22. package/dist/cjs/websocket/OndoWsProvider.js.map +1 -1
  23. package/dist/esm/OndoProvider.d.ts.map +1 -1
  24. package/dist/esm/OndoProvider.js +5 -6
  25. package/dist/esm/OndoProvider.js.map +1 -1
  26. package/dist/esm/utils/index.d.ts +1 -1
  27. package/dist/esm/utils/index.d.ts.map +1 -1
  28. package/dist/esm/utils/index.js +1 -1
  29. package/dist/esm/utils/index.js.map +1 -1
  30. package/dist/esm/utils/liquidation.js +1 -1
  31. package/dist/esm/utils/liquidation.js.map +1 -1
  32. package/dist/esm/utils/orderFormatting.d.ts +8 -8
  33. package/dist/esm/utils/orderFormatting.d.ts.map +1 -1
  34. package/dist/esm/utils/orderFormatting.js +9 -9
  35. package/dist/esm/utils/orderFormatting.js.map +1 -1
  36. package/dist/esm/utils/withdrawableBalances.d.ts +4 -4
  37. package/dist/esm/utils/withdrawableBalances.d.ts.map +1 -1
  38. package/dist/esm/utils/withdrawableBalances.js +16 -16
  39. package/dist/esm/utils/withdrawableBalances.js.map +1 -1
  40. package/dist/esm/websocket/OndoWsProvider.d.ts +7 -5
  41. package/dist/esm/websocket/OndoWsProvider.d.ts.map +1 -1
  42. package/dist/esm/websocket/OndoWsProvider.js +127 -43
  43. package/dist/esm/websocket/OndoWsProvider.js.map +1 -1
  44. package/dist/types/OndoProvider.d.ts.map +1 -1
  45. package/dist/types/utils/index.d.ts +1 -1
  46. package/dist/types/utils/index.d.ts.map +1 -1
  47. package/dist/types/utils/orderFormatting.d.ts +8 -8
  48. package/dist/types/utils/orderFormatting.d.ts.map +1 -1
  49. package/dist/types/utils/withdrawableBalances.d.ts +4 -4
  50. package/dist/types/utils/withdrawableBalances.d.ts.map +1 -1
  51. package/dist/types/websocket/OndoWsProvider.d.ts +7 -5
  52. package/dist/types/websocket/OndoWsProvider.d.ts.map +1 -1
  53. package/package.json +3 -3
  54. package/src/OndoProvider.ts +12 -17
  55. package/src/utils/index.ts +1 -1
  56. package/src/utils/liquidation.ts +1 -1
  57. package/src/utils/orderFormatting.ts +19 -9
  58. package/src/utils/withdrawableBalances.ts +22 -20
  59. package/src/websocket/OndoWsProvider.ts +156 -45
@@ -92,6 +92,7 @@ interface SubState {
92
92
  frame: { channel: string; markets?: string[]; resolution?: string }
93
93
  needsAuth: boolean
94
94
  address?: Address
95
+ authGeneration?: number
95
96
  }
96
97
 
97
98
  /**
@@ -138,6 +139,7 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
138
139
  * positions frame, which is what moves them.
139
140
  */
140
141
  private accountSummary: OndoAccountBalance | undefined
142
+ private accountSummaryGeneration = 0
141
143
 
142
144
  /**
143
145
  * Serializes the balance re-read: a single trade event emits a fills frame
@@ -159,6 +161,9 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
159
161
  /** The single address this connection's login is bound to. */
160
162
  private accountAddress: string | undefined
161
163
  private loginPromise: Promise<void> | undefined
164
+ private authGeneration = 0
165
+ private authBindingRefs = 0
166
+ private closeGeneration = 0
162
167
 
163
168
  constructor(
164
169
  wsUrl: string = DEFAULT_ONDO_WS_URL,
@@ -240,18 +245,28 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
240
245
  const wireSubs = this.resolveChannel(sub)
241
246
  const needsLogin =
242
247
  isAuthChannel(sub.channel) || sub.channel === 'accountSummary'
248
+ const closeGeneration = this.closeGeneration
249
+ let authGeneration: number | undefined
243
250
 
244
- let boundHere = false
245
251
  if (needsLogin) {
246
252
  const { address } = sub as { address: Address }
247
253
  // Reject before the address binds and before any wire sub registers: a
248
254
  // registered sub whose login can never succeed is retried on every
249
255
  // reopen without the caller ever seeing the failure.
250
256
  await this.requireSession(address)
257
+ if (closeGeneration !== this.closeGeneration) {
258
+ return () => {}
259
+ }
251
260
  const bound = address.toLowerCase()
252
- boundHere = this.accountAddress !== bound
253
261
  this.bindAddress(bound)
262
+ authGeneration = this.authGeneration
263
+ this.authBindingRefs += 1
254
264
  }
265
+ const summaryGeneration =
266
+ sub.channel === 'accountSummary'
267
+ ? this.accountSummaryGeneration
268
+ : undefined
269
+ let acquiredWireCount = 0
255
270
 
256
271
  try {
257
272
  if (needsLogin) {
@@ -259,32 +274,79 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
259
274
  // market identity the mapped orders/fills/positions embed.
260
275
  await this.registry?.sync()
261
276
  }
262
- if (sub.channel === 'accountSummary') {
263
- await this.seedAccountSummary((sub as { address: Address }).address)
277
+ if (
278
+ authGeneration !== undefined &&
279
+ authGeneration !== this.authGeneration
280
+ ) {
281
+ return () => {}
282
+ }
283
+ if (sub.channel === 'accountSummary' && summaryGeneration !== undefined) {
284
+ if (summaryGeneration !== this.accountSummaryGeneration) {
285
+ return () => {}
286
+ }
287
+ await this.seedAccountSummary(sub.address, summaryGeneration)
288
+ if (summaryGeneration !== this.accountSummaryGeneration) {
289
+ return () => {}
290
+ }
264
291
  }
265
292
  // Authenticated wire subs are shared and ref-counted; public ones are 1:1.
266
293
  for (const [key, state] of wireSubs) {
294
+ state.authGeneration = authGeneration
267
295
  await this.acquireWire(key, state, needsLogin)
296
+ acquiredWireCount += 1
297
+ if (
298
+ authGeneration !== undefined &&
299
+ authGeneration !== this.authGeneration
300
+ ) {
301
+ return () => {}
302
+ }
268
303
  }
269
304
  await this.rws.ready()
305
+ if (
306
+ authGeneration !== undefined &&
307
+ authGeneration !== this.authGeneration
308
+ ) {
309
+ return () => {}
310
+ }
270
311
  } catch (err) {
271
- // Registry sync, summary seed, or wire acquire failed after the address
272
- // bound: release a binding this call newly reserved so a later
273
- // subscribe can rebind. A `requireSession` throw never reaches here.
274
- if (boundHere) {
275
- this.accountAddress = undefined
312
+ // A failed opening releases only its own binding reservation.
313
+ if (
314
+ authGeneration === undefined ||
315
+ authGeneration === this.authGeneration
316
+ ) {
317
+ if (summaryGeneration !== undefined) {
318
+ this.clearAccountSummary()
319
+ }
320
+ for (let index = 0; index < acquiredWireCount; index += 1) {
321
+ const [key, state] = wireSubs[index]
322
+ this.releaseWire(key, state, needsLogin)
323
+ }
324
+ if (authGeneration !== undefined) {
325
+ this.releaseBinding()
326
+ }
276
327
  }
277
328
  throw err
278
329
  }
279
330
 
280
331
  return () => {
281
- if (sub.channel === 'accountSummary') {
282
- this.accountSummary = undefined
283
- this.accountSummaryChain.reset()
332
+ if (
333
+ authGeneration !== undefined &&
334
+ authGeneration !== this.authGeneration
335
+ ) {
336
+ return
337
+ }
338
+ if (summaryGeneration !== undefined) {
339
+ if (summaryGeneration !== this.accountSummaryGeneration) {
340
+ return
341
+ }
342
+ this.clearAccountSummary()
284
343
  }
285
344
  for (const [key, state] of wireSubs) {
286
345
  this.releaseWire(key, state, needsLogin)
287
346
  }
347
+ if (authGeneration !== undefined) {
348
+ this.releaseBinding()
349
+ }
288
350
  }
289
351
  }
290
352
 
@@ -324,14 +386,15 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
324
386
  return true
325
387
  }
326
388
 
327
- /**
328
- * Forget the authenticated binding and cycle the connection. The venue
329
- * permits one login per socket, so a later subscribe for a different address
330
- * must authenticate on a fresh connection rather than re-login on this one.
331
- */
389
+ /** Release a channel's binding reservation, including one still opening. */
332
390
  private releaseBinding(): void {
391
+ this.authBindingRefs -= 1
392
+ if (this.authBindingRefs > 0) {
393
+ return
394
+ }
333
395
  this.accountAddress = undefined
334
396
  this.loginPromise = undefined
397
+ this.authGeneration += 1
335
398
  this.rws.reconnect()
336
399
  }
337
400
 
@@ -352,7 +415,9 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
352
415
  await this.registerSub(key, state)
353
416
  } catch (err) {
354
417
  // Roll back the ref we just took so a later subscribe re-registers.
355
- this.authWireRefs.delete(key)
418
+ if (state.authGeneration === this.authGeneration) {
419
+ this.authWireRefs.delete(key)
420
+ }
356
421
  throw err
357
422
  }
358
423
  }
@@ -361,6 +426,9 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
361
426
  /** Mirror of {@link acquireWire}: drop the wire sub on its last release. */
362
427
  private releaseWire(key: string, state: SubState, shared: boolean): void {
363
428
  if (shared) {
429
+ if (state.authGeneration !== this.authGeneration) {
430
+ return
431
+ }
364
432
  const remaining = (this.authWireRefs.get(key) ?? 1) - 1
365
433
  if (remaining > 0) {
366
434
  this.authWireRefs.set(key, remaining)
@@ -370,19 +438,24 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
370
438
  }
371
439
  this.unregisterSub(key)
372
440
  this.rws.send(JSON.stringify({ op: 'unsubscribe', ...state.frame }))
373
- // Last authenticated wire gone: forget the binding and cycle the socket so
374
- // the next address logs in on a fresh connection.
375
- if (shared && this.authWireRefs.size === 0) {
376
- this.releaseBinding()
377
- }
378
441
  }
379
442
 
380
443
  protected async sendSubscribe(state: SubState): Promise<void> {
381
444
  if (state.needsAuth && state.address !== undefined) {
445
+ if (state.authGeneration !== this.authGeneration) {
446
+ return
447
+ }
382
448
  // Re-assert the binding: a replay after a reconnect (which cleared it on
383
449
  // close) must restore the address account frames emit under.
384
450
  this.accountAddress = state.address.toLowerCase()
385
- await this.ensureLogin(state.address)
451
+ const login = this.ensureLogin(state.address)
452
+ await login
453
+ if (
454
+ this.loginPromise !== login ||
455
+ state.authGeneration !== this.authGeneration
456
+ ) {
457
+ return
458
+ }
386
459
  }
387
460
  this.rws.send(JSON.stringify({ op: 'subscribe', ...state.frame }))
388
461
  }
@@ -392,6 +465,11 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
392
465
  this.pendingFunding.clear()
393
466
  this.loginPromise = undefined
394
467
  this.accountAddress = undefined
468
+ this.authGeneration += 1
469
+ this.closeGeneration += 1
470
+ this.authWireRefs.clear()
471
+ this.authBindingRefs = 0
472
+ this.clearAccountSummary()
395
473
  }
396
474
 
397
475
  /** Wire subs for `sub`, keyed uniquely for the base's replay registry. */
@@ -523,14 +601,20 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
523
601
  */
524
602
  private ensureLogin(address: Address): Promise<void> {
525
603
  if (this.loginPromise === undefined) {
526
- this.loginPromise = (async () => {
527
- const token = await this.requireSession(address)
528
- this.rws.send(
529
- JSON.stringify({ op: 'login', args: { token: token.token } })
530
- )
531
- })()
532
- this.loginPromise.catch(() => {
533
- this.loginPromise = undefined
604
+ const login: Promise<void> = this.requireSession(address).then(
605
+ (token) => {
606
+ if (this.loginPromise === login) {
607
+ this.rws.send(
608
+ JSON.stringify({ op: 'login', args: { token: token.token } })
609
+ )
610
+ }
611
+ }
612
+ )
613
+ this.loginPromise = login
614
+ login.catch(() => {
615
+ if (this.loginPromise === login) {
616
+ this.loginPromise = undefined
617
+ }
534
618
  })
535
619
  }
536
620
  return this.loginPromise
@@ -548,24 +632,50 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
548
632
  * OndoSessionExpiredError} when the SIWE session is missing, matching the
549
633
  * other authenticated channels.
550
634
  */
551
- private async seedAccountSummary(address: Address): Promise<void> {
635
+ private async seedAccountSummary(
636
+ address: Address,
637
+ generation: number
638
+ ): Promise<void> {
552
639
  const token = await this.requireSession(address)
553
- this.accountSummary = await this.readBalance(token.token)
554
- this.emitAccountSummary()
640
+ if (generation !== this.accountSummaryGeneration) {
641
+ return
642
+ }
643
+ const balance = await this.readBalance(token.token)
644
+ if (generation !== this.accountSummaryGeneration) {
645
+ return
646
+ }
647
+ this.accountSummary = balance
648
+ this.emitAccountSummary(address)
555
649
  }
556
650
 
557
651
  /** Re-read the venue balance moved by the frame and re-emit the summary. */
558
- private async refreshAccountSummary(address: Address): Promise<void> {
652
+ private async refreshAccountSummary(
653
+ address: Address,
654
+ generation: number
655
+ ): Promise<void> {
559
656
  const token = await this.tokenStore.get(address)
560
- if (token === null || this.accountSummary === undefined) {
657
+ if (
658
+ token === null ||
659
+ this.accountSummary === undefined ||
660
+ generation !== this.accountSummaryGeneration
661
+ ) {
561
662
  return
562
663
  }
563
664
  const balance = await this.readBalance(token.token)
564
- if (this.accountSummary === undefined) {
665
+ if (
666
+ this.accountSummary === undefined ||
667
+ generation !== this.accountSummaryGeneration
668
+ ) {
565
669
  return
566
670
  }
567
671
  this.accountSummary = balance
568
- this.emitAccountSummary()
672
+ this.emitAccountSummary(address)
673
+ }
674
+
675
+ private clearAccountSummary(): void {
676
+ this.accountSummaryGeneration += 1
677
+ this.accountSummary = undefined
678
+ this.accountSummaryChain.reset()
569
679
  }
570
680
 
571
681
  private async readBalance(authToken: string): Promise<OndoAccountBalance> {
@@ -584,13 +694,12 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
584
694
  }
585
695
 
586
696
  /** Emit the current account summary from the venue balance figures. */
587
- private emitAccountSummary(): void {
697
+ private emitAccountSummary(address: Address): void {
588
698
  const balance = this.accountSummary
589
- const address = this.accountAddress
590
- if (balance === undefined || address === undefined) {
699
+ if (balance === undefined) {
591
700
  return
592
701
  }
593
- this.emit(`accountSummary:${address}`, {
702
+ this.emit(`accountSummary:${address.toLowerCase()}`, {
594
703
  channel: 'accountSummary',
595
704
  data: {
596
705
  portfolioValue: balance.marginBalance,
@@ -803,8 +912,9 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
803
912
  }
804
913
  this.emit(`fills:${address}`, { channel: 'fills', data: mapped })
805
914
  if (this.accountSummary !== undefined) {
915
+ const generation = this.accountSummaryGeneration
806
916
  this.accountSummaryChain.push(() =>
807
- this.refreshAccountSummary(address as Address)
917
+ this.refreshAccountSummary(address as Address, generation)
808
918
  )
809
919
  }
810
920
  }
@@ -829,8 +939,9 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
829
939
  data: mapped,
830
940
  })
831
941
  if (this.accountSummary !== undefined) {
942
+ const generation = this.accountSummaryGeneration
832
943
  this.accountSummaryChain.push(() =>
833
- this.refreshAccountSummary(address as Address)
944
+ this.refreshAccountSummary(address as Address, generation)
834
945
  )
835
946
  }
836
947
  }