@imbingox/acex 0.4.0-beta.4 → 0.4.0-beta.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@imbingox/acex",
3
- "version": "0.4.0-beta.4",
3
+ "version": "0.4.0-beta.5",
4
4
  "description": "Multi-exchange trading SDK for market data, account, and order management",
5
5
  "repository": {
6
6
  "type": "git",
@@ -699,6 +699,7 @@ export class JuplendPrivateAdapter implements PrivateUserDataAdapter {
699
699
  private readonly options: {
700
700
  readonly fetchFn?: FetchLike;
701
701
  readonly httpTimeoutMs?: number;
702
+ readonly pollIntervalMs?: number;
702
703
  } = {},
703
704
  ) {}
704
705
 
@@ -762,13 +763,13 @@ export class JuplendPrivateAdapter implements PrivateUserDataAdapter {
762
763
  createPrivateStream(
763
764
  credentials: AccountCredentials,
764
765
  callbacks: PrivateStreamCallbacks,
765
- options: PrivateStreamOptions,
766
+ _options: PrivateStreamOptions,
766
767
  accountOptions?: Record<string, unknown>,
767
768
  ): StreamHandle {
768
769
  let closed = false;
769
770
  let timer: ReturnType<typeof setTimeout> | undefined;
770
771
  const pollIntervalMs =
771
- options.juplendPollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
772
+ this.options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
772
773
 
773
774
  const poll = async (): Promise<void> => {
774
775
  try {
@@ -210,7 +210,6 @@ export interface PrivateStreamOptions {
210
210
  reconnectDelayMs: number;
211
211
  reconnectMaxDelayMs: number;
212
212
  listenKeyKeepAliveMs: number;
213
- juplendPollIntervalMs?: number;
214
213
  now?: () => number;
215
214
  }
216
215
 
@@ -13,6 +13,7 @@ import type {
13
13
  PrivateRuntimeReason,
14
14
  PrivateRuntimeStatus,
15
15
  Venue,
16
+ VenueOrderCapabilities,
16
17
  } from "../types/index.ts";
17
18
 
18
19
  export interface RegisteredAccountRecord {
@@ -26,6 +27,7 @@ export interface ClientContext {
26
27
  now(): number;
27
28
  assertStarted(): void;
28
29
  getRegisteredAccount(accountId: string): RegisteredAccountRecord;
30
+ getPrivateOrderCapabilities(venue: Venue): VenueOrderCapabilities | undefined;
29
31
  ensurePrivateCredentials(accountId: string): void;
30
32
  subscribePrivateAccountFeed(accountId: string): Promise<void>;
31
33
  unsubscribePrivateAccountFeed(accountId: string): void;
@@ -105,13 +107,11 @@ export interface PrivateOrderDataConsumer {
105
107
 
106
108
  export function hasPrivateCredentials(
107
109
  credentials?: AccountCredentials,
108
- venue?: Venue,
110
+ credentialsRequired = true,
109
111
  ): boolean {
110
- if (venue === "juplend") {
111
- return true;
112
- }
113
-
114
- return Boolean(credentials?.apiKey && credentials.secret);
112
+ return credentialsRequired
113
+ ? Boolean(credentials?.apiKey && credentials.secret)
114
+ : true;
115
115
  }
116
116
 
117
117
  export function mergeCredentials(
@@ -73,7 +73,6 @@ export class PrivateSubscriptionCoordinator {
73
73
  private readonly streamReconnectMaxDelayMs: number;
74
74
  private readonly listenKeyKeepAliveMs: number;
75
75
  private readonly binanceRiskPollIntervalMs: number;
76
- private readonly juplendPollIntervalMs?: number;
77
76
  private readonly records = new Map<string, PrivateSubscriptionRecord>();
78
77
 
79
78
  constructor(
@@ -102,7 +101,6 @@ export class PrivateSubscriptionCoordinator {
102
101
  options.binance?.riskPollIntervalMs,
103
102
  DEFAULT_BINANCE_RISK_POLL_INTERVAL_MS,
104
103
  );
105
- this.juplendPollIntervalMs = options.juplend?.pollIntervalMs;
106
104
  }
107
105
 
108
106
  async subscribeAccountFeed(accountId: string): Promise<void> {
@@ -115,7 +113,8 @@ export class PrivateSubscriptionCoordinator {
115
113
  }
116
114
 
117
115
  try {
118
- if (record.venue === "juplend") {
116
+ const adapter = this.getAdapter(record.venue);
117
+ if (adapter.accountCapabilities.updates === "polling") {
119
118
  await this.bootstrapAccount(record, account);
120
119
  await this.ensureStream(record, account);
121
120
  } else {
@@ -233,7 +232,11 @@ export class PrivateSubscriptionCoordinator {
233
232
  this.stopAccountRefreshPolling(record);
234
233
 
235
234
  try {
236
- if (record.venue === "juplend" && record.accountSubscribed) {
235
+ const adapter = this.getAdapter(record.venue);
236
+ if (
237
+ adapter.accountCapabilities.updates === "polling" &&
238
+ record.accountSubscribed
239
+ ) {
237
240
  await this.bootstrapAccount(record, account);
238
241
  await this.ensureStream(record, account);
239
242
  } else {
@@ -318,7 +321,7 @@ export class PrivateSubscriptionCoordinator {
318
321
 
319
322
  private ensureAccountRefreshPolling(record: PrivateSubscriptionRecord): void {
320
323
  if (
321
- record.venue !== "binance" ||
324
+ typeof this.getAdapter(record.venue).refreshAccount !== "function" ||
322
325
  !record.accountSubscribed ||
323
326
  record.accountRefreshTimer ||
324
327
  record.accountRefreshInFlight
@@ -339,7 +342,10 @@ export class PrivateSubscriptionCoordinator {
339
342
  }
340
343
 
341
344
  private scheduleAccountRefreshPoll(record: PrivateSubscriptionRecord): void {
342
- if (record.venue !== "binance" || !record.accountSubscribed) {
345
+ if (
346
+ typeof this.getAdapter(record.venue).refreshAccount !== "function" ||
347
+ !record.accountSubscribed
348
+ ) {
343
349
  return;
344
350
  }
345
351
 
@@ -348,7 +354,7 @@ export class PrivateSubscriptionCoordinator {
348
354
  record.accountRefreshTimer = undefined;
349
355
  if (
350
356
  generation !== record.accountRefreshGeneration ||
351
- record.venue !== "binance" ||
357
+ typeof this.getAdapter(record.venue).refreshAccount !== "function" ||
352
358
  !record.accountSubscribed
353
359
  ) {
354
360
  return;
@@ -374,7 +380,10 @@ export class PrivateSubscriptionCoordinator {
374
380
  }
375
381
 
376
382
  record.accountRefreshInFlight = undefined;
377
- if (record.accountSubscribed && record.venue === "binance") {
383
+ if (
384
+ record.accountSubscribed &&
385
+ typeof this.getAdapter(record.venue).refreshAccount === "function"
386
+ ) {
378
387
  this.scheduleAccountRefreshPoll(record);
379
388
  }
380
389
  });
@@ -488,15 +497,15 @@ export class PrivateSubscriptionCoordinator {
488
497
  record: PrivateSubscriptionRecord,
489
498
  account: RegisteredAccountRecord,
490
499
  ): Promise<void> {
500
+ const adapter = this.getAdapter(record.venue);
491
501
  const credentials = account.credentials;
492
- if (!credentials && record.venue !== "juplend") {
502
+ if (adapter.accountCapabilities.credentialsRequired && !credentials) {
493
503
  throw new AcexError(
494
504
  "CREDENTIALS_MISSING",
495
505
  `Account credentials are required for private subscriptions: ${account.accountId}`,
496
506
  );
497
507
  }
498
508
 
499
- const adapter = this.getAdapter(record.venue);
500
509
  const stream = adapter.createPrivateStream(
501
510
  credentials ?? {},
502
511
  {
@@ -592,7 +601,6 @@ export class PrivateSubscriptionCoordinator {
592
601
  reconnectDelayMs: this.streamReconnectDelayMs,
593
602
  reconnectMaxDelayMs: this.streamReconnectMaxDelayMs,
594
603
  listenKeyKeepAliveMs: this.listenKeyKeepAliveMs,
595
- juplendPollIntervalMs: this.juplendPollIntervalMs,
596
604
  now: () => this.context.now(),
597
605
  },
598
606
  { ...account.options, accountId: account.accountId },
@@ -664,7 +672,8 @@ export class PrivateSubscriptionCoordinator {
664
672
  account: RegisteredAccountRecord,
665
673
  ): Promise<void> {
666
674
  try {
667
- const bootstrap = await this.getAdapter(record.venue).bootstrapAccount(
675
+ const adapter = this.getAdapter(record.venue);
676
+ const bootstrap = await adapter.bootstrapAccount(
668
677
  account.credentials ?? {},
669
678
  { ...account.options, accountId: account.accountId },
670
679
  );
@@ -700,7 +709,10 @@ export class PrivateSubscriptionCoordinator {
700
709
  ready: false,
701
710
  reason: transportReason(
702
711
  error,
703
- record.venue === "juplend" ? "http_failed" : "auth_failed",
712
+ this.getAdapter(record.venue).accountCapabilities
713
+ .credentialsRequired
714
+ ? "auth_failed"
715
+ : "http_failed",
704
716
  ),
705
717
  },
706
718
  );
@@ -38,6 +38,7 @@ import type {
38
38
  StopOptions,
39
39
  Venue,
40
40
  VenueCapabilities,
41
+ VenueOrderCapabilities,
41
42
  } from "../types/index.ts";
42
43
  import {
43
44
  type ClientContext,
@@ -114,6 +115,9 @@ export class AcexClientImpl implements AcexClient, ClientContext {
114
115
  new JuplendPrivateAdapter(
115
116
  options.account?.juplend?.rpcUrl,
116
117
  options.account?.juplend?.jupApiKey,
118
+ {
119
+ pollIntervalMs: options.account?.juplend?.pollIntervalMs,
120
+ },
117
121
  ),
118
122
  ];
119
123
  this.privateAdapters = new Map(
@@ -298,9 +302,20 @@ export class AcexClientImpl implements AcexClient, ClientContext {
298
302
  return account;
299
303
  }
300
304
 
305
+ getPrivateOrderCapabilities(
306
+ venue: Venue,
307
+ ): VenueOrderCapabilities | undefined {
308
+ return this.privateAdapters.get(venue)?.orderCapabilities;
309
+ }
310
+
301
311
  ensurePrivateCredentials(accountId: string): void {
302
312
  const account = this.getRegisteredAccount(accountId);
303
- if (hasPrivateCredentials(account.credentials, account.venue)) {
313
+ if (
314
+ hasPrivateCredentials(
315
+ account.credentials,
316
+ this.getPrivateCredentialsRequired(account.venue),
317
+ )
318
+ ) {
304
319
  return;
305
320
  }
306
321
 
@@ -429,7 +444,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
429
444
  private getPrivateCommandAccount(accountId: string): RegisteredAccountRecord {
430
445
  const account = this.getRegisteredAccount(accountId);
431
446
  const adapter = this.getPrivateAdapter(account.venue);
432
- if (adapter.venue === "juplend") {
447
+ if (!adapter.orderCapabilities.supported) {
433
448
  throw this.createError(
434
449
  "VENUE_NOT_SUPPORTED",
435
450
  `Venue does not support private order commands: ${account.venue}`,
@@ -437,7 +452,12 @@ export class AcexClientImpl implements AcexClient, ClientContext {
437
452
  );
438
453
  }
439
454
 
440
- if (!hasPrivateCredentials(account.credentials, account.venue)) {
455
+ if (
456
+ !hasPrivateCredentials(
457
+ account.credentials,
458
+ adapter.accountCapabilities.credentialsRequired,
459
+ )
460
+ ) {
441
461
  throw this.createError(
442
462
  "CREDENTIALS_MISSING",
443
463
  `Account credentials are required for private order commands: ${accountId}`,
@@ -448,6 +468,13 @@ export class AcexClientImpl implements AcexClient, ClientContext {
448
468
  return account;
449
469
  }
450
470
 
471
+ private getPrivateCredentialsRequired(venue: Venue): boolean {
472
+ return (
473
+ this.privateAdapters.get(venue)?.accountCapabilities
474
+ .credentialsRequired ?? true
475
+ );
476
+ }
477
+
451
478
  private getPrivateAdapter(venue: Venue): PrivateUserDataAdapter {
452
479
  const adapter = this.privateAdapters.get(venue);
453
480
  if (!adapter) {
@@ -102,7 +102,10 @@ export class OrderManagerImpl
102
102
  async subscribeOrders(input: SubscribeOrdersInput): Promise<void> {
103
103
  this.context.assertStarted();
104
104
  const account = this.context.getRegisteredAccount(input.accountId);
105
- if (account.venue === "juplend") {
105
+ if (
106
+ this.context.getPrivateOrderCapabilities(account.venue)?.updates ===
107
+ "unsupported"
108
+ ) {
106
109
  throw this.createError(
107
110
  "VENUE_NOT_SUPPORTED",
108
111
  `Venue does not support private order subscriptions: ${account.venue}`,