@solard/sdk 0.2.33 → 0.2.37
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 +34 -34
- package/package.json +2 -2
- package/src/client.exact-sell.test.ts +95 -45
- package/src/client.listen-trades.test.ts +64 -34
- package/src/client.trade-fees.test.ts +37 -15
- package/src/client.ts +106 -48
- package/src/default.ts +2 -5
- package/src/export-surface.test.ts +36 -17
- package/src/index.ts +5 -4
- package/src/live.test.ts +191 -58
- package/src/live.ts +268 -83
- package/src/operator-api.architecture.test.ts +3 -1
package/src/live.ts
CHANGED
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
subscribeTrades as subscribeCoreTrades,
|
|
6
6
|
type TradeEvent,
|
|
7
7
|
type TradeSubscription,
|
|
8
|
-
type MigrationEvent,
|
|
9
|
-
createLiveTradeHistoryRecorder,
|
|
10
|
-
type LiveTradeHistoryOptions,
|
|
8
|
+
type MigrationEvent,
|
|
9
|
+
createLiveTradeHistoryRecorder,
|
|
10
|
+
type LiveTradeHistoryOptions,
|
|
11
11
|
} from "@solard/core";
|
|
12
12
|
|
|
13
13
|
export type LiveEndpointOptions = {
|
|
@@ -19,21 +19,29 @@ export type LiveEndpointOptions = {
|
|
|
19
19
|
export type SubscribeLaunchesOptions = Omit<
|
|
20
20
|
Parameters<typeof subscribeCoreLaunches>[0],
|
|
21
21
|
"connection"
|
|
22
|
-
> &
|
|
22
|
+
> &
|
|
23
|
+
LiveEndpointOptions;
|
|
23
24
|
export type SubscribeMigrationsOptions = Omit<
|
|
24
25
|
Parameters<typeof subscribeCoreMigrations>[0],
|
|
25
26
|
"connection"
|
|
26
|
-
> &
|
|
27
|
+
> &
|
|
28
|
+
LiveEndpointOptions;
|
|
27
29
|
export type SubscribeTradesOptions = Omit<
|
|
28
30
|
Parameters<typeof subscribeCoreTrades>[0],
|
|
29
31
|
"connection"
|
|
30
|
-
> &
|
|
32
|
+
> &
|
|
33
|
+
LiveEndpointOptions;
|
|
31
34
|
|
|
32
|
-
export type ListenTradesOptions = Omit<SubscribeTradesOptions, "onTrade"> & {
|
|
33
|
-
dbPath?: string;
|
|
34
|
-
/** Enabled by default; collected live history is partial and retains commitment. */
|
|
35
|
-
history?:
|
|
36
|
-
|
|
35
|
+
export type ListenTradesOptions = Omit<SubscribeTradesOptions, "onTrade"> & {
|
|
36
|
+
dbPath?: string;
|
|
37
|
+
/** Enabled by default; collected live history is partial and retains commitment. */
|
|
38
|
+
history?:
|
|
39
|
+
| false
|
|
40
|
+
| Pick<
|
|
41
|
+
LiveTradeHistoryOptions,
|
|
42
|
+
"flushIntervalMs" | "batchSize" | "maxPending"
|
|
43
|
+
>;
|
|
44
|
+
};
|
|
37
45
|
|
|
38
46
|
export type TradeListener = {
|
|
39
47
|
add(tokens: string | readonly string[]): Promise<void>;
|
|
@@ -41,11 +49,16 @@ export type TradeListener = {
|
|
|
41
49
|
has(token: string): boolean;
|
|
42
50
|
list(): string[];
|
|
43
51
|
onTrade(callback: (event: TradeEvent) => void | Promise<void>): () => void;
|
|
44
|
-
onMigration(
|
|
52
|
+
onMigration(
|
|
53
|
+
callback: (event: TradeListenerMigration) => void | Promise<void>,
|
|
54
|
+
): () => void;
|
|
45
55
|
close(): Promise<void>;
|
|
46
56
|
readonly closed: Promise<void>;
|
|
47
57
|
};
|
|
48
|
-
export type TradeListenerMigration = MigrationEvent & {
|
|
58
|
+
export type TradeListenerMigration = MigrationEvent & {
|
|
59
|
+
oldPool: string | null;
|
|
60
|
+
newPool: string;
|
|
61
|
+
};
|
|
49
62
|
|
|
50
63
|
type LiveConnection = {
|
|
51
64
|
endpoint: string;
|
|
@@ -60,39 +73,69 @@ let readiness: { endpoint: string; promise: Promise<void> } | null = null;
|
|
|
60
73
|
export class LiveEndpointPool {
|
|
61
74
|
private active = 0;
|
|
62
75
|
readonly endpoints: readonly string[];
|
|
63
|
-
constructor(
|
|
64
|
-
|
|
65
|
-
|
|
76
|
+
constructor(
|
|
77
|
+
endpoints: readonly string[],
|
|
78
|
+
private readonly probe: (endpoint: string) => Promise<void> = async (
|
|
79
|
+
endpoint,
|
|
80
|
+
) => {
|
|
81
|
+
await Promise.all([preflightRpc(endpoint), preflightWebSocket(endpoint)]);
|
|
82
|
+
},
|
|
83
|
+
) {
|
|
66
84
|
this.endpoints = [...new Set(endpoints)];
|
|
67
|
-
if (!this.endpoints.length)
|
|
85
|
+
if (!this.endpoints.length)
|
|
86
|
+
throw new Error("Live RPC endpoint pool cannot be empty");
|
|
68
87
|
for (const endpoint of this.endpoints) deriveWebSocketEndpoint(endpoint);
|
|
69
88
|
}
|
|
70
|
-
get endpoint(): string {
|
|
89
|
+
get endpoint(): string {
|
|
90
|
+
return this.endpoints[this.active]!;
|
|
91
|
+
}
|
|
71
92
|
async select(skipCurrent = false): Promise<string> {
|
|
72
93
|
const start = this.active;
|
|
73
94
|
let lastError: unknown;
|
|
74
|
-
for (
|
|
95
|
+
for (
|
|
96
|
+
let offset = skipCurrent ? 1 : 0;
|
|
97
|
+
offset < this.endpoints.length + (skipCurrent ? 1 : 0);
|
|
98
|
+
offset++
|
|
99
|
+
) {
|
|
75
100
|
const index = (start + offset) % this.endpoints.length;
|
|
76
|
-
try {
|
|
77
|
-
|
|
101
|
+
try {
|
|
102
|
+
await this.probe(this.endpoints[index]!);
|
|
103
|
+
this.active = index;
|
|
104
|
+
return this.endpoint;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
lastError = error;
|
|
107
|
+
}
|
|
78
108
|
}
|
|
79
109
|
throw lastError;
|
|
80
110
|
}
|
|
81
111
|
async check(): Promise<string> {
|
|
82
|
-
try {
|
|
83
|
-
|
|
112
|
+
try {
|
|
113
|
+
await this.probe(this.endpoint);
|
|
114
|
+
return this.endpoint;
|
|
115
|
+
} catch {
|
|
116
|
+
return await this.select(true);
|
|
117
|
+
}
|
|
84
118
|
}
|
|
85
119
|
}
|
|
86
120
|
const livePools = new Map<string, LiveEndpointPool>();
|
|
87
|
-
function endpointPool(
|
|
121
|
+
function endpointPool(
|
|
122
|
+
options: LiveEndpointOptions,
|
|
123
|
+
): LiveEndpointPool | undefined {
|
|
88
124
|
if (!options.rpcUrls?.length) return undefined;
|
|
89
125
|
const key = JSON.stringify(options.rpcUrls);
|
|
90
126
|
let pool = livePools.get(key);
|
|
91
|
-
if (!pool) {
|
|
127
|
+
if (!pool) {
|
|
128
|
+
pool = new LiveEndpointPool(options.rpcUrls);
|
|
129
|
+
livePools.set(key, pool);
|
|
130
|
+
}
|
|
92
131
|
return pool;
|
|
93
132
|
}
|
|
94
133
|
function connectionFor(endpoint: string): LiveConnection {
|
|
95
|
-
return {
|
|
134
|
+
return {
|
|
135
|
+
endpoint,
|
|
136
|
+
websocketEndpoint: deriveWebSocketEndpoint(endpoint),
|
|
137
|
+
connection: new Connection(endpoint, "confirmed"),
|
|
138
|
+
};
|
|
96
139
|
}
|
|
97
140
|
|
|
98
141
|
function endpointFromEnv(): string {
|
|
@@ -295,7 +338,9 @@ async function ensureReady(endpoint: string): Promise<void> {
|
|
|
295
338
|
}
|
|
296
339
|
}
|
|
297
340
|
|
|
298
|
-
async function liveConnection(
|
|
341
|
+
async function liveConnection(
|
|
342
|
+
options: LiveEndpointOptions = {},
|
|
343
|
+
): Promise<LiveConnection> {
|
|
299
344
|
const pool = endpointPool(options);
|
|
300
345
|
if (pool) return connectionFor(await pool.select());
|
|
301
346
|
const endpoint = endpointFromEnv();
|
|
@@ -327,26 +372,45 @@ export async function subscribeTrades(options: SubscribeTradesOptions) {
|
|
|
327
372
|
let live = await liveConnection(options);
|
|
328
373
|
notifyReady(options, live);
|
|
329
374
|
const pool = endpointPool(options);
|
|
330
|
-
if (!pool)
|
|
375
|
+
if (!pool)
|
|
376
|
+
return await subscribeCoreTrades({
|
|
377
|
+
...options,
|
|
378
|
+
connection: live.connection,
|
|
379
|
+
});
|
|
331
380
|
// Keep duplicate suppression across connection changes. Provider probes detect
|
|
332
381
|
// dead credentials/quota; they cannot diagnose a silent old socket when a new
|
|
333
382
|
// handshake to that provider remains healthy.
|
|
334
383
|
const seen = new Set<string>();
|
|
335
384
|
const onTrade = async (event: TradeEvent) => {
|
|
336
|
-
const key = [
|
|
385
|
+
const key = [
|
|
386
|
+
event.signature,
|
|
387
|
+
event.venue,
|
|
388
|
+
event.mint,
|
|
389
|
+
event.pool,
|
|
390
|
+
event.eventIndex ?? `${event.side}:${event.baseRaw}:${event.quoteRaw}`,
|
|
391
|
+
].join(":");
|
|
337
392
|
if (seen.has(key)) return;
|
|
338
393
|
seen.add(key);
|
|
339
394
|
if (seen.size > 4096) seen.delete(seen.values().next().value!);
|
|
340
395
|
await options.onTrade(event);
|
|
341
396
|
};
|
|
342
|
-
let subscription = await subscribeCoreTrades({
|
|
397
|
+
let subscription = await subscribeCoreTrades({
|
|
398
|
+
...options,
|
|
399
|
+
signal: undefined,
|
|
400
|
+
onTrade,
|
|
401
|
+
connection: live.connection,
|
|
402
|
+
});
|
|
343
403
|
let stopped = false;
|
|
344
404
|
let tail: Promise<unknown> = Promise.resolve();
|
|
345
405
|
const serial = <T>(work: () => Promise<T>): Promise<T> => {
|
|
346
|
-
const pending = tail.then(work, work);
|
|
406
|
+
const pending = tail.then(work, work);
|
|
407
|
+
tail = pending.catch(() => undefined);
|
|
408
|
+
return pending;
|
|
347
409
|
};
|
|
348
410
|
let settle!: () => void;
|
|
349
|
-
const closed = new Promise<void>((resolve) => {
|
|
411
|
+
const closed = new Promise<void>((resolve) => {
|
|
412
|
+
settle = resolve;
|
|
413
|
+
});
|
|
350
414
|
const requestedInterval = options.healthCheckIntervalMs ?? 30_000;
|
|
351
415
|
if (!Number.isFinite(requestedInterval) || requestedInterval < 1000) {
|
|
352
416
|
await subscription.close();
|
|
@@ -363,15 +427,25 @@ export async function subscribeTrades(options: SubscribeTradesOptions) {
|
|
|
363
427
|
if (stopped || endpoint === live.endpoint) return;
|
|
364
428
|
const tokens = subscription.listTokens();
|
|
365
429
|
const nextLive = connectionFor(endpoint);
|
|
366
|
-
const next = await subscribeCoreTrades({
|
|
430
|
+
const next = await subscribeCoreTrades({
|
|
431
|
+
...options,
|
|
432
|
+
signal: undefined,
|
|
433
|
+
tokens,
|
|
434
|
+
onTrade,
|
|
435
|
+
connection: nextLive.connection,
|
|
436
|
+
});
|
|
367
437
|
await subscription.close();
|
|
368
438
|
subscription = next;
|
|
369
439
|
live = nextLive;
|
|
370
440
|
notifyReady(options, live);
|
|
371
441
|
} catch (error) {
|
|
372
|
-
try {
|
|
442
|
+
try {
|
|
443
|
+
options.onStatus?.("rpc-unavailable", { error: safeMessage(error) });
|
|
444
|
+
} catch {}
|
|
373
445
|
}
|
|
374
|
-
}).finally(() => {
|
|
446
|
+
}).finally(() => {
|
|
447
|
+
checking = false;
|
|
448
|
+
});
|
|
375
449
|
}, Math.trunc(requestedInterval));
|
|
376
450
|
timer.unref?.();
|
|
377
451
|
const close = async () => {
|
|
@@ -379,17 +453,30 @@ export async function subscribeTrades(options: SubscribeTradesOptions) {
|
|
|
379
453
|
stopped = true;
|
|
380
454
|
clearInterval(timer);
|
|
381
455
|
options.signal?.removeEventListener("abort", abort);
|
|
382
|
-
await serial(async () => {
|
|
456
|
+
await serial(async () => {
|
|
457
|
+
await subscription.close();
|
|
458
|
+
settle();
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
const abort = () => {
|
|
462
|
+
void close();
|
|
383
463
|
};
|
|
384
|
-
const abort = () => { void close(); };
|
|
385
464
|
if (options.signal?.aborted) await close();
|
|
386
465
|
else options.signal?.addEventListener("abort", abort, { once: true });
|
|
387
466
|
return {
|
|
388
|
-
addTokens: (tokens: string | readonly string[]) =>
|
|
389
|
-
|
|
467
|
+
addTokens: (tokens: string | readonly string[]) =>
|
|
468
|
+
serial(async () => {
|
|
469
|
+
if (stopped) throw new Error("Trade subscription is closed");
|
|
470
|
+
await subscription.addTokens(tokens);
|
|
471
|
+
}),
|
|
472
|
+
removeTokens: (tokens: string | readonly string[]) =>
|
|
473
|
+
serial(async () => {
|
|
474
|
+
if (!stopped) await subscription.removeTokens(tokens);
|
|
475
|
+
}),
|
|
390
476
|
hasToken: (token: string) => !stopped && subscription.hasToken(token),
|
|
391
|
-
listTokens: () => stopped ? [] : subscription.listTokens(),
|
|
392
|
-
close,
|
|
477
|
+
listTokens: () => (stopped ? [] : subscription.listTokens()),
|
|
478
|
+
close,
|
|
479
|
+
closed,
|
|
393
480
|
} satisfies TradeSubscription;
|
|
394
481
|
}
|
|
395
482
|
|
|
@@ -413,25 +500,46 @@ export async function subscribeMigrations(options: SubscribeMigrationsOptions) {
|
|
|
413
500
|
export async function listenTrades(
|
|
414
501
|
options: ListenTradesOptions,
|
|
415
502
|
): Promise<TradeListener> {
|
|
416
|
-
const callbacks = new Set<(event: TradeEvent) => void | Promise<void>>();
|
|
417
|
-
const history =
|
|
503
|
+
const callbacks = new Set<(event: TradeEvent) => void | Promise<void>>();
|
|
504
|
+
const history =
|
|
505
|
+
options.history === false
|
|
506
|
+
? undefined
|
|
507
|
+
: createLiveTradeHistoryRecorder({
|
|
508
|
+
...options.history,
|
|
509
|
+
dbPath: options.dbPath,
|
|
510
|
+
commitment: options.commitment,
|
|
511
|
+
onStatus: options.onStatus,
|
|
512
|
+
});
|
|
418
513
|
const pending: TradeEvent[] = [];
|
|
419
|
-
const migrationCallbacks = new Set<
|
|
514
|
+
const migrationCallbacks = new Set<
|
|
515
|
+
(event: TradeListenerMigration) => void | Promise<void>
|
|
516
|
+
>();
|
|
420
517
|
const pendingMigrations: TradeListenerMigration[] = [];
|
|
421
518
|
const watched = new Set(options.tokens);
|
|
422
519
|
const lastPools = new Map<string, string | null>();
|
|
423
520
|
let subscription: TradeSubscription;
|
|
424
|
-
let migrations: Awaited<ReturnType<typeof subscribeMigrations>> | undefined;
|
|
425
|
-
let stopped = false;
|
|
426
|
-
let resolveClosed!: () => void;
|
|
427
|
-
let rejectClosed!: (error: unknown) => void;
|
|
428
|
-
const closed = new Promise<void>((resolve, reject) => {
|
|
429
|
-
|
|
521
|
+
let migrations: Awaited<ReturnType<typeof subscribeMigrations>> | undefined;
|
|
522
|
+
let stopped = false;
|
|
523
|
+
let resolveClosed!: () => void;
|
|
524
|
+
let rejectClosed!: (error: unknown) => void;
|
|
525
|
+
const closed = new Promise<void>((resolve, reject) => {
|
|
526
|
+
resolveClosed = resolve;
|
|
527
|
+
rejectClosed = reject;
|
|
528
|
+
});
|
|
529
|
+
void closed.catch(() => undefined);
|
|
430
530
|
let tail: Promise<unknown> = Promise.resolve();
|
|
431
|
-
const serial = <T>(work: () => Promise<T>): Promise<T> => {
|
|
531
|
+
const serial = <T>(work: () => Promise<T>): Promise<T> => {
|
|
532
|
+
const job = tail.then(work, work);
|
|
533
|
+
tail = job.catch(() => undefined);
|
|
534
|
+
return job;
|
|
535
|
+
};
|
|
432
536
|
const onMigration = async (event: MigrationEvent) => {
|
|
433
537
|
if (stopped || !watched.has(event.mint)) return;
|
|
434
|
-
const migration = {
|
|
538
|
+
const migration = {
|
|
539
|
+
...event,
|
|
540
|
+
oldPool: lastPools.get(event.mint) ?? null,
|
|
541
|
+
newPool: event.pool,
|
|
542
|
+
};
|
|
435
543
|
lastPools.set(event.mint, event.pool);
|
|
436
544
|
await serial(async () => {
|
|
437
545
|
if (stopped || !watched.has(event.mint)) return;
|
|
@@ -440,14 +548,24 @@ export async function listenTrades(
|
|
|
440
548
|
await subscription.removeTokens(event.mint);
|
|
441
549
|
await subscription.addTokens(event.mint);
|
|
442
550
|
});
|
|
443
|
-
if (!migrationCallbacks.size) {
|
|
444
|
-
|
|
551
|
+
if (!migrationCallbacks.size) {
|
|
552
|
+
pendingMigrations.push(migration);
|
|
553
|
+
if (pendingMigrations.length > 1000) pendingMigrations.shift();
|
|
554
|
+
} else
|
|
555
|
+
await Promise.all(
|
|
556
|
+
[...migrationCallbacks].map((callback) => callback(migration)),
|
|
557
|
+
);
|
|
445
558
|
};
|
|
446
|
-
const dispatch = async (event: TradeEvent) => {
|
|
447
|
-
if (!watched.has(event.mint) || stopped) return;
|
|
448
|
-
lastPools.set(event.mint, event.pool);
|
|
449
|
-
try {
|
|
450
|
-
|
|
559
|
+
const dispatch = async (event: TradeEvent) => {
|
|
560
|
+
if (!watched.has(event.mint) || stopped) return;
|
|
561
|
+
lastPools.set(event.mint, event.pool);
|
|
562
|
+
try {
|
|
563
|
+
await history?.record(event);
|
|
564
|
+
} catch (error) {
|
|
565
|
+
options.onStatus?.("history-persistence-error", {
|
|
566
|
+
error: safeMessage(error),
|
|
567
|
+
});
|
|
568
|
+
}
|
|
451
569
|
if (callbacks.size === 0) {
|
|
452
570
|
pending.push(event);
|
|
453
571
|
if (pending.length > 1_000) pending.shift();
|
|
@@ -461,32 +579,94 @@ export async function listenTrades(
|
|
|
461
579
|
};
|
|
462
580
|
const onStatus = (event: string, data?: Record<string, unknown>) => {
|
|
463
581
|
options.onStatus?.(event, data);
|
|
464
|
-
if (event === "rpc-ready" && migrations && !stopped)
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
582
|
+
if (event === "rpc-ready" && migrations && !stopped)
|
|
583
|
+
void serial(async () => {
|
|
584
|
+
if (stopped) return;
|
|
585
|
+
const next = await subscribeMigrations({
|
|
586
|
+
rpcUrls: options.rpcUrls,
|
|
587
|
+
tokens: [...watched],
|
|
588
|
+
commitment: options.commitment,
|
|
589
|
+
metadata: options.metadata,
|
|
590
|
+
onMigration,
|
|
591
|
+
onStatus: options.onStatus,
|
|
592
|
+
});
|
|
593
|
+
await migrations?.close();
|
|
594
|
+
migrations = next;
|
|
595
|
+
}).catch((error) =>
|
|
596
|
+
options.onStatus?.("migration-resubscribe-error", {
|
|
597
|
+
error: safeMessage(error),
|
|
598
|
+
}),
|
|
599
|
+
);
|
|
469
600
|
};
|
|
470
|
-
try {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
601
|
+
try {
|
|
602
|
+
subscription = await subscribeTrades({
|
|
603
|
+
...options,
|
|
604
|
+
signal: undefined,
|
|
605
|
+
onStatus,
|
|
606
|
+
onTrade: dispatch,
|
|
607
|
+
});
|
|
608
|
+
} catch (error) {
|
|
609
|
+
await history?.close();
|
|
610
|
+
throw error;
|
|
611
|
+
}
|
|
612
|
+
try {
|
|
613
|
+
migrations = await subscribeMigrations({
|
|
614
|
+
rpcUrls: options.rpcUrls,
|
|
615
|
+
tokens: [...watched],
|
|
616
|
+
commitment: options.commitment,
|
|
617
|
+
metadata: options.metadata,
|
|
618
|
+
onMigration,
|
|
619
|
+
onStatus: options.onStatus,
|
|
620
|
+
});
|
|
621
|
+
} catch (error) {
|
|
622
|
+
await subscription.close();
|
|
623
|
+
await history?.close();
|
|
624
|
+
throw error;
|
|
625
|
+
}
|
|
626
|
+
const close = async () => {
|
|
627
|
+
if (stopped) return await closed;
|
|
628
|
+
stopped = true;
|
|
629
|
+
options.signal?.removeEventListener("abort", abort);
|
|
630
|
+
try {
|
|
631
|
+
await serial(async () => {
|
|
632
|
+
await Promise.all([subscription.close(), migrations?.close()]);
|
|
633
|
+
watched.clear();
|
|
634
|
+
await history?.close();
|
|
635
|
+
});
|
|
636
|
+
resolveClosed();
|
|
637
|
+
} catch (error) {
|
|
638
|
+
rejectClosed(error);
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
641
|
+
};
|
|
642
|
+
const abort = () => {
|
|
643
|
+
void close();
|
|
481
644
|
};
|
|
482
|
-
const abort = () => { void close(); };
|
|
483
645
|
if (options.signal?.aborted) await close();
|
|
484
646
|
else options.signal?.addEventListener("abort", abort, { once: true });
|
|
485
647
|
return {
|
|
486
|
-
add: (tokens) =>
|
|
487
|
-
|
|
648
|
+
add: (tokens) =>
|
|
649
|
+
serial(async () => {
|
|
650
|
+
if (stopped) throw new Error("Trade listener is closed");
|
|
651
|
+
const values = typeof tokens === "string" ? [tokens] : [...tokens];
|
|
652
|
+
await subscription.addTokens(values);
|
|
653
|
+
await migrations!.addTokens(values);
|
|
654
|
+
values.forEach((token) => watched.add(token));
|
|
655
|
+
}),
|
|
656
|
+
remove: (tokens) =>
|
|
657
|
+
serial(async () => {
|
|
658
|
+
const values = typeof tokens === "string" ? [tokens] : [...tokens];
|
|
659
|
+
values.forEach((token) => {
|
|
660
|
+
watched.delete(token);
|
|
661
|
+
lastPools.delete(token);
|
|
662
|
+
});
|
|
663
|
+
await Promise.all([
|
|
664
|
+
subscription.removeTokens(values),
|
|
665
|
+
migrations!.removeTokens(values),
|
|
666
|
+
]);
|
|
667
|
+
}),
|
|
488
668
|
has: (token) => !stopped && watched.has(token),
|
|
489
|
-
list: () => stopped ? [] : [...watched],
|
|
669
|
+
list: () => (stopped ? [] : [...watched]),
|
|
490
670
|
onTrade(callback) {
|
|
491
671
|
callbacks.add(callback);
|
|
492
672
|
if (pending.length) {
|
|
@@ -499,12 +679,17 @@ export async function listenTrades(
|
|
|
499
679
|
},
|
|
500
680
|
onMigration(callback) {
|
|
501
681
|
migrationCallbacks.add(callback);
|
|
502
|
-
if (pendingMigrations.length) {
|
|
682
|
+
if (pendingMigrations.length) {
|
|
683
|
+
const rows = pendingMigrations.splice(0);
|
|
684
|
+
void Promise.resolve().then(async () => {
|
|
685
|
+
for (const event of rows) await callback(event);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
503
688
|
return () => migrationCallbacks.delete(callback);
|
|
504
689
|
},
|
|
505
|
-
close,
|
|
506
|
-
get closed() {
|
|
507
|
-
return closed;
|
|
690
|
+
close,
|
|
691
|
+
get closed() {
|
|
692
|
+
return closed;
|
|
508
693
|
},
|
|
509
694
|
};
|
|
510
695
|
}
|
|
@@ -14,7 +14,9 @@ describe("operator api", () => {
|
|
|
14
14
|
test("uses one object-form trade api", () => {
|
|
15
15
|
expect(client).toContain("input: SolardBuyInput");
|
|
16
16
|
expect(client).toContain("input: SolardSellInput");
|
|
17
|
-
expect(client).toContain(
|
|
17
|
+
expect(client).toContain(
|
|
18
|
+
"SolardTradeExecutionOptions = TradeExecutionOptions",
|
|
19
|
+
);
|
|
18
20
|
expect(client).toContain("exportPrivateKey");
|
|
19
21
|
expect(client).toContain("getTransaction(");
|
|
20
22
|
});
|