@imbingox/acex 0.3.0-beta.0 → 0.3.0-beta.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.
- package/README.md +73 -23
- package/package.json +9 -3
- package/src/adapters/binance/adapter.ts +1 -1
- package/src/adapters/binance/market-catalog.ts +2 -2
- package/src/adapters/binance/private-adapter.ts +14 -4
- package/src/adapters/juplend/private-adapter.ts +483 -0
- package/src/adapters/types.ts +27 -4
- package/src/client/context.ts +16 -11
- package/src/client/private-subscription-coordinator.ts +101 -47
- package/src/client/runtime.ts +43 -20
- package/src/errors.ts +1 -1
- package/src/internal/filters.ts +9 -9
- package/src/managers/account-manager.ts +95 -58
- package/src/managers/market-manager.ts +129 -44
- package/src/managers/order-manager.ts +49 -56
- package/src/types/account.ts +30 -10
- package/src/types/client.ts +2 -2
- package/src/types/market.ts +40 -16
- package/src/types/order.ts +8 -7
- package/src/types/shared.ts +43 -7
|
@@ -3,7 +3,7 @@ import type {
|
|
|
3
3
|
StreamHandle,
|
|
4
4
|
} from "../adapters/types.ts";
|
|
5
5
|
import { AcexError } from "../errors.ts";
|
|
6
|
-
import type { AccountRuntimeOptions,
|
|
6
|
+
import type { AccountRuntimeOptions, Venue } from "../types/index.ts";
|
|
7
7
|
import type {
|
|
8
8
|
ClientContext,
|
|
9
9
|
PrivateAccountDataConsumer,
|
|
@@ -13,7 +13,7 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
interface PrivateSubscriptionRecord {
|
|
15
15
|
accountId: string;
|
|
16
|
-
|
|
16
|
+
venue: Venue;
|
|
17
17
|
accountSubscribed: boolean;
|
|
18
18
|
ordersSubscribed: boolean;
|
|
19
19
|
accountReady: boolean;
|
|
@@ -30,24 +30,27 @@ const DEFAULT_LISTEN_KEY_KEEPALIVE_MS = 30 * 60 * 1_000;
|
|
|
30
30
|
|
|
31
31
|
export class PrivateSubscriptionCoordinator {
|
|
32
32
|
private readonly context: ClientContext;
|
|
33
|
-
private readonly
|
|
33
|
+
private readonly adapters: Map<Venue, PrivateUserDataAdapter>;
|
|
34
34
|
private readonly accountConsumer: PrivateAccountDataConsumer;
|
|
35
35
|
private readonly orderConsumer: PrivateOrderDataConsumer;
|
|
36
36
|
private readonly streamOpenTimeoutMs: number;
|
|
37
37
|
private readonly streamReconnectDelayMs: number;
|
|
38
38
|
private readonly streamReconnectMaxDelayMs: number;
|
|
39
39
|
private readonly listenKeyKeepAliveMs: number;
|
|
40
|
+
private readonly juplendPollIntervalMs?: number;
|
|
40
41
|
private readonly records = new Map<string, PrivateSubscriptionRecord>();
|
|
41
42
|
|
|
42
43
|
constructor(
|
|
43
44
|
context: ClientContext,
|
|
44
|
-
|
|
45
|
+
adapters: PrivateUserDataAdapter[],
|
|
45
46
|
accountConsumer: PrivateAccountDataConsumer,
|
|
46
47
|
orderConsumer: PrivateOrderDataConsumer,
|
|
47
48
|
options: AccountRuntimeOptions = {},
|
|
48
49
|
) {
|
|
49
50
|
this.context = context;
|
|
50
|
-
this.
|
|
51
|
+
this.adapters = new Map(
|
|
52
|
+
adapters.map((adapter) => [adapter.venue, adapter]),
|
|
53
|
+
);
|
|
51
54
|
this.accountConsumer = accountConsumer;
|
|
52
55
|
this.orderConsumer = orderConsumer;
|
|
53
56
|
this.streamOpenTimeoutMs =
|
|
@@ -59,6 +62,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
59
62
|
DEFAULT_STREAM_RECONNECT_MAX_DELAY_MS;
|
|
60
63
|
this.listenKeyKeepAliveMs =
|
|
61
64
|
options.listenKeyKeepAliveMs ?? DEFAULT_LISTEN_KEY_KEEPALIVE_MS;
|
|
65
|
+
this.juplendPollIntervalMs = options.juplend?.pollIntervalMs;
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
async subscribeAccountFeed(accountId: string): Promise<void> {
|
|
@@ -67,12 +71,17 @@ export class PrivateSubscriptionCoordinator {
|
|
|
67
71
|
const needsPending = !record.stream && !record.startPromise;
|
|
68
72
|
record.accountSubscribed = true;
|
|
69
73
|
if (needsPending) {
|
|
70
|
-
this.accountConsumer.onPrivateAccountPending(accountId, record.
|
|
74
|
+
this.accountConsumer.onPrivateAccountPending(accountId, record.venue);
|
|
71
75
|
}
|
|
72
76
|
|
|
73
77
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
if (record.venue === "juplend") {
|
|
79
|
+
await this.bootstrapAccount(record, account);
|
|
80
|
+
await this.ensureStream(record, account);
|
|
81
|
+
} else {
|
|
82
|
+
await this.ensureStream(record, account);
|
|
83
|
+
await this.bootstrapAccount(record, account);
|
|
84
|
+
}
|
|
76
85
|
} catch (error) {
|
|
77
86
|
record.accountSubscribed = false;
|
|
78
87
|
this.closeIfUnused(record);
|
|
@@ -96,7 +105,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
96
105
|
const needsPending = !record.stream && !record.startPromise;
|
|
97
106
|
record.ordersSubscribed = true;
|
|
98
107
|
if (needsPending) {
|
|
99
|
-
this.orderConsumer.onPrivateOrderPending(accountId, record.
|
|
108
|
+
this.orderConsumer.onPrivateOrderPending(accountId, record.venue);
|
|
100
109
|
}
|
|
101
110
|
|
|
102
111
|
try {
|
|
@@ -128,13 +137,13 @@ export class PrivateSubscriptionCoordinator {
|
|
|
128
137
|
if (record.accountSubscribed) {
|
|
129
138
|
this.accountConsumer.onPrivateAccountPending(
|
|
130
139
|
record.accountId,
|
|
131
|
-
record.
|
|
140
|
+
record.venue,
|
|
132
141
|
);
|
|
133
142
|
}
|
|
134
143
|
if (record.ordersSubscribed) {
|
|
135
144
|
this.orderConsumer.onPrivateOrderPending(
|
|
136
145
|
record.accountId,
|
|
137
|
-
record.
|
|
146
|
+
record.venue,
|
|
138
147
|
);
|
|
139
148
|
}
|
|
140
149
|
|
|
@@ -165,10 +174,10 @@ export class PrivateSubscriptionCoordinator {
|
|
|
165
174
|
}
|
|
166
175
|
|
|
167
176
|
if (record.accountSubscribed) {
|
|
168
|
-
this.accountConsumer.onPrivateAccountPending(accountId, record.
|
|
177
|
+
this.accountConsumer.onPrivateAccountPending(accountId, record.venue);
|
|
169
178
|
}
|
|
170
179
|
if (record.ordersSubscribed) {
|
|
171
|
-
this.orderConsumer.onPrivateOrderPending(accountId, record.
|
|
180
|
+
this.orderConsumer.onPrivateOrderPending(accountId, record.venue);
|
|
172
181
|
}
|
|
173
182
|
|
|
174
183
|
void this.resumeRecord(record);
|
|
@@ -179,9 +188,14 @@ export class PrivateSubscriptionCoordinator {
|
|
|
179
188
|
this.closeStream(record);
|
|
180
189
|
|
|
181
190
|
try {
|
|
182
|
-
|
|
183
|
-
if (record.accountSubscribed) {
|
|
191
|
+
if (record.venue === "juplend" && record.accountSubscribed) {
|
|
184
192
|
await this.bootstrapAccount(record, account);
|
|
193
|
+
await this.ensureStream(record, account);
|
|
194
|
+
} else {
|
|
195
|
+
await this.ensureStream(record, account);
|
|
196
|
+
if (record.accountSubscribed) {
|
|
197
|
+
await this.bootstrapAccount(record, account);
|
|
198
|
+
}
|
|
185
199
|
}
|
|
186
200
|
if (record.ordersSubscribed) {
|
|
187
201
|
await this.bootstrapOrders(record, account);
|
|
@@ -193,16 +207,28 @@ export class PrivateSubscriptionCoordinator {
|
|
|
193
207
|
|
|
194
208
|
private getAccount(accountId: string): RegisteredAccountRecord {
|
|
195
209
|
const account = this.context.getRegisteredAccount(accountId);
|
|
196
|
-
if (
|
|
210
|
+
if (!this.adapters.has(account.venue)) {
|
|
197
211
|
throw new AcexError(
|
|
198
|
-
"
|
|
199
|
-
`
|
|
212
|
+
"VENUE_NOT_SUPPORTED",
|
|
213
|
+
`Venue is not supported yet: ${account.venue}`,
|
|
200
214
|
);
|
|
201
215
|
}
|
|
202
216
|
|
|
203
217
|
return account;
|
|
204
218
|
}
|
|
205
219
|
|
|
220
|
+
private getAdapter(venue: Venue): PrivateUserDataAdapter {
|
|
221
|
+
const adapter = this.adapters.get(venue);
|
|
222
|
+
if (!adapter) {
|
|
223
|
+
throw new AcexError(
|
|
224
|
+
"VENUE_NOT_SUPPORTED",
|
|
225
|
+
`Venue is not supported yet: ${venue}`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return adapter;
|
|
230
|
+
}
|
|
231
|
+
|
|
206
232
|
private getOrCreateRecord(
|
|
207
233
|
account: RegisteredAccountRecord,
|
|
208
234
|
): PrivateSubscriptionRecord {
|
|
@@ -213,7 +239,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
213
239
|
|
|
214
240
|
const record: PrivateSubscriptionRecord = {
|
|
215
241
|
accountId: account.accountId,
|
|
216
|
-
|
|
242
|
+
venue: account.venue,
|
|
217
243
|
accountSubscribed: false,
|
|
218
244
|
ordersSubscribed: false,
|
|
219
245
|
accountReady: false,
|
|
@@ -275,9 +301,22 @@ export class PrivateSubscriptionCoordinator {
|
|
|
275
301
|
);
|
|
276
302
|
}
|
|
277
303
|
|
|
278
|
-
const
|
|
304
|
+
const adapter = this.getAdapter(record.venue);
|
|
305
|
+
const stream = adapter.createPrivateStream(
|
|
279
306
|
credentials,
|
|
280
307
|
{
|
|
308
|
+
onAccountSnapshot: (snapshot) => {
|
|
309
|
+
if (!record.accountSubscribed) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
record.accountReady = true;
|
|
314
|
+
this.accountConsumer.onPrivateAccountBootstrap(
|
|
315
|
+
record.accountId,
|
|
316
|
+
record.venue,
|
|
317
|
+
snapshot,
|
|
318
|
+
);
|
|
319
|
+
},
|
|
281
320
|
onAccountUpdate: (update) => {
|
|
282
321
|
if (!record.accountSubscribed) {
|
|
283
322
|
return;
|
|
@@ -286,7 +325,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
286
325
|
record.accountReady = true;
|
|
287
326
|
this.accountConsumer.onPrivateAccountUpdate(
|
|
288
327
|
record.accountId,
|
|
289
|
-
record.
|
|
328
|
+
record.venue,
|
|
290
329
|
update,
|
|
291
330
|
);
|
|
292
331
|
},
|
|
@@ -298,7 +337,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
298
337
|
record.orderReady = true;
|
|
299
338
|
this.orderConsumer.onPrivateOrderUpdate(
|
|
300
339
|
record.accountId,
|
|
301
|
-
record.
|
|
340
|
+
record.venue,
|
|
302
341
|
update,
|
|
303
342
|
);
|
|
304
343
|
},
|
|
@@ -306,7 +345,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
306
345
|
if (record.accountSubscribed) {
|
|
307
346
|
this.accountConsumer.onPrivateAccountStreamState(
|
|
308
347
|
record.accountId,
|
|
309
|
-
record.
|
|
348
|
+
record.venue,
|
|
310
349
|
{
|
|
311
350
|
runtimeStatus: "reconnecting",
|
|
312
351
|
ready: record.accountReady,
|
|
@@ -317,7 +356,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
317
356
|
if (record.ordersSubscribed) {
|
|
318
357
|
this.orderConsumer.onPrivateOrderStreamState(
|
|
319
358
|
record.accountId,
|
|
320
|
-
record.
|
|
359
|
+
record.venue,
|
|
321
360
|
{
|
|
322
361
|
runtimeStatus: "reconnecting",
|
|
323
362
|
ready: record.orderReady,
|
|
@@ -338,8 +377,19 @@ export class PrivateSubscriptionCoordinator {
|
|
|
338
377
|
onError: (error) => {
|
|
339
378
|
this.context.publishRuntimeError("adapter", error, {
|
|
340
379
|
accountId: record.accountId,
|
|
341
|
-
|
|
380
|
+
venue: record.venue,
|
|
342
381
|
});
|
|
382
|
+
if (record.accountSubscribed) {
|
|
383
|
+
this.accountConsumer.onPrivateAccountStreamState(
|
|
384
|
+
record.accountId,
|
|
385
|
+
record.venue,
|
|
386
|
+
{
|
|
387
|
+
runtimeStatus: "degraded",
|
|
388
|
+
ready: record.accountReady,
|
|
389
|
+
reason: "http_failed",
|
|
390
|
+
},
|
|
391
|
+
);
|
|
392
|
+
}
|
|
343
393
|
},
|
|
344
394
|
},
|
|
345
395
|
{
|
|
@@ -347,6 +397,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
347
397
|
reconnectDelayMs: this.streamReconnectDelayMs,
|
|
348
398
|
reconnectMaxDelayMs: this.streamReconnectMaxDelayMs,
|
|
349
399
|
listenKeyKeepAliveMs: this.listenKeyKeepAliveMs,
|
|
400
|
+
juplendPollIntervalMs: this.juplendPollIntervalMs,
|
|
350
401
|
now: () => this.context.now(),
|
|
351
402
|
},
|
|
352
403
|
account.options,
|
|
@@ -361,16 +412,16 @@ export class PrivateSubscriptionCoordinator {
|
|
|
361
412
|
const runtimeError =
|
|
362
413
|
error instanceof Error
|
|
363
414
|
? error
|
|
364
|
-
: new Error(
|
|
415
|
+
: new Error(`Failed to open ${record.venue} private stream`);
|
|
365
416
|
this.context.publishRuntimeError("adapter", runtimeError, {
|
|
366
417
|
accountId: record.accountId,
|
|
367
|
-
|
|
418
|
+
venue: record.venue,
|
|
368
419
|
});
|
|
369
420
|
|
|
370
421
|
if (record.accountSubscribed) {
|
|
371
422
|
this.accountConsumer.onPrivateAccountStreamState(
|
|
372
423
|
record.accountId,
|
|
373
|
-
record.
|
|
424
|
+
record.venue,
|
|
374
425
|
{
|
|
375
426
|
runtimeStatus: "degraded",
|
|
376
427
|
ready: record.accountReady,
|
|
@@ -381,7 +432,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
381
432
|
if (record.ordersSubscribed) {
|
|
382
433
|
this.orderConsumer.onPrivateOrderStreamState(
|
|
383
434
|
record.accountId,
|
|
384
|
-
record.
|
|
435
|
+
record.venue,
|
|
385
436
|
{
|
|
386
437
|
runtimeStatus: "degraded",
|
|
387
438
|
ready: record.orderReady,
|
|
@@ -402,16 +453,13 @@ export class PrivateSubscriptionCoordinator {
|
|
|
402
453
|
if (record.accountSubscribed) {
|
|
403
454
|
this.accountConsumer.onPrivateAccountPending(
|
|
404
455
|
record.accountId,
|
|
405
|
-
record.
|
|
456
|
+
record.venue,
|
|
406
457
|
);
|
|
407
458
|
await this.bootstrapAccount(record, account);
|
|
408
459
|
}
|
|
409
460
|
|
|
410
461
|
if (record.ordersSubscribed) {
|
|
411
|
-
this.orderConsumer.onPrivateOrderPending(
|
|
412
|
-
record.accountId,
|
|
413
|
-
record.exchange,
|
|
414
|
-
);
|
|
462
|
+
this.orderConsumer.onPrivateOrderPending(record.accountId, record.venue);
|
|
415
463
|
await this.bootstrapOrders(record, account);
|
|
416
464
|
}
|
|
417
465
|
}
|
|
@@ -421,9 +469,9 @@ export class PrivateSubscriptionCoordinator {
|
|
|
421
469
|
account: RegisteredAccountRecord,
|
|
422
470
|
): Promise<void> {
|
|
423
471
|
try {
|
|
424
|
-
const bootstrap = await this.
|
|
472
|
+
const bootstrap = await this.getAdapter(record.venue).bootstrapAccount(
|
|
425
473
|
account.credentials ?? {},
|
|
426
|
-
account.options,
|
|
474
|
+
{ ...account.options, accountId: account.accountId },
|
|
427
475
|
);
|
|
428
476
|
if (!record.accountSubscribed) {
|
|
429
477
|
return;
|
|
@@ -432,7 +480,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
432
480
|
record.accountReady = true;
|
|
433
481
|
this.accountConsumer.onPrivateAccountBootstrap(
|
|
434
482
|
record.accountId,
|
|
435
|
-
record.
|
|
483
|
+
record.venue,
|
|
436
484
|
bootstrap,
|
|
437
485
|
);
|
|
438
486
|
} catch (error) {
|
|
@@ -441,24 +489,28 @@ export class PrivateSubscriptionCoordinator {
|
|
|
441
489
|
"adapter",
|
|
442
490
|
error instanceof Error
|
|
443
491
|
? error
|
|
444
|
-
: new Error(
|
|
492
|
+
: new Error(
|
|
493
|
+
`Failed to bootstrap ${record.venue} private account state`,
|
|
494
|
+
),
|
|
445
495
|
{
|
|
446
496
|
accountId: record.accountId,
|
|
447
|
-
|
|
497
|
+
venue: record.venue,
|
|
448
498
|
},
|
|
449
499
|
);
|
|
450
500
|
this.accountConsumer.onPrivateAccountStreamState(
|
|
451
501
|
record.accountId,
|
|
452
|
-
record.
|
|
502
|
+
record.venue,
|
|
453
503
|
{
|
|
454
504
|
runtimeStatus: "degraded",
|
|
455
505
|
ready: false,
|
|
456
|
-
reason: "auth_failed",
|
|
506
|
+
reason: record.venue === "juplend" ? "http_failed" : "auth_failed",
|
|
457
507
|
},
|
|
458
508
|
);
|
|
509
|
+
const reason =
|
|
510
|
+
error instanceof Error && error.message ? ` (${error.message})` : "";
|
|
459
511
|
throw new AcexError(
|
|
460
512
|
"ACCOUNT_BOOTSTRAP_FAILED",
|
|
461
|
-
`Failed to bootstrap account data: ${record.accountId}`,
|
|
513
|
+
`Failed to bootstrap account data: ${record.accountId}${reason}`,
|
|
462
514
|
);
|
|
463
515
|
}
|
|
464
516
|
}
|
|
@@ -468,7 +520,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
468
520
|
account: RegisteredAccountRecord,
|
|
469
521
|
): Promise<void> {
|
|
470
522
|
try {
|
|
471
|
-
const snapshots = await this.
|
|
523
|
+
const snapshots = await this.getAdapter(record.venue).bootstrapOpenOrders(
|
|
472
524
|
account.credentials ?? {},
|
|
473
525
|
account.options,
|
|
474
526
|
);
|
|
@@ -479,7 +531,7 @@ export class PrivateSubscriptionCoordinator {
|
|
|
479
531
|
record.orderReady = true;
|
|
480
532
|
this.orderConsumer.onPrivateOrderBootstrap(
|
|
481
533
|
record.accountId,
|
|
482
|
-
record.
|
|
534
|
+
record.venue,
|
|
483
535
|
snapshots,
|
|
484
536
|
);
|
|
485
537
|
} catch (error) {
|
|
@@ -488,15 +540,17 @@ export class PrivateSubscriptionCoordinator {
|
|
|
488
540
|
"adapter",
|
|
489
541
|
error instanceof Error
|
|
490
542
|
? error
|
|
491
|
-
: new Error(
|
|
543
|
+
: new Error(
|
|
544
|
+
`Failed to bootstrap ${record.venue} private order state`,
|
|
545
|
+
),
|
|
492
546
|
{
|
|
493
547
|
accountId: record.accountId,
|
|
494
|
-
|
|
548
|
+
venue: record.venue,
|
|
495
549
|
},
|
|
496
550
|
);
|
|
497
551
|
this.orderConsumer.onPrivateOrderStreamState(
|
|
498
552
|
record.accountId,
|
|
499
|
-
record.
|
|
553
|
+
record.venue,
|
|
500
554
|
{
|
|
501
555
|
runtimeStatus: "degraded",
|
|
502
556
|
ready: false,
|
package/src/client/runtime.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BinanceMarketAdapter } from "../adapters/binance/adapter.ts";
|
|
2
2
|
import { BinancePrivateAdapter } from "../adapters/binance/private-adapter.ts";
|
|
3
|
+
import { JuplendPrivateAdapter } from "../adapters/juplend/private-adapter.ts";
|
|
3
4
|
import type {
|
|
4
5
|
CancelAllOrdersRequest,
|
|
5
6
|
CancelOrderRequest,
|
|
@@ -33,6 +34,7 @@ import type {
|
|
|
33
34
|
RegisterAccountInput,
|
|
34
35
|
RegisterAccountResult,
|
|
35
36
|
StopOptions,
|
|
37
|
+
Venue,
|
|
36
38
|
} from "../types/index.ts";
|
|
37
39
|
import {
|
|
38
40
|
type ClientContext,
|
|
@@ -87,14 +89,20 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
87
89
|
private readonly marketManager: MarketManagerImpl;
|
|
88
90
|
private readonly accountManager: AccountManagerImpl;
|
|
89
91
|
private readonly orderManager: OrderManagerImpl;
|
|
90
|
-
private readonly
|
|
92
|
+
private readonly privateAdapters: Map<string, PrivateUserDataAdapter>;
|
|
91
93
|
private readonly privateCoordinator: PrivateSubscriptionCoordinator;
|
|
92
94
|
|
|
93
95
|
constructor(options: CreateClientOptions = {}) {
|
|
94
96
|
activeClients.add(this);
|
|
95
97
|
|
|
96
98
|
const marketAdapter = new BinanceMarketAdapter();
|
|
97
|
-
|
|
99
|
+
const privateAdapters = [
|
|
100
|
+
new BinancePrivateAdapter(),
|
|
101
|
+
new JuplendPrivateAdapter(),
|
|
102
|
+
];
|
|
103
|
+
this.privateAdapters = new Map(
|
|
104
|
+
privateAdapters.map((adapter) => [adapter.venue, adapter]),
|
|
105
|
+
);
|
|
98
106
|
|
|
99
107
|
this.marketManager = new MarketManagerImpl(this, marketAdapter, {
|
|
100
108
|
initialL1TimeoutMs: options.market?.l1InitialMessageTimeoutMs,
|
|
@@ -106,7 +114,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
106
114
|
this.orderManager = new OrderManagerImpl(this);
|
|
107
115
|
this.privateCoordinator = new PrivateSubscriptionCoordinator(
|
|
108
116
|
this,
|
|
109
|
-
|
|
117
|
+
privateAdapters,
|
|
110
118
|
this.accountManager as PrivateAccountDataConsumer,
|
|
111
119
|
this.orderManager as PrivateOrderDataConsumer,
|
|
112
120
|
options.account,
|
|
@@ -141,20 +149,20 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
141
149
|
throw this.createError(
|
|
142
150
|
"ACCOUNT_ALREADY_EXISTS",
|
|
143
151
|
`Account already exists: ${input.accountId}`,
|
|
144
|
-
{ accountId: input.accountId,
|
|
152
|
+
{ accountId: input.accountId, venue: input.venue },
|
|
145
153
|
);
|
|
146
154
|
}
|
|
147
155
|
|
|
148
156
|
this.registeredAccounts.set(input.accountId, {
|
|
149
157
|
accountId: input.accountId,
|
|
150
|
-
|
|
158
|
+
venue: input.venue,
|
|
151
159
|
credentials: input.credentials,
|
|
152
|
-
options: input.options,
|
|
160
|
+
options: input.options as Record<string, unknown> | undefined,
|
|
153
161
|
});
|
|
154
162
|
|
|
155
163
|
return {
|
|
156
164
|
accountId: input.accountId,
|
|
157
|
-
|
|
165
|
+
venue: input.venue,
|
|
158
166
|
};
|
|
159
167
|
}
|
|
160
168
|
|
|
@@ -177,8 +185,8 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
177
185
|
return;
|
|
178
186
|
}
|
|
179
187
|
|
|
180
|
-
this.accountManager.onCredentialsUpdated(accountId, account.
|
|
181
|
-
this.orderManager.onCredentialsUpdated(accountId, account.
|
|
188
|
+
this.accountManager.onCredentialsUpdated(accountId, account.venue);
|
|
189
|
+
this.orderManager.onCredentialsUpdated(accountId, account.venue);
|
|
182
190
|
this.privateCoordinator.onCredentialsUpdated(accountId);
|
|
183
191
|
}
|
|
184
192
|
|
|
@@ -262,14 +270,14 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
262
270
|
|
|
263
271
|
ensurePrivateCredentials(accountId: string): void {
|
|
264
272
|
const account = this.getRegisteredAccount(accountId);
|
|
265
|
-
if (hasPrivateCredentials(account.credentials)) {
|
|
273
|
+
if (hasPrivateCredentials(account.credentials, account.venue)) {
|
|
266
274
|
return;
|
|
267
275
|
}
|
|
268
276
|
|
|
269
277
|
throw this.createError(
|
|
270
278
|
"CREDENTIALS_MISSING",
|
|
271
279
|
`Account credentials are required for private subscriptions: ${accountId}`,
|
|
272
|
-
{ accountId,
|
|
280
|
+
{ accountId, venue: account.venue },
|
|
273
281
|
);
|
|
274
282
|
}
|
|
275
283
|
|
|
@@ -297,12 +305,13 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
297
305
|
type: input.type,
|
|
298
306
|
amount: input.amount,
|
|
299
307
|
price: input.type === "limit" ? input.price : undefined,
|
|
308
|
+
postOnly: input.type === "limit" ? input.postOnly : undefined,
|
|
300
309
|
clientOrderId: input.clientOrderId,
|
|
301
310
|
reduceOnly: input.reduceOnly,
|
|
302
311
|
positionSide: input.positionSide,
|
|
303
312
|
};
|
|
304
313
|
|
|
305
|
-
return this.
|
|
314
|
+
return this.getPrivateAdapter(account.venue).createOrder(
|
|
306
315
|
account.credentials ?? {},
|
|
307
316
|
request,
|
|
308
317
|
account.options,
|
|
@@ -317,7 +326,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
317
326
|
clientOrderId: input.clientOrderId,
|
|
318
327
|
};
|
|
319
328
|
|
|
320
|
-
return this.
|
|
329
|
+
return this.getPrivateAdapter(account.venue).cancelOrder(
|
|
321
330
|
account.credentials ?? {},
|
|
322
331
|
request,
|
|
323
332
|
account.options,
|
|
@@ -330,7 +339,7 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
330
339
|
symbol: input.symbol,
|
|
331
340
|
};
|
|
332
341
|
|
|
333
|
-
return this.
|
|
342
|
+
return this.getPrivateAdapter(account.venue).cancelAllOrders(
|
|
334
343
|
account.credentials ?? {},
|
|
335
344
|
request,
|
|
336
345
|
account.options,
|
|
@@ -389,22 +398,36 @@ export class AcexClientImpl implements AcexClient, ClientContext {
|
|
|
389
398
|
|
|
390
399
|
private getPrivateCommandAccount(accountId: string): RegisteredAccountRecord {
|
|
391
400
|
const account = this.getRegisteredAccount(accountId);
|
|
392
|
-
|
|
401
|
+
const adapter = this.getPrivateAdapter(account.venue);
|
|
402
|
+
if (adapter.venue === "juplend") {
|
|
393
403
|
throw this.createError(
|
|
394
|
-
"
|
|
395
|
-
`
|
|
396
|
-
{ accountId,
|
|
404
|
+
"VENUE_NOT_SUPPORTED",
|
|
405
|
+
`Venue does not support private order commands: ${account.venue}`,
|
|
406
|
+
{ accountId, venue: account.venue },
|
|
397
407
|
);
|
|
398
408
|
}
|
|
399
409
|
|
|
400
|
-
if (!hasPrivateCredentials(account.credentials)) {
|
|
410
|
+
if (!hasPrivateCredentials(account.credentials, account.venue)) {
|
|
401
411
|
throw this.createError(
|
|
402
412
|
"CREDENTIALS_MISSING",
|
|
403
413
|
`Account credentials are required for private order commands: ${accountId}`,
|
|
404
|
-
{ accountId,
|
|
414
|
+
{ accountId, venue: account.venue },
|
|
405
415
|
);
|
|
406
416
|
}
|
|
407
417
|
|
|
408
418
|
return account;
|
|
409
419
|
}
|
|
420
|
+
|
|
421
|
+
private getPrivateAdapter(venue: Venue): PrivateUserDataAdapter {
|
|
422
|
+
const adapter = this.privateAdapters.get(venue);
|
|
423
|
+
if (!adapter) {
|
|
424
|
+
throw this.createError(
|
|
425
|
+
"VENUE_NOT_SUPPORTED",
|
|
426
|
+
`Venue is not supported yet: ${venue}`,
|
|
427
|
+
{ venue },
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return adapter;
|
|
432
|
+
}
|
|
410
433
|
}
|
package/src/errors.ts
CHANGED
package/src/internal/filters.ts
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AccountEventFilter,
|
|
3
|
-
Exchange,
|
|
4
3
|
HealthEvent,
|
|
5
4
|
HealthEventFilter,
|
|
6
5
|
MarketEventFilter,
|
|
7
6
|
OrderEventFilter,
|
|
7
|
+
Venue,
|
|
8
8
|
} from "../types/index.ts";
|
|
9
9
|
|
|
10
10
|
export function matchesMarketFilter(
|
|
11
|
-
event: {
|
|
11
|
+
event: { venue: Venue; symbol: string },
|
|
12
12
|
filter?: MarketEventFilter,
|
|
13
13
|
): boolean {
|
|
14
14
|
if (!filter) {
|
|
15
15
|
return true;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
if (filter.
|
|
18
|
+
if (filter.venue && event.venue !== filter.venue) {
|
|
19
19
|
return false;
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ export function matchesMarketFilter(
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
export function matchesAccountFilter(
|
|
30
|
-
event: { accountId: string;
|
|
30
|
+
event: { accountId: string; venue: Venue; symbol?: string },
|
|
31
31
|
filter?: AccountEventFilter,
|
|
32
32
|
): boolean {
|
|
33
33
|
if (!filter) {
|
|
@@ -38,7 +38,7 @@ export function matchesAccountFilter(
|
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
if (filter.
|
|
41
|
+
if (filter.venue && event.venue !== filter.venue) {
|
|
42
42
|
return false;
|
|
43
43
|
}
|
|
44
44
|
|
|
@@ -50,7 +50,7 @@ export function matchesAccountFilter(
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export function matchesOrderFilter(
|
|
53
|
-
event: { accountId: string;
|
|
53
|
+
event: { accountId: string; venue: Venue; symbol?: string },
|
|
54
54
|
filter?: OrderEventFilter,
|
|
55
55
|
): boolean {
|
|
56
56
|
if (!filter) {
|
|
@@ -61,7 +61,7 @@ export function matchesOrderFilter(
|
|
|
61
61
|
return false;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
if (filter.
|
|
64
|
+
if (filter.venue && event.venue !== filter.venue) {
|
|
65
65
|
return false;
|
|
66
66
|
}
|
|
67
67
|
|
|
@@ -95,8 +95,8 @@ export function matchesHealthFilter(
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
if (filter.
|
|
99
|
-
if (!("
|
|
98
|
+
if (filter.venue) {
|
|
99
|
+
if (!("venue" in event) || event.venue !== filter.venue) {
|
|
100
100
|
return false;
|
|
101
101
|
}
|
|
102
102
|
}
|