@imbingox/acex 0.4.0-beta.1 → 0.4.0-beta.11
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 +4 -3
- package/docs/api.md +477 -1002
- package/package.json +1 -1
- package/src/adapters/binance/adapter.ts +19 -1
- package/src/adapters/binance/market-catalog.ts +83 -12
- package/src/adapters/binance/private-adapter.ts +302 -59
- package/src/adapters/binance/rate-limit.ts +47 -0
- package/src/adapters/binance/server-time.ts +106 -0
- package/src/adapters/juplend/private-adapter.ts +97 -68
- package/src/adapters/types.ts +25 -1
- package/src/client/context.ts +26 -9
- package/src/client/private-subscription-coordinator.ts +898 -63
- package/src/client/runtime.ts +49 -11
- package/src/client/venue-capabilities.ts +1 -0
- package/src/errors.ts +156 -2
- package/src/index.ts +8 -1
- package/src/internal/http-client.ts +608 -0
- package/src/internal/rate-limiter.ts +181 -0
- package/src/internal/watermark.ts +83 -0
- package/src/managers/account-manager.ts +227 -23
- package/src/managers/market-manager.ts +224 -34
- package/src/managers/order-manager.ts +991 -84
- package/src/types/client.ts +1 -0
- package/src/types/market.ts +25 -0
- package/src/types/order.ts +1 -0
- package/src/types/shared.ts +66 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type HttpClientMessages,
|
|
3
|
+
httpRequest,
|
|
4
|
+
isTransportError,
|
|
5
|
+
} from "../../internal/http-client.ts";
|
|
6
|
+
import type {
|
|
7
|
+
RateLimiter,
|
|
8
|
+
RateLimitScope,
|
|
9
|
+
VenueServerTime,
|
|
10
|
+
} from "../../types/index.ts";
|
|
11
|
+
import { parseBinanceRateLimitUsage } from "./rate-limit.ts";
|
|
12
|
+
|
|
13
|
+
type FetchLike = (
|
|
14
|
+
input: string | URL | Request,
|
|
15
|
+
init?: RequestInit,
|
|
16
|
+
) => Promise<Response>;
|
|
17
|
+
|
|
18
|
+
interface BinanceServerTimeResponse {
|
|
19
|
+
serverTime?: unknown;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FetchBinanceServerTimeOptions {
|
|
23
|
+
readonly rateLimiter?: RateLimiter;
|
|
24
|
+
readonly fetchFn?: FetchLike;
|
|
25
|
+
readonly now?: () => number;
|
|
26
|
+
readonly monotonicNow?: () => number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const BINANCE_USDM_SERVER_TIME_URL = "https://fapi.binance.com/fapi/v1/time";
|
|
30
|
+
const DEFAULT_HTTP_TIMEOUT_MS = 10_000;
|
|
31
|
+
const BINANCE_SERVER_TIME_HTTP_MESSAGES: HttpClientMessages = {
|
|
32
|
+
http: ({ status, statusText }) =>
|
|
33
|
+
`Binance server time request failed: ${status} ${statusText ?? ""}`,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export async function fetchBinanceServerTime(
|
|
37
|
+
options: FetchBinanceServerTimeOptions = {},
|
|
38
|
+
): Promise<VenueServerTime> {
|
|
39
|
+
const fetchFn = options.fetchFn ?? fetch;
|
|
40
|
+
const now = options.now ?? Date.now;
|
|
41
|
+
const monotonicNow = options.monotonicNow ?? (() => performance.now());
|
|
42
|
+
const scope: RateLimitScope = {
|
|
43
|
+
venue: "binance",
|
|
44
|
+
endpointKey: "GET /fapi/v1/time",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
await options.rateLimiter?.beforeRequest({ scope });
|
|
48
|
+
|
|
49
|
+
const requestSentAt = now();
|
|
50
|
+
const startMono = monotonicNow();
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const response = await httpRequest<BinanceServerTimeResponse>({
|
|
54
|
+
fetchFn,
|
|
55
|
+
url: BINANCE_USDM_SERVER_TIME_URL,
|
|
56
|
+
timeoutMs: DEFAULT_HTTP_TIMEOUT_MS,
|
|
57
|
+
parseAs: "json",
|
|
58
|
+
jsonParseMode: "response",
|
|
59
|
+
retryPolicy: {
|
|
60
|
+
idempotent: true,
|
|
61
|
+
maxAttempts: 1,
|
|
62
|
+
},
|
|
63
|
+
messages: BINANCE_SERVER_TIME_HTTP_MESSAGES,
|
|
64
|
+
});
|
|
65
|
+
const responseReceivedAt = now();
|
|
66
|
+
const endMono = monotonicNow();
|
|
67
|
+
|
|
68
|
+
await options.rateLimiter?.afterResponse(
|
|
69
|
+
{ scope },
|
|
70
|
+
{
|
|
71
|
+
status: response.status,
|
|
72
|
+
headers: response.headers,
|
|
73
|
+
usage: parseBinanceRateLimitUsage(response.headers),
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
const { serverTime } = response.body;
|
|
78
|
+
if (typeof serverTime !== "number" || !Number.isFinite(serverTime)) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
"Binance server time response missing numeric serverTime",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
serverTime,
|
|
86
|
+
requestSentAt,
|
|
87
|
+
responseReceivedAt,
|
|
88
|
+
roundTripMs: endMono - startMono,
|
|
89
|
+
estimatedOffsetMs: serverTime - (requestSentAt + responseReceivedAt) / 2,
|
|
90
|
+
};
|
|
91
|
+
} catch (error) {
|
|
92
|
+
if (isTransportError(error)) {
|
|
93
|
+
await options.rateLimiter?.onTransportError(
|
|
94
|
+
{ scope },
|
|
95
|
+
{
|
|
96
|
+
status: error.status,
|
|
97
|
+
headers: error.headers,
|
|
98
|
+
retryAfterMs: error.retryAfterMs,
|
|
99
|
+
usage: parseBinanceRateLimitUsage(error.headers),
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
throw error;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import BigNumber from "bignumber.js";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type HttpClientMessages,
|
|
4
|
+
httpRequest,
|
|
5
|
+
} from "../../internal/http-client.ts";
|
|
3
6
|
import type {
|
|
4
7
|
AccountCredentials,
|
|
5
8
|
VenueAccountCapabilities,
|
|
@@ -66,6 +69,8 @@ interface JuplendPriceApiEntry {
|
|
|
66
69
|
decimals?: number | string;
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
type FetchLike = typeof fetch;
|
|
73
|
+
|
|
69
74
|
interface JuplendTokenSearchEntry {
|
|
70
75
|
id?: string;
|
|
71
76
|
address?: string;
|
|
@@ -86,6 +91,13 @@ const DEFAULT_HTTP_TIMEOUT_MS = 10_000;
|
|
|
86
91
|
// not mint-atomic token amounts.
|
|
87
92
|
const POSITION_AMOUNT_SCALE_DECIMALS = 9;
|
|
88
93
|
const VAULT_CACHE_TTL_MS = 60 * 60 * 1_000;
|
|
94
|
+
function getJuplendHttpMessages(timeoutMs: number): HttpClientMessages {
|
|
95
|
+
return {
|
|
96
|
+
http: ({ status, statusText }) => `Juplend HTTP ${status}: ${statusText}`,
|
|
97
|
+
timeout: () => `Juplend fetch timeout after ${timeoutMs}ms`,
|
|
98
|
+
aborted: () => "Juplend fetch aborted",
|
|
99
|
+
};
|
|
100
|
+
}
|
|
89
101
|
|
|
90
102
|
interface JuplendVaultEnrichmentCacheEntry {
|
|
91
103
|
loadedAt: number;
|
|
@@ -260,52 +272,30 @@ function buildRisk(input: {
|
|
|
260
272
|
};
|
|
261
273
|
}
|
|
262
274
|
|
|
263
|
-
async function
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
signal: controller.signal,
|
|
286
|
-
});
|
|
287
|
-
if (!response.ok) {
|
|
288
|
-
throw new Error(
|
|
289
|
-
`Juplend HTTP ${response.status}: ${response.statusText}`,
|
|
290
|
-
);
|
|
291
|
-
}
|
|
275
|
+
async function requestJuplendJson<T>(
|
|
276
|
+
url: string,
|
|
277
|
+
init: RequestInit | undefined,
|
|
278
|
+
fetchFn: FetchLike | undefined,
|
|
279
|
+
timeoutMs: number,
|
|
280
|
+
): Promise<T> {
|
|
281
|
+
const response = await httpRequest<T>({
|
|
282
|
+
fetchFn,
|
|
283
|
+
url,
|
|
284
|
+
method: init?.method,
|
|
285
|
+
headers: init?.headers,
|
|
286
|
+
body: init?.body,
|
|
287
|
+
signal: init?.signal ?? undefined,
|
|
288
|
+
timeoutMs,
|
|
289
|
+
parseAs: "json",
|
|
290
|
+
jsonParseMode: "response",
|
|
291
|
+
retryPolicy: {
|
|
292
|
+
idempotent: true,
|
|
293
|
+
maxAttempts: 3,
|
|
294
|
+
},
|
|
295
|
+
messages: getJuplendHttpMessages(timeoutMs),
|
|
296
|
+
});
|
|
292
297
|
|
|
293
|
-
|
|
294
|
-
} catch (error) {
|
|
295
|
-
if (error instanceof Error && error.name === "AbortError") {
|
|
296
|
-
throw new Error(
|
|
297
|
-
timedOut
|
|
298
|
-
? `Juplend fetch timeout after ${DEFAULT_HTTP_TIMEOUT_MS}ms`
|
|
299
|
-
: "Juplend fetch aborted",
|
|
300
|
-
);
|
|
301
|
-
}
|
|
302
|
-
throw error;
|
|
303
|
-
} finally {
|
|
304
|
-
clearTimeout(timeout);
|
|
305
|
-
if (upstreamSignal) {
|
|
306
|
-
upstreamSignal.removeEventListener("abort", onUpstreamAbort);
|
|
307
|
-
}
|
|
308
|
-
}
|
|
298
|
+
return response.body;
|
|
309
299
|
}
|
|
310
300
|
|
|
311
301
|
function getJupApiKey(explicitApiKey?: string): string | undefined {
|
|
@@ -326,12 +316,19 @@ function withBaseUrl(baseUrl: string, path: string): string {
|
|
|
326
316
|
|
|
327
317
|
async function loadVaultMetadataFromLiteApi(
|
|
328
318
|
apiKey?: string,
|
|
319
|
+
fetchFn?: FetchLike,
|
|
320
|
+
timeoutMs = DEFAULT_HTTP_TIMEOUT_MS,
|
|
329
321
|
): Promise<Map<string, JuplendVaultMetadata>> {
|
|
330
|
-
const response = await
|
|
322
|
+
const response = await requestJuplendJson<
|
|
331
323
|
JuplendVaultMetadata[] | { data?: JuplendVaultMetadata[] }
|
|
332
|
-
>(
|
|
333
|
-
|
|
334
|
-
|
|
324
|
+
>(
|
|
325
|
+
withBaseUrl(JUP_LITE_API_BASE_URL, LEND_VAULTS_PATH),
|
|
326
|
+
{
|
|
327
|
+
headers: buildApiHeaders(apiKey),
|
|
328
|
+
},
|
|
329
|
+
fetchFn,
|
|
330
|
+
timeoutMs,
|
|
331
|
+
);
|
|
335
332
|
const rawVaults = Array.isArray(response) ? response : response.data;
|
|
336
333
|
const vaults = new Map<string, JuplendVaultMetadata>();
|
|
337
334
|
|
|
@@ -348,17 +345,21 @@ async function loadVaultMetadataFromLiteApi(
|
|
|
348
345
|
async function loadTokenSearchMap(
|
|
349
346
|
mintAddresses: string[],
|
|
350
347
|
apiKey?: string,
|
|
348
|
+
fetchFn?: FetchLike,
|
|
349
|
+
timeoutMs = DEFAULT_HTTP_TIMEOUT_MS,
|
|
351
350
|
): Promise<Map<string, JuplendTokenMetadata>> {
|
|
352
351
|
if (mintAddresses.length === 0) {
|
|
353
352
|
return new Map();
|
|
354
353
|
}
|
|
355
354
|
|
|
356
355
|
const query = encodeURIComponent(mintAddresses.join(","));
|
|
357
|
-
const response = await
|
|
356
|
+
const response = await requestJuplendJson<JuplendTokenSearchEntry[]>(
|
|
358
357
|
`${withBaseUrl(JUP_API_BASE_URL, TOKENS_SEARCH_PATH)}?query=${query}`,
|
|
359
358
|
{
|
|
360
359
|
headers: buildApiHeaders(apiKey),
|
|
361
360
|
},
|
|
361
|
+
fetchFn,
|
|
362
|
+
timeoutMs,
|
|
362
363
|
);
|
|
363
364
|
|
|
364
365
|
const tokens = new Map<string, JuplendTokenMetadata>();
|
|
@@ -385,17 +386,23 @@ async function loadTokenSearchMap(
|
|
|
385
386
|
async function loadPriceMap(
|
|
386
387
|
mintAddresses: string[],
|
|
387
388
|
apiKey?: string,
|
|
389
|
+
fetchFn?: FetchLike,
|
|
390
|
+
timeoutMs = DEFAULT_HTTP_TIMEOUT_MS,
|
|
388
391
|
): Promise<Map<string, JuplendPriceApiEntry>> {
|
|
389
392
|
if (mintAddresses.length === 0) {
|
|
390
393
|
return new Map();
|
|
391
394
|
}
|
|
392
395
|
|
|
393
396
|
const ids = encodeURIComponent(mintAddresses.join(","));
|
|
394
|
-
const response = await
|
|
397
|
+
const response = await requestJuplendJson<
|
|
398
|
+
Record<string, JuplendPriceApiEntry>
|
|
399
|
+
>(
|
|
395
400
|
`${withBaseUrl(JUP_API_BASE_URL, PRICE_V3_PATH)}?ids=${ids}`,
|
|
396
401
|
{
|
|
397
402
|
headers: buildApiHeaders(apiKey),
|
|
398
403
|
},
|
|
404
|
+
fetchFn,
|
|
405
|
+
timeoutMs,
|
|
399
406
|
);
|
|
400
407
|
|
|
401
408
|
return new Map(Object.entries(response ?? {}));
|
|
@@ -436,6 +443,8 @@ function mergeTokenMetadata(
|
|
|
436
443
|
async function enrichVaultsWithJupApi(input: {
|
|
437
444
|
apiKey?: string;
|
|
438
445
|
baseVaults: Map<string, JuplendVaultMetadata>;
|
|
446
|
+
fetchFn?: FetchLike;
|
|
447
|
+
timeoutMs: number;
|
|
439
448
|
}): Promise<Map<string, JuplendVaultMetadata>> {
|
|
440
449
|
const mintAddresses = new Set<string>();
|
|
441
450
|
for (const vault of input.baseVaults.values()) {
|
|
@@ -450,8 +459,18 @@ async function enrichVaultsWithJupApi(input: {
|
|
|
450
459
|
}
|
|
451
460
|
|
|
452
461
|
const [tokenMap, priceMap] = await Promise.all([
|
|
453
|
-
loadTokenSearchMap(
|
|
454
|
-
|
|
462
|
+
loadTokenSearchMap(
|
|
463
|
+
[...mintAddresses],
|
|
464
|
+
input.apiKey,
|
|
465
|
+
input.fetchFn,
|
|
466
|
+
input.timeoutMs,
|
|
467
|
+
),
|
|
468
|
+
loadPriceMap(
|
|
469
|
+
[...mintAddresses],
|
|
470
|
+
input.apiKey,
|
|
471
|
+
input.fetchFn,
|
|
472
|
+
input.timeoutMs,
|
|
473
|
+
),
|
|
455
474
|
]);
|
|
456
475
|
|
|
457
476
|
const enriched = new Map<string, JuplendVaultMetadata>();
|
|
@@ -480,6 +499,8 @@ async function enrichVaultsWithJupApi(input: {
|
|
|
480
499
|
async function loadVaults(
|
|
481
500
|
now: number,
|
|
482
501
|
apiKey?: string,
|
|
502
|
+
fetchFn?: FetchLike,
|
|
503
|
+
timeoutMs = DEFAULT_HTTP_TIMEOUT_MS,
|
|
483
504
|
): Promise<Map<string, JuplendVaultMetadata>> {
|
|
484
505
|
const cacheKey = getEnrichmentCacheKey(apiKey);
|
|
485
506
|
const cached = enrichmentCache.get(cacheKey);
|
|
@@ -492,7 +513,11 @@ async function loadVaults(
|
|
|
492
513
|
const inflight = enrichmentCachePromise.get(cacheKey);
|
|
493
514
|
if (!inflight) {
|
|
494
515
|
const nextPromise = (async () => {
|
|
495
|
-
const baseVaults = await loadVaultMetadataFromLiteApi(
|
|
516
|
+
const baseVaults = await loadVaultMetadataFromLiteApi(
|
|
517
|
+
apiKey,
|
|
518
|
+
fetchFn,
|
|
519
|
+
timeoutMs,
|
|
520
|
+
);
|
|
496
521
|
if (!apiKey) {
|
|
497
522
|
enrichmentCache.set(cacheKey, {
|
|
498
523
|
loadedAt: now,
|
|
@@ -506,6 +531,8 @@ async function loadVaults(
|
|
|
506
531
|
const enrichedVaults = await enrichVaultsWithJupApi({
|
|
507
532
|
apiKey,
|
|
508
533
|
baseVaults,
|
|
534
|
+
fetchFn,
|
|
535
|
+
timeoutMs,
|
|
509
536
|
});
|
|
510
537
|
enrichmentCache.set(cacheKey, {
|
|
511
538
|
loadedAt: now,
|
|
@@ -545,9 +572,11 @@ async function mapAccount(
|
|
|
545
572
|
receivedAt: number,
|
|
546
573
|
rpcUrl: string | undefined,
|
|
547
574
|
jupApiKey: string | undefined,
|
|
575
|
+
fetchFn: FetchLike | undefined,
|
|
576
|
+
timeoutMs: number,
|
|
548
577
|
): Promise<JuplendMappedAccount> {
|
|
549
578
|
const [vaults, positionResult] = await Promise.all([
|
|
550
|
-
loadVaults(receivedAt, jupApiKey),
|
|
579
|
+
loadVaults(receivedAt, jupApiKey, fetchFn, timeoutMs),
|
|
551
580
|
readJuplendPositions({
|
|
552
581
|
walletAddress: accountOptions.walletAddress,
|
|
553
582
|
vaultId: accountOptions.vaultId,
|
|
@@ -666,6 +695,11 @@ export class JuplendPrivateAdapter implements PrivateUserDataAdapter {
|
|
|
666
695
|
constructor(
|
|
667
696
|
private readonly rpcUrl?: string,
|
|
668
697
|
private readonly jupApiKey?: string,
|
|
698
|
+
private readonly options: {
|
|
699
|
+
readonly fetchFn?: FetchLike;
|
|
700
|
+
readonly httpTimeoutMs?: number;
|
|
701
|
+
readonly pollIntervalMs?: number;
|
|
702
|
+
} = {},
|
|
669
703
|
) {}
|
|
670
704
|
|
|
671
705
|
async bootstrapAccount(
|
|
@@ -679,6 +713,8 @@ export class JuplendPrivateAdapter implements PrivateUserDataAdapter {
|
|
|
679
713
|
receivedAt,
|
|
680
714
|
this.rpcUrl,
|
|
681
715
|
getJupApiKey(this.jupApiKey),
|
|
716
|
+
this.options.fetchFn,
|
|
717
|
+
this.options.httpTimeoutMs ?? DEFAULT_HTTP_TIMEOUT_MS,
|
|
682
718
|
);
|
|
683
719
|
|
|
684
720
|
return {
|
|
@@ -697,28 +733,21 @@ export class JuplendPrivateAdapter implements PrivateUserDataAdapter {
|
|
|
697
733
|
_credentials: AccountCredentials,
|
|
698
734
|
_request: CreateOrderRequest,
|
|
699
735
|
): Promise<RawOrderUpdate> {
|
|
700
|
-
throw new
|
|
701
|
-
"VENUE_NOT_SUPPORTED",
|
|
702
|
-
"Juplend is read-only and does not support createOrder",
|
|
703
|
-
);
|
|
736
|
+
throw new Error("Juplend is read-only and does not support createOrder");
|
|
704
737
|
}
|
|
705
738
|
|
|
706
739
|
cancelOrder(
|
|
707
740
|
_credentials: AccountCredentials,
|
|
708
741
|
_request: CancelOrderRequest,
|
|
709
742
|
): Promise<RawOrderUpdate> {
|
|
710
|
-
throw new
|
|
711
|
-
"VENUE_NOT_SUPPORTED",
|
|
712
|
-
"Juplend is read-only and does not support cancelOrder",
|
|
713
|
-
);
|
|
743
|
+
throw new Error("Juplend is read-only and does not support cancelOrder");
|
|
714
744
|
}
|
|
715
745
|
|
|
716
746
|
cancelAllOrders(
|
|
717
747
|
_credentials: AccountCredentials,
|
|
718
748
|
_request: CancelAllOrdersRequest,
|
|
719
749
|
): Promise<RawOrderUpdate[]> {
|
|
720
|
-
throw new
|
|
721
|
-
"VENUE_NOT_SUPPORTED",
|
|
750
|
+
throw new Error(
|
|
722
751
|
"Juplend is read-only and does not support cancelAllOrders",
|
|
723
752
|
);
|
|
724
753
|
}
|
|
@@ -726,13 +755,13 @@ export class JuplendPrivateAdapter implements PrivateUserDataAdapter {
|
|
|
726
755
|
createPrivateStream(
|
|
727
756
|
credentials: AccountCredentials,
|
|
728
757
|
callbacks: PrivateStreamCallbacks,
|
|
729
|
-
|
|
758
|
+
_options: PrivateStreamOptions,
|
|
730
759
|
accountOptions?: Record<string, unknown>,
|
|
731
760
|
): StreamHandle {
|
|
732
761
|
let closed = false;
|
|
733
762
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
734
763
|
const pollIntervalMs =
|
|
735
|
-
options.
|
|
764
|
+
this.options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
736
765
|
|
|
737
766
|
const poll = async (): Promise<void> => {
|
|
738
767
|
try {
|
package/src/adapters/types.ts
CHANGED
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
VenueAccountCapabilities,
|
|
10
10
|
VenueMarketCapabilities,
|
|
11
11
|
VenueOrderCapabilities,
|
|
12
|
+
VenueServerTime,
|
|
12
13
|
} from "../types/index.ts";
|
|
13
14
|
|
|
14
15
|
export interface StreamHandle {
|
|
@@ -74,6 +75,7 @@ export interface MarketAdapter {
|
|
|
74
75
|
readonly venue: Venue;
|
|
75
76
|
readonly marketCapabilities: VenueMarketCapabilities;
|
|
76
77
|
loadMarkets(): Promise<MarketDefinition[]>;
|
|
78
|
+
fetchServerTime?(): Promise<VenueServerTime>;
|
|
77
79
|
createL1BookStream(
|
|
78
80
|
market: MarketDefinition,
|
|
79
81
|
callbacks: L1BookStreamCallbacks,
|
|
@@ -174,6 +176,16 @@ export interface RawOrderUpdate {
|
|
|
174
176
|
receivedAt: number;
|
|
175
177
|
}
|
|
176
178
|
|
|
179
|
+
export interface RawOpenOrdersSnapshot {
|
|
180
|
+
orders: RawOrderUpdate[];
|
|
181
|
+
snapshotReceivedAt: number;
|
|
182
|
+
snapshotExchangeTs?: number;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export type FetchOrderRequest =
|
|
186
|
+
| { symbol: string; orderId: string; clientOrderId?: string }
|
|
187
|
+
| { symbol: string; clientOrderId: string; orderId?: string };
|
|
188
|
+
|
|
177
189
|
export interface CreateOrderRequest {
|
|
178
190
|
symbol: string;
|
|
179
191
|
side: OrderSide;
|
|
@@ -210,7 +222,6 @@ export interface PrivateStreamOptions {
|
|
|
210
222
|
reconnectDelayMs: number;
|
|
211
223
|
reconnectMaxDelayMs: number;
|
|
212
224
|
listenKeyKeepAliveMs: number;
|
|
213
|
-
juplendPollIntervalMs?: number;
|
|
214
225
|
now?: () => number;
|
|
215
226
|
}
|
|
216
227
|
|
|
@@ -228,10 +239,23 @@ export interface PrivateUserDataAdapter {
|
|
|
228
239
|
credentials: AccountCredentials,
|
|
229
240
|
accountOptions?: Record<string, unknown>,
|
|
230
241
|
): Promise<RawAccountUpdate>;
|
|
242
|
+
reconcileAccount?(
|
|
243
|
+
credentials: AccountCredentials,
|
|
244
|
+
accountOptions?: Record<string, unknown>,
|
|
245
|
+
): Promise<RawAccountBootstrap>;
|
|
231
246
|
bootstrapOpenOrders(
|
|
232
247
|
credentials: AccountCredentials,
|
|
233
248
|
accountOptions?: Record<string, unknown>,
|
|
234
249
|
): Promise<RawOrderUpdate[]>;
|
|
250
|
+
fetchOpenOrders?(
|
|
251
|
+
credentials: AccountCredentials,
|
|
252
|
+
accountOptions?: Record<string, unknown>,
|
|
253
|
+
): Promise<RawOpenOrdersSnapshot>;
|
|
254
|
+
fetchOrder?(
|
|
255
|
+
credentials: AccountCredentials,
|
|
256
|
+
request: FetchOrderRequest,
|
|
257
|
+
accountOptions?: Record<string, unknown>,
|
|
258
|
+
): Promise<RawOrderUpdate | undefined>;
|
|
235
259
|
createOrder(
|
|
236
260
|
credentials: AccountCredentials,
|
|
237
261
|
request: CreateOrderRequest,
|
package/src/client/context.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
RawAccountBootstrap,
|
|
3
3
|
RawAccountUpdate,
|
|
4
|
+
RawOpenOrdersSnapshot,
|
|
4
5
|
RawOrderUpdate,
|
|
5
6
|
} from "../adapters/types.ts";
|
|
6
7
|
import type {
|
|
@@ -10,9 +11,11 @@ import type {
|
|
|
10
11
|
CancelOrderInput,
|
|
11
12
|
CreateOrderInput,
|
|
12
13
|
HealthEvent,
|
|
14
|
+
OrderSnapshot,
|
|
13
15
|
PrivateRuntimeReason,
|
|
14
16
|
PrivateRuntimeStatus,
|
|
15
17
|
Venue,
|
|
18
|
+
VenueOrderCapabilities,
|
|
16
19
|
} from "../types/index.ts";
|
|
17
20
|
|
|
18
21
|
export interface RegisteredAccountRecord {
|
|
@@ -26,6 +29,7 @@ export interface ClientContext {
|
|
|
26
29
|
now(): number;
|
|
27
30
|
assertStarted(): void;
|
|
28
31
|
getRegisteredAccount(accountId: string): RegisteredAccountRecord;
|
|
32
|
+
getPrivateOrderCapabilities(venue: Venue): VenueOrderCapabilities | undefined;
|
|
29
33
|
ensurePrivateCredentials(accountId: string): void;
|
|
30
34
|
subscribePrivateAccountFeed(accountId: string): Promise<void>;
|
|
31
35
|
unsubscribePrivateAccountFeed(accountId: string): void;
|
|
@@ -75,7 +79,13 @@ export interface PrivateAccountDataConsumer {
|
|
|
75
79
|
accountId: string,
|
|
76
80
|
venue: Venue,
|
|
77
81
|
update: RawAccountUpdate,
|
|
78
|
-
options?: { preserveStatus?: boolean },
|
|
82
|
+
options?: { preserveStatus?: boolean; requestStartedAt?: number },
|
|
83
|
+
): void;
|
|
84
|
+
onPrivateAccountReconcile(
|
|
85
|
+
accountId: string,
|
|
86
|
+
venue: Venue,
|
|
87
|
+
snapshot: RawAccountBootstrap,
|
|
88
|
+
options: { requestStartedAt: number; preserveStatus?: boolean },
|
|
79
89
|
): void;
|
|
80
90
|
onPrivateAccountStreamState(
|
|
81
91
|
accountId: string,
|
|
@@ -89,13 +99,22 @@ export interface PrivateOrderDataConsumer {
|
|
|
89
99
|
onPrivateOrderBootstrap(
|
|
90
100
|
accountId: string,
|
|
91
101
|
venue: Venue,
|
|
92
|
-
|
|
93
|
-
|
|
102
|
+
snapshot: RawOpenOrdersSnapshot,
|
|
103
|
+
options: { requestStartedAt: number; preserveStatus?: boolean },
|
|
104
|
+
): OrderSnapshot[];
|
|
105
|
+
onPrivateOrderReconcile(
|
|
106
|
+
accountId: string,
|
|
107
|
+
venue: Venue,
|
|
108
|
+
snapshot: RawOpenOrdersSnapshot,
|
|
109
|
+
options: { requestStartedAt: number; preserveStatus?: boolean },
|
|
110
|
+
): OrderSnapshot[];
|
|
94
111
|
onPrivateOrderUpdate(
|
|
95
112
|
accountId: string,
|
|
96
113
|
venue: Venue,
|
|
97
114
|
update: RawOrderUpdate,
|
|
115
|
+
options?: { requestStartedAt?: number; preserveStatus?: boolean },
|
|
98
116
|
): void;
|
|
117
|
+
getPrivateOpenOrders(accountId: string): OrderSnapshot[];
|
|
99
118
|
onPrivateOrderStreamState(
|
|
100
119
|
accountId: string,
|
|
101
120
|
venue: Venue,
|
|
@@ -105,13 +124,11 @@ export interface PrivateOrderDataConsumer {
|
|
|
105
124
|
|
|
106
125
|
export function hasPrivateCredentials(
|
|
107
126
|
credentials?: AccountCredentials,
|
|
108
|
-
|
|
127
|
+
credentialsRequired = true,
|
|
109
128
|
): boolean {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
return Boolean(credentials?.apiKey && credentials.secret);
|
|
129
|
+
return credentialsRequired
|
|
130
|
+
? Boolean(credentials?.apiKey && credentials.secret)
|
|
131
|
+
: true;
|
|
115
132
|
}
|
|
116
133
|
|
|
117
134
|
export function mergeCredentials(
|