@lifi/perps-sdk-provider-ondo 1.0.0 → 1.0.2

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 (65) hide show
  1. package/README.md +43 -28
  2. package/dist/cjs/OndoProvider.d.ts.map +1 -1
  3. package/dist/cjs/OndoProvider.js +6 -2
  4. package/dist/cjs/OndoProvider.js.map +1 -1
  5. package/dist/cjs/auth/OndoApiKeyStore.d.ts +1 -0
  6. package/dist/cjs/auth/OndoApiKeyStore.d.ts.map +1 -1
  7. package/dist/cjs/auth/OndoApiKeyStore.js +3 -2
  8. package/dist/cjs/auth/OndoApiKeyStore.js.map +1 -1
  9. package/dist/cjs/auth/OndoTokenStore.d.ts.map +1 -1
  10. package/dist/cjs/auth/OndoTokenStore.js +2 -3
  11. package/dist/cjs/auth/OndoTokenStore.js.map +1 -1
  12. package/dist/cjs/auth/signActions.d.ts +1 -1
  13. package/dist/cjs/auth/signActions.d.ts.map +1 -1
  14. package/dist/cjs/auth/signActions.js +16 -7
  15. package/dist/cjs/auth/signActions.js.map +1 -1
  16. package/dist/cjs/types/auth.d.ts +0 -1
  17. package/dist/cjs/types/auth.d.ts.map +1 -1
  18. package/dist/cjs/types/wire.d.ts +7 -0
  19. package/dist/cjs/types/wire.d.ts.map +1 -1
  20. package/dist/cjs/websocket/OndoWsProvider.d.ts +3 -0
  21. package/dist/cjs/websocket/OndoWsProvider.d.ts.map +1 -1
  22. package/dist/cjs/websocket/OndoWsProvider.js +55 -11
  23. package/dist/cjs/websocket/OndoWsProvider.js.map +1 -1
  24. package/dist/esm/OndoProvider.d.ts.map +1 -1
  25. package/dist/esm/OndoProvider.js +7 -3
  26. package/dist/esm/OndoProvider.js.map +1 -1
  27. package/dist/esm/auth/OndoApiKeyStore.d.ts +3 -1
  28. package/dist/esm/auth/OndoApiKeyStore.d.ts.map +1 -1
  29. package/dist/esm/auth/OndoApiKeyStore.js +3 -2
  30. package/dist/esm/auth/OndoApiKeyStore.js.map +1 -1
  31. package/dist/esm/auth/OndoTokenStore.d.ts.map +1 -1
  32. package/dist/esm/auth/OndoTokenStore.js +2 -3
  33. package/dist/esm/auth/OndoTokenStore.js.map +1 -1
  34. package/dist/esm/auth/signActions.d.ts +2 -2
  35. package/dist/esm/auth/signActions.d.ts.map +1 -1
  36. package/dist/esm/auth/signActions.js +29 -12
  37. package/dist/esm/auth/signActions.js.map +1 -1
  38. package/dist/esm/types/auth.d.ts +0 -2
  39. package/dist/esm/types/auth.d.ts.map +1 -1
  40. package/dist/esm/types/wire.d.ts +12 -0
  41. package/dist/esm/types/wire.d.ts.map +1 -1
  42. package/dist/esm/websocket/OndoWsProvider.d.ts +19 -0
  43. package/dist/esm/websocket/OndoWsProvider.d.ts.map +1 -1
  44. package/dist/esm/websocket/OndoWsProvider.js +84 -16
  45. package/dist/esm/websocket/OndoWsProvider.js.map +1 -1
  46. package/dist/types/OndoProvider.d.ts.map +1 -1
  47. package/dist/types/auth/OndoApiKeyStore.d.ts +3 -1
  48. package/dist/types/auth/OndoApiKeyStore.d.ts.map +1 -1
  49. package/dist/types/auth/OndoTokenStore.d.ts.map +1 -1
  50. package/dist/types/auth/signActions.d.ts +2 -2
  51. package/dist/types/auth/signActions.d.ts.map +1 -1
  52. package/dist/types/types/auth.d.ts +0 -2
  53. package/dist/types/types/auth.d.ts.map +1 -1
  54. package/dist/types/types/wire.d.ts +12 -0
  55. package/dist/types/types/wire.d.ts.map +1 -1
  56. package/dist/types/websocket/OndoWsProvider.d.ts +19 -0
  57. package/dist/types/websocket/OndoWsProvider.d.ts.map +1 -1
  58. package/package.json +3 -3
  59. package/src/OndoProvider.ts +10 -2
  60. package/src/auth/OndoApiKeyStore.ts +3 -2
  61. package/src/auth/OndoTokenStore.ts +1 -3
  62. package/src/auth/signActions.ts +33 -12
  63. package/src/types/auth.ts +0 -2
  64. package/src/types/wire.ts +13 -0
  65. package/src/websocket/OndoWsProvider.ts +90 -21
@@ -70,6 +70,13 @@ const ONDO_AUTH_CHANNEL = {
70
70
 
71
71
  type AuthChannel = keyof typeof ONDO_AUTH_CHANNEL
72
72
 
73
+ // Fan-out channels that reserve the connection's single authenticated-address
74
+ // binding; `accountSummary` rides the auth wires without being one itself.
75
+ const ONDO_AUTH_FANOUT: readonly string[] = [
76
+ ...Object.keys(ONDO_AUTH_CHANNEL),
77
+ 'accountSummary',
78
+ ]
79
+
73
80
  const isAuthChannel = (
74
81
  channel: Subscription['channel']
75
82
  ): channel is AuthChannel => channel in ONDO_AUTH_CHANNEL
@@ -157,9 +164,11 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
157
164
  )
158
165
  this.client = client
159
166
  this.registry = client && getMarketRegistry(client, providerKey)
160
- // The venue's login lives and dies with the connection.
167
+ // The venue's login lives and dies with the connection, so a drop must
168
+ // forget both the in-flight login and the address it bound.
161
169
  this.rws.on('close', () => {
162
170
  this.loginPromise = undefined
171
+ this.accountAddress = undefined
163
172
  })
164
173
  }
165
174
 
@@ -217,31 +226,35 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
217
226
  const needsLogin =
218
227
  isAuthChannel(sub.channel) || sub.channel === 'accountSummary'
219
228
 
229
+ let boundHere = false
220
230
  if (needsLogin) {
221
- // One login per connection means one authenticated address.
222
231
  const address = (sub as { address: Address }).address.toLowerCase()
223
- if (
224
- this.accountAddress !== undefined &&
225
- this.accountAddress !== address
226
- ) {
227
- throw new Error(
228
- `Ondo WS supports one authenticated address per connection; already bound to ${this.accountAddress}, cannot subscribe for ${address}.`
229
- )
230
- }
231
- this.accountAddress = address
232
- // Account frames carry venue market symbols; the registry supplies the
233
- // market identity the mapped orders/fills/positions embed.
234
- await this.registry?.sync()
232
+ boundHere = this.accountAddress !== address
233
+ this.bindAddress(address)
235
234
  }
236
235
 
237
- if (sub.channel === 'accountSummary') {
238
- await this.seedAccountSummary((sub as { address: Address }).address)
239
- }
240
- // Authenticated wire subs are shared and ref-counted; public ones are 1:1.
241
- for (const [key, state] of wireSubs) {
242
- await this.acquireWire(key, state, needsLogin)
236
+ try {
237
+ if (needsLogin) {
238
+ // Account frames carry venue market symbols; the registry supplies the
239
+ // market identity the mapped orders/fills/positions embed.
240
+ await this.registry?.sync()
241
+ }
242
+ if (sub.channel === 'accountSummary') {
243
+ await this.seedAccountSummary((sub as { address: Address }).address)
244
+ }
245
+ // Authenticated wire subs are shared and ref-counted; public ones are 1:1.
246
+ for (const [key, state] of wireSubs) {
247
+ await this.acquireWire(key, state, needsLogin)
248
+ }
249
+ await this.rws.ready()
250
+ } catch (err) {
251
+ // Login/seed failed before the binding took hold on the wire: release a
252
+ // binding this call newly reserved so a later subscribe can rebind.
253
+ if (boundHere) {
254
+ this.accountAddress = undefined
255
+ }
256
+ throw err
243
257
  }
244
- await this.rws.ready()
245
258
 
246
259
  return () => {
247
260
  if (sub.channel === 'accountSummary') {
@@ -253,6 +266,53 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
253
266
  }
254
267
  }
255
268
 
269
+ /**
270
+ * Reserve the connection's single authenticated slot for `address`. A prior
271
+ * binding whose channels have all been released — still lingering in the
272
+ * base teardown window — is reclaimed (cycling the connection) before the
273
+ * new one takes it; a binding that another address still holds live throws.
274
+ */
275
+ private bindAddress(address: string): void {
276
+ if (this.accountAddress === address) {
277
+ return
278
+ }
279
+ if (this.accountAddress !== undefined && !this.releaseBindingIfIdle()) {
280
+ throw new Error(
281
+ `Ondo WS supports one authenticated address per connection; already bound to ${this.accountAddress}, cannot subscribe for ${address}.`
282
+ )
283
+ }
284
+ this.accountAddress = address
285
+ }
286
+
287
+ /**
288
+ * Force-tear-down the bound address's authenticated channels when every one
289
+ * is idle, which cycles the connection via {@link releaseBinding}. Returns
290
+ * false — leaving the binding intact — while any is still live.
291
+ */
292
+ private releaseBindingIfIdle(): boolean {
293
+ const bound = this.accountAddress
294
+ if (bound === undefined) {
295
+ return true
296
+ }
297
+ for (const channel of ONDO_AUTH_FANOUT) {
298
+ if (!this.closeChannelIfIdle(`${channel}:${bound}`)) {
299
+ return false
300
+ }
301
+ }
302
+ return true
303
+ }
304
+
305
+ /**
306
+ * Forget the authenticated binding and cycle the connection. The venue
307
+ * permits one login per socket, so a later subscribe for a different address
308
+ * must authenticate on a fresh connection rather than re-login on this one.
309
+ */
310
+ private releaseBinding(): void {
311
+ this.accountAddress = undefined
312
+ this.loginPromise = undefined
313
+ this.rws.reconnect()
314
+ }
315
+
256
316
  /** Register a wire sub, ref-counting the shared authenticated channels. */
257
317
  private async acquireWire(
258
318
  key: string,
@@ -288,10 +348,18 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
288
348
  }
289
349
  this.unregisterSub(key)
290
350
  this.rws.send(JSON.stringify({ op: 'unsubscribe', ...state.frame }))
351
+ // Last authenticated wire gone: forget the binding and cycle the socket so
352
+ // the next address logs in on a fresh connection.
353
+ if (shared && this.authWireRefs.size === 0) {
354
+ this.releaseBinding()
355
+ }
291
356
  }
292
357
 
293
358
  protected async sendSubscribe(state: SubState): Promise<void> {
294
359
  if (state.needsAuth && state.address !== undefined) {
360
+ // Re-assert the binding: a replay after a reconnect (which cleared it on
361
+ // close) must restore the address account frames emit under.
362
+ this.accountAddress = state.address.toLowerCase()
295
363
  await this.ensureLogin(state.address)
296
364
  }
297
365
  this.rws.send(JSON.stringify({ op: 'subscribe', ...state.frame }))
@@ -301,6 +369,7 @@ export class OndoWsProvider extends WsProviderBase<SubState> {
301
369
  this.contexts = {}
302
370
  this.pendingFunding.clear()
303
371
  this.loginPromise = undefined
372
+ this.accountAddress = undefined
304
373
  }
305
374
 
306
375
  /** Wire subs for `sub`, keyed uniquely for the base's replay registry. */