@acta-markets/ts-sdk 0.0.4-beta → 0.0.5-beta
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 -1
- package/dist/cjs/ws/client.js +94 -13
- package/dist/cjs/ws/client.test.js +113 -3
- package/dist/ws/client.d.ts +17 -13
- package/dist/ws/client.js +94 -13
- package/dist/ws/client.test.js +113 -3
- package/dist/ws/types.d.ts +58 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,11 +88,14 @@ client.ws!.on("positions", (positions) => {
|
|
|
88
88
|
console.log("first expiry", positions[0]?.expiry_ts);
|
|
89
89
|
});
|
|
90
90
|
|
|
91
|
-
client.ws!.getMarkets();
|
|
91
|
+
const marketsRequestId = client.ws!.getMarkets();
|
|
92
|
+
console.log("GetMarkets request_id:", marketsRequestId);
|
|
92
93
|
// positions require auth:
|
|
93
94
|
// client.ws!.getPositions();
|
|
94
95
|
```
|
|
95
96
|
|
|
97
|
+
Most WS read/query methods now return the generated `request_id` so UI code can correlate responses (`data.request_id`) without building custom request id plumbing.
|
|
98
|
+
|
|
96
99
|
### RFQ → accept quote → sign sponsored tx (wallet UX)
|
|
97
100
|
|
|
98
101
|
```ts
|
package/dist/cjs/ws/client.js
CHANGED
|
@@ -73,6 +73,21 @@ function getCloseInfo(ev) {
|
|
|
73
73
|
reason: typeof rec.reason === "string" ? rec.reason : undefined,
|
|
74
74
|
};
|
|
75
75
|
}
|
|
76
|
+
function generateRequestId() {
|
|
77
|
+
const cryptoApi = globalThis.crypto;
|
|
78
|
+
if (cryptoApi?.randomUUID) {
|
|
79
|
+
return cryptoApi.randomUUID();
|
|
80
|
+
}
|
|
81
|
+
if (cryptoApi?.getRandomValues) {
|
|
82
|
+
const bytes = cryptoApi.getRandomValues(new Uint8Array(16));
|
|
83
|
+
// RFC4122 v4 bits: version=0100, variant=10xx.
|
|
84
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
85
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
86
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
87
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
88
|
+
}
|
|
89
|
+
return `req-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
90
|
+
}
|
|
76
91
|
class ActaWsClient extends TypedEventEmitter {
|
|
77
92
|
ws = null;
|
|
78
93
|
options;
|
|
@@ -82,6 +97,7 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
82
97
|
pendingResumeSessionId = null;
|
|
83
98
|
connectionState = "disconnected";
|
|
84
99
|
sessionId = null;
|
|
100
|
+
lastAuthSessionId = null;
|
|
85
101
|
helloSent = false;
|
|
86
102
|
welcomeReceived = false;
|
|
87
103
|
pendingMessages = [];
|
|
@@ -124,6 +140,7 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
124
140
|
this.authProvider = null;
|
|
125
141
|
this.authRequested = false;
|
|
126
142
|
this.pendingResumeSessionId = null;
|
|
143
|
+
this.lastAuthSessionId = null;
|
|
127
144
|
this.shouldReconnect = this.options.autoReconnect;
|
|
128
145
|
this.doConnect();
|
|
129
146
|
}
|
|
@@ -223,47 +240,89 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
223
240
|
}
|
|
224
241
|
getPositions() {
|
|
225
242
|
this.ensureAuthenticated();
|
|
226
|
-
this.
|
|
243
|
+
const requestId = this.nextRequestId();
|
|
244
|
+
this.send({
|
|
245
|
+
type: "GetPositions",
|
|
246
|
+
data: { request_id: requestId },
|
|
247
|
+
});
|
|
248
|
+
return requestId;
|
|
227
249
|
}
|
|
228
250
|
getMarkets() {
|
|
229
|
-
this.
|
|
251
|
+
const requestId = this.nextRequestId();
|
|
252
|
+
this.send({
|
|
253
|
+
type: "GetMarkets",
|
|
254
|
+
data: { request_id: requestId },
|
|
255
|
+
});
|
|
256
|
+
return requestId;
|
|
230
257
|
}
|
|
231
258
|
getMarketDescriptors(args) {
|
|
259
|
+
const requestId = this.nextRequestId();
|
|
232
260
|
const data = {
|
|
261
|
+
request_id: requestId,
|
|
233
262
|
active_only: args?.active_only ?? true,
|
|
234
263
|
};
|
|
235
264
|
this.send({ type: "GetMarketDescriptors", data });
|
|
265
|
+
return requestId;
|
|
236
266
|
}
|
|
237
267
|
getExpiries(args) {
|
|
268
|
+
const requestId = this.nextRequestId();
|
|
238
269
|
this.send({
|
|
239
270
|
type: "GetExpiries",
|
|
240
271
|
data: {
|
|
272
|
+
request_id: requestId,
|
|
241
273
|
underlying_mint: args?.underlying_mint,
|
|
242
274
|
quote_mint: args?.quote_mint,
|
|
243
275
|
is_put: args?.is_put ?? null,
|
|
244
276
|
},
|
|
245
277
|
});
|
|
278
|
+
return requestId;
|
|
246
279
|
}
|
|
247
280
|
getTokens(args) {
|
|
281
|
+
const requestId = this.nextRequestId();
|
|
248
282
|
const data = {
|
|
283
|
+
request_id: requestId,
|
|
249
284
|
active_only: args?.active_only ?? true,
|
|
250
285
|
};
|
|
251
286
|
this.send({ type: "GetTokens", data });
|
|
287
|
+
return requestId;
|
|
252
288
|
}
|
|
253
289
|
getMyActiveRfqs() {
|
|
254
290
|
this.ensureAuthenticated();
|
|
255
|
-
this.
|
|
291
|
+
const requestId = this.nextRequestId();
|
|
292
|
+
this.send({
|
|
293
|
+
type: "GetMyActiveRfqs",
|
|
294
|
+
data: { request_id: requestId },
|
|
295
|
+
});
|
|
296
|
+
return requestId;
|
|
256
297
|
}
|
|
257
298
|
getActiveRfqs() {
|
|
258
|
-
this.
|
|
299
|
+
const requestId = this.nextRequestId();
|
|
300
|
+
this.send({
|
|
301
|
+
type: "GetActiveRfqs",
|
|
302
|
+
data: { request_id: requestId },
|
|
303
|
+
});
|
|
304
|
+
return requestId;
|
|
305
|
+
}
|
|
306
|
+
logout() {
|
|
307
|
+
this.send({ type: "Logout" });
|
|
259
308
|
}
|
|
260
309
|
getOrderStatus(orderIdHex) {
|
|
261
310
|
this.ensureAuthenticated();
|
|
262
|
-
this.
|
|
311
|
+
const requestId = this.nextRequestId();
|
|
312
|
+
this.send({
|
|
313
|
+
type: "GetOrderStatus",
|
|
314
|
+
data: { request_id: requestId, order_id: orderIdHex },
|
|
315
|
+
});
|
|
316
|
+
return requestId;
|
|
263
317
|
}
|
|
264
318
|
cancelRfq(rfqId) {
|
|
265
319
|
this.ensureAuthenticated();
|
|
266
|
-
this.
|
|
320
|
+
const requestId = this.nextRequestId();
|
|
321
|
+
this.send({
|
|
322
|
+
type: "CancelRfq",
|
|
323
|
+
data: { rfq_id: rfqId, request_id: requestId },
|
|
324
|
+
});
|
|
325
|
+
return requestId;
|
|
267
326
|
}
|
|
268
327
|
submitQuote(quote) {
|
|
269
328
|
this.ensureAuthenticated();
|
|
@@ -293,7 +352,12 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
293
352
|
}
|
|
294
353
|
cancelQuote(rfqId) {
|
|
295
354
|
this.ensureAuthenticated();
|
|
296
|
-
this.
|
|
355
|
+
const requestId = this.nextRequestId();
|
|
356
|
+
this.send({
|
|
357
|
+
type: "CancelQuote",
|
|
358
|
+
data: { rfq_id: rfqId, request_id: requestId },
|
|
359
|
+
});
|
|
360
|
+
return requestId;
|
|
297
361
|
}
|
|
298
362
|
subscribe(channels, markets) {
|
|
299
363
|
this.ensureAuthenticated();
|
|
@@ -418,6 +482,14 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
418
482
|
case "AuthError":
|
|
419
483
|
this.handleAuthError(message.data.reason, message.data.message);
|
|
420
484
|
break;
|
|
485
|
+
case "LogoutSuccess":
|
|
486
|
+
this.sessionId = null;
|
|
487
|
+
this.pendingResumeSessionId = null;
|
|
488
|
+
this.lastAuthSessionId = null;
|
|
489
|
+
this.startAuthSent = false;
|
|
490
|
+
this.setConnectionState("connecting");
|
|
491
|
+
this.emit("logoutSuccess");
|
|
492
|
+
break;
|
|
421
493
|
case "Snapshot":
|
|
422
494
|
this.handleSnapshot(message.data);
|
|
423
495
|
break;
|
|
@@ -587,10 +659,12 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
587
659
|
}
|
|
588
660
|
/** Taker-only: request current indicative prices for a market + position_type. */
|
|
589
661
|
getIndicativePrices(req) {
|
|
662
|
+
const requestId = this.nextRequestId();
|
|
590
663
|
this.send({
|
|
591
664
|
type: "GetIndicativePrices",
|
|
592
|
-
data: req,
|
|
665
|
+
data: { ...req, request_id: requestId },
|
|
593
666
|
});
|
|
667
|
+
return requestId;
|
|
594
668
|
}
|
|
595
669
|
/** Maker-only: respond to an indicative request (unsigned, non-binding). */
|
|
596
670
|
sendIndicativePricesResponse(resp) {
|
|
@@ -619,8 +693,9 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
619
693
|
}
|
|
620
694
|
}
|
|
621
695
|
async beginAuthHandshake() {
|
|
622
|
-
|
|
623
|
-
|
|
696
|
+
const resumeSessionId = this.pendingResumeSessionId ?? this.lastAuthSessionId;
|
|
697
|
+
if (resumeSessionId) {
|
|
698
|
+
this.sendResumeAuth(resumeSessionId);
|
|
624
699
|
return;
|
|
625
700
|
}
|
|
626
701
|
await this.sendStartAuth();
|
|
@@ -657,6 +732,9 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
657
732
|
handleAuthSuccess(sessionId, expiresAt) {
|
|
658
733
|
this.sessionId = sessionId;
|
|
659
734
|
this.pendingResumeSessionId = null;
|
|
735
|
+
if (expiresAt !== null) {
|
|
736
|
+
this.lastAuthSessionId = sessionId;
|
|
737
|
+
}
|
|
660
738
|
this.setConnectionState("authenticated");
|
|
661
739
|
this.emit("authenticated", sessionId, expiresAt);
|
|
662
740
|
if (this.subscribedChannels.size > 0) {
|
|
@@ -668,12 +746,13 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
668
746
|
}
|
|
669
747
|
}
|
|
670
748
|
handleAuthError(reason, message) {
|
|
671
|
-
this.emit("authError", reason);
|
|
749
|
+
this.emit("authError", reason, message);
|
|
672
750
|
if (reason === "session_expired" &&
|
|
673
751
|
this.authRequested &&
|
|
674
752
|
this.authProvider &&
|
|
675
|
-
this.pendingResumeSessionId) {
|
|
753
|
+
(this.pendingResumeSessionId || this.lastAuthSessionId)) {
|
|
676
754
|
this.pendingResumeSessionId = null;
|
|
755
|
+
this.lastAuthSessionId = null;
|
|
677
756
|
this.startAuthSent = false;
|
|
678
757
|
void this.sendStartAuth().catch((err) => {
|
|
679
758
|
this.emit("error", err);
|
|
@@ -789,6 +868,9 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
789
868
|
this.log("Cannot send, WebSocket not open");
|
|
790
869
|
}
|
|
791
870
|
}
|
|
871
|
+
nextRequestId() {
|
|
872
|
+
return generateRequestId();
|
|
873
|
+
}
|
|
792
874
|
ensureAuthenticated() {
|
|
793
875
|
if (this.connectionState !== "authenticated") {
|
|
794
876
|
throw new Error("Client is not authenticated");
|
|
@@ -883,7 +965,6 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
883
965
|
this.helloSent = false;
|
|
884
966
|
this.welcomeReceived = false;
|
|
885
967
|
this.startAuthSent = false;
|
|
886
|
-
this.pendingResumeSessionId = null;
|
|
887
968
|
this.pendingMessages = [];
|
|
888
969
|
this.setConnectionState("disconnected");
|
|
889
970
|
}
|
|
@@ -169,13 +169,123 @@ describe("ActaWsClient", () => {
|
|
|
169
169
|
});
|
|
170
170
|
it("emits authError reason on AuthError", () => {
|
|
171
171
|
const { client } = makeHarness();
|
|
172
|
-
const
|
|
173
|
-
client.on("authError", (reason) =>
|
|
172
|
+
const errors = [];
|
|
173
|
+
client.on("authError", (reason, message) => errors.push({ reason, message }));
|
|
174
174
|
client.handleMessage({
|
|
175
175
|
type: "AuthError",
|
|
176
176
|
data: { reason: "invalid_signature", message: "bad signature bytes" },
|
|
177
177
|
});
|
|
178
|
-
expect(
|
|
178
|
+
expect(errors).toEqual([
|
|
179
|
+
{ reason: "invalid_signature", message: "bad signature bytes" },
|
|
180
|
+
]);
|
|
181
|
+
});
|
|
182
|
+
it("uses last auth session for resume-first reconnect", async () => {
|
|
183
|
+
const { client, socket } = makeHarness();
|
|
184
|
+
const auth = makeAuthProvider("WalletPubkey", "WalletSignature");
|
|
185
|
+
client.connectAndAuthenticate(auth);
|
|
186
|
+
let ws = socket();
|
|
187
|
+
ws.triggerOpen();
|
|
188
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
189
|
+
await flushMicrotasks();
|
|
190
|
+
ws.triggerMessage({
|
|
191
|
+
type: "AuthSuccess",
|
|
192
|
+
data: { session_id: "persisted-session", expires_at: 1_710_086_400 },
|
|
193
|
+
});
|
|
194
|
+
ws.triggerClose(1006, "network_drop");
|
|
195
|
+
client.connectAndAuthenticate(auth);
|
|
196
|
+
ws = socket();
|
|
197
|
+
ws.triggerOpen();
|
|
198
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
199
|
+
await flushMicrotasks();
|
|
200
|
+
const sentTypes = ws.sent.map((payload) => parseClientMessage(payload).type);
|
|
201
|
+
expect(sentTypes).toEqual(["Hello", "ResumeAuth"]);
|
|
202
|
+
const resumeAuth = parseClientMessage(ws.sent[1]);
|
|
203
|
+
expect(resumeAuth.type).toBe("ResumeAuth");
|
|
204
|
+
if (resumeAuth.type === "ResumeAuth") {
|
|
205
|
+
expect(resumeAuth.data.session_id).toBe("persisted-session");
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
it("sends Logout and emits logoutSuccess", () => {
|
|
209
|
+
const { client, socket } = makeHarness();
|
|
210
|
+
const events = [];
|
|
211
|
+
client.on("logoutSuccess", () => events.push("logoutSuccess"));
|
|
212
|
+
client.connectAnonymous();
|
|
213
|
+
const ws = socket();
|
|
214
|
+
ws.triggerOpen();
|
|
215
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
216
|
+
client.logout();
|
|
217
|
+
const sent = parseClientMessage(ws.sent[1]);
|
|
218
|
+
expect(sent.type).toBe("Logout");
|
|
219
|
+
ws.triggerMessage({ type: "LogoutSuccess", data: {} });
|
|
220
|
+
expect(events).toEqual(["logoutSuccess"]);
|
|
221
|
+
});
|
|
222
|
+
it("adds request_id to public request messages", () => {
|
|
223
|
+
const { client, socket } = makeHarness();
|
|
224
|
+
client.connectAnonymous();
|
|
225
|
+
const ws = socket();
|
|
226
|
+
ws.triggerOpen();
|
|
227
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
228
|
+
const getMarketsId = client.getMarkets();
|
|
229
|
+
const getMarketDescriptorsId = client.getMarketDescriptors();
|
|
230
|
+
const getTokensId = client.getTokens();
|
|
231
|
+
const getExpiriesId = client.getExpiries();
|
|
232
|
+
const getActiveRfqsId = client.getActiveRfqs();
|
|
233
|
+
const getIndicativePricesId = client.getIndicativePrices({
|
|
234
|
+
market: "market-1",
|
|
235
|
+
position_type: "covered_call",
|
|
236
|
+
});
|
|
237
|
+
const sent = ws.sent.slice(1).map(parseClientMessage);
|
|
238
|
+
const withRequestId = sent.filter((msg) => msg.type !== "Logout");
|
|
239
|
+
for (const msg of withRequestId) {
|
|
240
|
+
if ("data" in msg) {
|
|
241
|
+
const data = msg.data;
|
|
242
|
+
expect(typeof data.request_id).toBe("string");
|
|
243
|
+
expect(data.request_id.length).toBeGreaterThan(0);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
expect(sent[0].data.request_id).toBe(getMarketsId);
|
|
247
|
+
expect(sent[1].data.request_id).toBe(getMarketDescriptorsId);
|
|
248
|
+
expect(sent[2].data.request_id).toBe(getTokensId);
|
|
249
|
+
expect(sent[3].data.request_id).toBe(getExpiriesId);
|
|
250
|
+
expect(sent[4].data.request_id).toBe(getActiveRfqsId);
|
|
251
|
+
expect(sent[5].data.request_id).toBe(getIndicativePricesId);
|
|
252
|
+
});
|
|
253
|
+
it("adds request_id to authenticated request messages", () => {
|
|
254
|
+
const { client, socket } = makeHarness();
|
|
255
|
+
client.connectAnonymous();
|
|
256
|
+
const ws = socket();
|
|
257
|
+
ws.triggerOpen();
|
|
258
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
259
|
+
client.handleMessage({
|
|
260
|
+
type: "AuthSuccess",
|
|
261
|
+
data: { session_id: "session-id", expires_at: 1_710_086_400 },
|
|
262
|
+
});
|
|
263
|
+
const getPositionsId = client.getPositions();
|
|
264
|
+
const getMyActiveRfqsId = client.getMyActiveRfqs();
|
|
265
|
+
const getOrderStatusId = client.getOrderStatus("11".repeat(32));
|
|
266
|
+
const cancelRfqId = client.cancelRfq("rfq-1");
|
|
267
|
+
const cancelQuoteId = client.cancelQuote("rfq-2");
|
|
268
|
+
const sent = ws.sent.slice(1).map(parseClientMessage);
|
|
269
|
+
const authRequests = sent.filter((msg) => [
|
|
270
|
+
"GetPositions",
|
|
271
|
+
"GetMyActiveRfqs",
|
|
272
|
+
"GetOrderStatus",
|
|
273
|
+
"CancelRfq",
|
|
274
|
+
"CancelQuote",
|
|
275
|
+
].includes(msg.type));
|
|
276
|
+
expect(authRequests).toHaveLength(5);
|
|
277
|
+
for (const msg of authRequests) {
|
|
278
|
+
if ("data" in msg) {
|
|
279
|
+
const data = msg.data;
|
|
280
|
+
expect(typeof data.request_id).toBe("string");
|
|
281
|
+
expect(data.request_id.length).toBeGreaterThan(0);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
expect(authRequests[0].data.request_id).toBe(getPositionsId);
|
|
285
|
+
expect(authRequests[1].data.request_id).toBe(getMyActiveRfqsId);
|
|
286
|
+
expect(authRequests[2].data.request_id).toBe(getOrderStatusId);
|
|
287
|
+
expect(authRequests[3].data.request_id).toBe(cancelRfqId);
|
|
288
|
+
expect(authRequests[4].data.request_id).toBe(cancelQuoteId);
|
|
179
289
|
});
|
|
180
290
|
it("drop_oldest policy keeps the latest queued messages", () => {
|
|
181
291
|
const { client, socket } = makeHarness({
|
package/dist/ws/client.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import type { AuthProvider } from "./auth";
|
|
3
3
|
import type { SignerLike } from "../chain/orders";
|
|
4
4
|
import type { Address } from "@solana/addresses";
|
|
5
|
-
import type { ActiveRfqInfo, ChainEventMessage, GlobalStats, MarketDescriptorInfo, MarketInfo, MyActiveRfqInfo, MyActiveRfqsMessage, OrderStatusMessage, PositionInfo, QuoteAcknowledgedMessage, QuoteBestStatusMessage, QuoteCancelledMessage, QuoteMessage, QuoteRefreshRequestedMessage, QuoteOutbidMessage, QuoteReceivedMessage, QuoteSelectedMessage, QuotesUpdateMessage, RfqBroadcastMessage, RfqClosedMessage, RfqCreatedMessage, RfqRequestMessage, RfqAvailableAgainMessage, QuoteExpiredMessage, QuoteFilledMessage, IndicativePricesMessage, IndicativePricesRequestMessage, IndicativePricesResponseMessage, GetIndicativePricesMessage, ServerMessage, SnapshotMessage, StatsDelta, SubscriptionsMessage, TokenInfo, TradeInfo, UuidString, VersionMismatchMessage, WelcomeMessage, WsChannel } from "./types";
|
|
5
|
+
import type { ActiveRfqInfo, ChainEventMessage, GlobalStats, MarketDescriptorInfo, MarketInfo, MyActiveRfqInfo, MyActiveRfqsMessage, OrderStatusMessage, PositionInfo, QuoteAcknowledgedMessage, QuoteBestStatusMessage, QuoteCancelledMessage, QuoteMessage, QuoteRefreshRequestedMessage, QuoteOutbidMessage, QuoteReceivedMessage, QuoteSelectedMessage, QuotesUpdateMessage, RfqBroadcastMessage, RfqClosedMessage, RfqCreatedMessage, RfqRequestMessage, RfqAvailableAgainMessage, QuoteExpiredMessage, QuoteFilledMessage, IndicativePricesMessage, IndicativePricesRequestMessage, IndicativePricesResponseMessage, GetIndicativePricesMessage, RequestId, ServerMessage, SnapshotMessage, StatsDelta, SubscriptionsMessage, TokenInfo, TradeInfo, UuidString, VersionMismatchMessage, WelcomeMessage, WsChannel } from "./types";
|
|
6
6
|
export type ConnectionState = "disconnected" | "connecting" | "authenticating" | "authenticated" | "error";
|
|
7
7
|
export type ClientRole = "taker" | "maker";
|
|
8
8
|
export type PendingMessagesOverflowPolicy = "drop_oldest" | "drop_newest" | "throw";
|
|
@@ -57,7 +57,8 @@ export type ActaWsClientEvents = {
|
|
|
57
57
|
welcome: (msg: WelcomeMessage) => void;
|
|
58
58
|
versionMismatch: (msg: VersionMismatchMessage) => void;
|
|
59
59
|
authenticated: (sessionId: string, expiresAt: number | null) => void;
|
|
60
|
-
authError: (reason: string) => void;
|
|
60
|
+
authError: (reason: string, message?: string) => void;
|
|
61
|
+
logoutSuccess: () => void;
|
|
61
62
|
disconnected: (code: number, reason: string) => void;
|
|
62
63
|
error: (error: Error) => void;
|
|
63
64
|
stateChange: (state: ConnectionState) => void;
|
|
@@ -151,6 +152,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
151
152
|
private pendingResumeSessionId;
|
|
152
153
|
private connectionState;
|
|
153
154
|
private sessionId;
|
|
155
|
+
private lastAuthSessionId;
|
|
154
156
|
private helloSent;
|
|
155
157
|
private welcomeReceived;
|
|
156
158
|
private pendingMessages;
|
|
@@ -195,23 +197,24 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
195
197
|
orderIdHex: string;
|
|
196
198
|
txBase64: string;
|
|
197
199
|
}): Promise<void>;
|
|
198
|
-
getPositions():
|
|
199
|
-
getMarkets():
|
|
200
|
+
getPositions(): RequestId;
|
|
201
|
+
getMarkets(): RequestId;
|
|
200
202
|
getMarketDescriptors(args?: {
|
|
201
203
|
active_only?: boolean;
|
|
202
|
-
}):
|
|
204
|
+
}): RequestId;
|
|
203
205
|
getExpiries(args?: {
|
|
204
206
|
underlying_mint?: Address<string>;
|
|
205
207
|
quote_mint?: Address<string>;
|
|
206
208
|
is_put?: boolean | null;
|
|
207
|
-
}):
|
|
209
|
+
}): RequestId;
|
|
208
210
|
getTokens(args?: {
|
|
209
211
|
active_only?: boolean;
|
|
210
|
-
}):
|
|
211
|
-
getMyActiveRfqs():
|
|
212
|
-
getActiveRfqs():
|
|
213
|
-
|
|
214
|
-
|
|
212
|
+
}): RequestId;
|
|
213
|
+
getMyActiveRfqs(): RequestId;
|
|
214
|
+
getActiveRfqs(): RequestId;
|
|
215
|
+
logout(): void;
|
|
216
|
+
getOrderStatus(orderIdHex: string): RequestId;
|
|
217
|
+
cancelRfq(rfqId: string): RequestId;
|
|
215
218
|
submitQuote(quote: QuoteMessage): void;
|
|
216
219
|
/**
|
|
217
220
|
* Convenience: sign 32-byte `orderId` and send `Quote`.
|
|
@@ -228,7 +231,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
228
231
|
orderId: Uint8Array;
|
|
229
232
|
makerSigner: SignerLike;
|
|
230
233
|
}): Promise<void>;
|
|
231
|
-
cancelQuote(rfqId: string):
|
|
234
|
+
cancelQuote(rfqId: string): RequestId;
|
|
232
235
|
subscribe(channels: WsChannel[], markets?: string[]): void;
|
|
233
236
|
unsubscribe(channels: WsChannel[]): void;
|
|
234
237
|
ping(): void;
|
|
@@ -236,7 +239,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
236
239
|
private doConnect;
|
|
237
240
|
private handleMessage;
|
|
238
241
|
/** Taker-only: request current indicative prices for a market + position_type. */
|
|
239
|
-
getIndicativePrices(req: GetIndicativePricesMessage):
|
|
242
|
+
getIndicativePrices(req: Omit<GetIndicativePricesMessage, "request_id">): RequestId;
|
|
240
243
|
/** Maker-only: respond to an indicative request (unsigned, non-binding). */
|
|
241
244
|
sendIndicativePricesResponse(resp: IndicativePricesResponseMessage): void;
|
|
242
245
|
private handleAuthRequest;
|
|
@@ -257,6 +260,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
257
260
|
private handlePositionUpdated;
|
|
258
261
|
private handleChainEvent;
|
|
259
262
|
private send;
|
|
263
|
+
private nextRequestId;
|
|
260
264
|
private ensureAuthenticated;
|
|
261
265
|
private setConnectionState;
|
|
262
266
|
private startPingInterval;
|
package/dist/ws/client.js
CHANGED
|
@@ -70,6 +70,21 @@ function getCloseInfo(ev) {
|
|
|
70
70
|
reason: typeof rec.reason === "string" ? rec.reason : undefined,
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
+
function generateRequestId() {
|
|
74
|
+
const cryptoApi = globalThis.crypto;
|
|
75
|
+
if (cryptoApi?.randomUUID) {
|
|
76
|
+
return cryptoApi.randomUUID();
|
|
77
|
+
}
|
|
78
|
+
if (cryptoApi?.getRandomValues) {
|
|
79
|
+
const bytes = cryptoApi.getRandomValues(new Uint8Array(16));
|
|
80
|
+
// RFC4122 v4 bits: version=0100, variant=10xx.
|
|
81
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
82
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
83
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
84
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
85
|
+
}
|
|
86
|
+
return `req-${Date.now()}-${Math.random().toString(16).slice(2, 10)}`;
|
|
87
|
+
}
|
|
73
88
|
export class ActaWsClient extends TypedEventEmitter {
|
|
74
89
|
ws = null;
|
|
75
90
|
options;
|
|
@@ -79,6 +94,7 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
79
94
|
pendingResumeSessionId = null;
|
|
80
95
|
connectionState = "disconnected";
|
|
81
96
|
sessionId = null;
|
|
97
|
+
lastAuthSessionId = null;
|
|
82
98
|
helloSent = false;
|
|
83
99
|
welcomeReceived = false;
|
|
84
100
|
pendingMessages = [];
|
|
@@ -121,6 +137,7 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
121
137
|
this.authProvider = null;
|
|
122
138
|
this.authRequested = false;
|
|
123
139
|
this.pendingResumeSessionId = null;
|
|
140
|
+
this.lastAuthSessionId = null;
|
|
124
141
|
this.shouldReconnect = this.options.autoReconnect;
|
|
125
142
|
this.doConnect();
|
|
126
143
|
}
|
|
@@ -220,47 +237,89 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
220
237
|
}
|
|
221
238
|
getPositions() {
|
|
222
239
|
this.ensureAuthenticated();
|
|
223
|
-
this.
|
|
240
|
+
const requestId = this.nextRequestId();
|
|
241
|
+
this.send({
|
|
242
|
+
type: "GetPositions",
|
|
243
|
+
data: { request_id: requestId },
|
|
244
|
+
});
|
|
245
|
+
return requestId;
|
|
224
246
|
}
|
|
225
247
|
getMarkets() {
|
|
226
|
-
this.
|
|
248
|
+
const requestId = this.nextRequestId();
|
|
249
|
+
this.send({
|
|
250
|
+
type: "GetMarkets",
|
|
251
|
+
data: { request_id: requestId },
|
|
252
|
+
});
|
|
253
|
+
return requestId;
|
|
227
254
|
}
|
|
228
255
|
getMarketDescriptors(args) {
|
|
256
|
+
const requestId = this.nextRequestId();
|
|
229
257
|
const data = {
|
|
258
|
+
request_id: requestId,
|
|
230
259
|
active_only: args?.active_only ?? true,
|
|
231
260
|
};
|
|
232
261
|
this.send({ type: "GetMarketDescriptors", data });
|
|
262
|
+
return requestId;
|
|
233
263
|
}
|
|
234
264
|
getExpiries(args) {
|
|
265
|
+
const requestId = this.nextRequestId();
|
|
235
266
|
this.send({
|
|
236
267
|
type: "GetExpiries",
|
|
237
268
|
data: {
|
|
269
|
+
request_id: requestId,
|
|
238
270
|
underlying_mint: args?.underlying_mint,
|
|
239
271
|
quote_mint: args?.quote_mint,
|
|
240
272
|
is_put: args?.is_put ?? null,
|
|
241
273
|
},
|
|
242
274
|
});
|
|
275
|
+
return requestId;
|
|
243
276
|
}
|
|
244
277
|
getTokens(args) {
|
|
278
|
+
const requestId = this.nextRequestId();
|
|
245
279
|
const data = {
|
|
280
|
+
request_id: requestId,
|
|
246
281
|
active_only: args?.active_only ?? true,
|
|
247
282
|
};
|
|
248
283
|
this.send({ type: "GetTokens", data });
|
|
284
|
+
return requestId;
|
|
249
285
|
}
|
|
250
286
|
getMyActiveRfqs() {
|
|
251
287
|
this.ensureAuthenticated();
|
|
252
|
-
this.
|
|
288
|
+
const requestId = this.nextRequestId();
|
|
289
|
+
this.send({
|
|
290
|
+
type: "GetMyActiveRfqs",
|
|
291
|
+
data: { request_id: requestId },
|
|
292
|
+
});
|
|
293
|
+
return requestId;
|
|
253
294
|
}
|
|
254
295
|
getActiveRfqs() {
|
|
255
|
-
this.
|
|
296
|
+
const requestId = this.nextRequestId();
|
|
297
|
+
this.send({
|
|
298
|
+
type: "GetActiveRfqs",
|
|
299
|
+
data: { request_id: requestId },
|
|
300
|
+
});
|
|
301
|
+
return requestId;
|
|
302
|
+
}
|
|
303
|
+
logout() {
|
|
304
|
+
this.send({ type: "Logout" });
|
|
256
305
|
}
|
|
257
306
|
getOrderStatus(orderIdHex) {
|
|
258
307
|
this.ensureAuthenticated();
|
|
259
|
-
this.
|
|
308
|
+
const requestId = this.nextRequestId();
|
|
309
|
+
this.send({
|
|
310
|
+
type: "GetOrderStatus",
|
|
311
|
+
data: { request_id: requestId, order_id: orderIdHex },
|
|
312
|
+
});
|
|
313
|
+
return requestId;
|
|
260
314
|
}
|
|
261
315
|
cancelRfq(rfqId) {
|
|
262
316
|
this.ensureAuthenticated();
|
|
263
|
-
this.
|
|
317
|
+
const requestId = this.nextRequestId();
|
|
318
|
+
this.send({
|
|
319
|
+
type: "CancelRfq",
|
|
320
|
+
data: { rfq_id: rfqId, request_id: requestId },
|
|
321
|
+
});
|
|
322
|
+
return requestId;
|
|
264
323
|
}
|
|
265
324
|
submitQuote(quote) {
|
|
266
325
|
this.ensureAuthenticated();
|
|
@@ -290,7 +349,12 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
290
349
|
}
|
|
291
350
|
cancelQuote(rfqId) {
|
|
292
351
|
this.ensureAuthenticated();
|
|
293
|
-
this.
|
|
352
|
+
const requestId = this.nextRequestId();
|
|
353
|
+
this.send({
|
|
354
|
+
type: "CancelQuote",
|
|
355
|
+
data: { rfq_id: rfqId, request_id: requestId },
|
|
356
|
+
});
|
|
357
|
+
return requestId;
|
|
294
358
|
}
|
|
295
359
|
subscribe(channels, markets) {
|
|
296
360
|
this.ensureAuthenticated();
|
|
@@ -415,6 +479,14 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
415
479
|
case "AuthError":
|
|
416
480
|
this.handleAuthError(message.data.reason, message.data.message);
|
|
417
481
|
break;
|
|
482
|
+
case "LogoutSuccess":
|
|
483
|
+
this.sessionId = null;
|
|
484
|
+
this.pendingResumeSessionId = null;
|
|
485
|
+
this.lastAuthSessionId = null;
|
|
486
|
+
this.startAuthSent = false;
|
|
487
|
+
this.setConnectionState("connecting");
|
|
488
|
+
this.emit("logoutSuccess");
|
|
489
|
+
break;
|
|
418
490
|
case "Snapshot":
|
|
419
491
|
this.handleSnapshot(message.data);
|
|
420
492
|
break;
|
|
@@ -584,10 +656,12 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
584
656
|
}
|
|
585
657
|
/** Taker-only: request current indicative prices for a market + position_type. */
|
|
586
658
|
getIndicativePrices(req) {
|
|
659
|
+
const requestId = this.nextRequestId();
|
|
587
660
|
this.send({
|
|
588
661
|
type: "GetIndicativePrices",
|
|
589
|
-
data: req,
|
|
662
|
+
data: { ...req, request_id: requestId },
|
|
590
663
|
});
|
|
664
|
+
return requestId;
|
|
591
665
|
}
|
|
592
666
|
/** Maker-only: respond to an indicative request (unsigned, non-binding). */
|
|
593
667
|
sendIndicativePricesResponse(resp) {
|
|
@@ -616,8 +690,9 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
616
690
|
}
|
|
617
691
|
}
|
|
618
692
|
async beginAuthHandshake() {
|
|
619
|
-
|
|
620
|
-
|
|
693
|
+
const resumeSessionId = this.pendingResumeSessionId ?? this.lastAuthSessionId;
|
|
694
|
+
if (resumeSessionId) {
|
|
695
|
+
this.sendResumeAuth(resumeSessionId);
|
|
621
696
|
return;
|
|
622
697
|
}
|
|
623
698
|
await this.sendStartAuth();
|
|
@@ -654,6 +729,9 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
654
729
|
handleAuthSuccess(sessionId, expiresAt) {
|
|
655
730
|
this.sessionId = sessionId;
|
|
656
731
|
this.pendingResumeSessionId = null;
|
|
732
|
+
if (expiresAt !== null) {
|
|
733
|
+
this.lastAuthSessionId = sessionId;
|
|
734
|
+
}
|
|
657
735
|
this.setConnectionState("authenticated");
|
|
658
736
|
this.emit("authenticated", sessionId, expiresAt);
|
|
659
737
|
if (this.subscribedChannels.size > 0) {
|
|
@@ -665,12 +743,13 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
665
743
|
}
|
|
666
744
|
}
|
|
667
745
|
handleAuthError(reason, message) {
|
|
668
|
-
this.emit("authError", reason);
|
|
746
|
+
this.emit("authError", reason, message);
|
|
669
747
|
if (reason === "session_expired" &&
|
|
670
748
|
this.authRequested &&
|
|
671
749
|
this.authProvider &&
|
|
672
|
-
this.pendingResumeSessionId) {
|
|
750
|
+
(this.pendingResumeSessionId || this.lastAuthSessionId)) {
|
|
673
751
|
this.pendingResumeSessionId = null;
|
|
752
|
+
this.lastAuthSessionId = null;
|
|
674
753
|
this.startAuthSent = false;
|
|
675
754
|
void this.sendStartAuth().catch((err) => {
|
|
676
755
|
this.emit("error", err);
|
|
@@ -786,6 +865,9 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
786
865
|
this.log("Cannot send, WebSocket not open");
|
|
787
866
|
}
|
|
788
867
|
}
|
|
868
|
+
nextRequestId() {
|
|
869
|
+
return generateRequestId();
|
|
870
|
+
}
|
|
789
871
|
ensureAuthenticated() {
|
|
790
872
|
if (this.connectionState !== "authenticated") {
|
|
791
873
|
throw new Error("Client is not authenticated");
|
|
@@ -880,7 +962,6 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
880
962
|
this.helloSent = false;
|
|
881
963
|
this.welcomeReceived = false;
|
|
882
964
|
this.startAuthSent = false;
|
|
883
|
-
this.pendingResumeSessionId = null;
|
|
884
965
|
this.pendingMessages = [];
|
|
885
966
|
this.setConnectionState("disconnected");
|
|
886
967
|
}
|
package/dist/ws/client.test.js
CHANGED
|
@@ -167,13 +167,123 @@ describe("ActaWsClient", () => {
|
|
|
167
167
|
});
|
|
168
168
|
it("emits authError reason on AuthError", () => {
|
|
169
169
|
const { client } = makeHarness();
|
|
170
|
-
const
|
|
171
|
-
client.on("authError", (reason) =>
|
|
170
|
+
const errors = [];
|
|
171
|
+
client.on("authError", (reason, message) => errors.push({ reason, message }));
|
|
172
172
|
client.handleMessage({
|
|
173
173
|
type: "AuthError",
|
|
174
174
|
data: { reason: "invalid_signature", message: "bad signature bytes" },
|
|
175
175
|
});
|
|
176
|
-
expect(
|
|
176
|
+
expect(errors).toEqual([
|
|
177
|
+
{ reason: "invalid_signature", message: "bad signature bytes" },
|
|
178
|
+
]);
|
|
179
|
+
});
|
|
180
|
+
it("uses last auth session for resume-first reconnect", async () => {
|
|
181
|
+
const { client, socket } = makeHarness();
|
|
182
|
+
const auth = makeAuthProvider("WalletPubkey", "WalletSignature");
|
|
183
|
+
client.connectAndAuthenticate(auth);
|
|
184
|
+
let ws = socket();
|
|
185
|
+
ws.triggerOpen();
|
|
186
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
187
|
+
await flushMicrotasks();
|
|
188
|
+
ws.triggerMessage({
|
|
189
|
+
type: "AuthSuccess",
|
|
190
|
+
data: { session_id: "persisted-session", expires_at: 1_710_086_400 },
|
|
191
|
+
});
|
|
192
|
+
ws.triggerClose(1006, "network_drop");
|
|
193
|
+
client.connectAndAuthenticate(auth);
|
|
194
|
+
ws = socket();
|
|
195
|
+
ws.triggerOpen();
|
|
196
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
197
|
+
await flushMicrotasks();
|
|
198
|
+
const sentTypes = ws.sent.map((payload) => parseClientMessage(payload).type);
|
|
199
|
+
expect(sentTypes).toEqual(["Hello", "ResumeAuth"]);
|
|
200
|
+
const resumeAuth = parseClientMessage(ws.sent[1]);
|
|
201
|
+
expect(resumeAuth.type).toBe("ResumeAuth");
|
|
202
|
+
if (resumeAuth.type === "ResumeAuth") {
|
|
203
|
+
expect(resumeAuth.data.session_id).toBe("persisted-session");
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
it("sends Logout and emits logoutSuccess", () => {
|
|
207
|
+
const { client, socket } = makeHarness();
|
|
208
|
+
const events = [];
|
|
209
|
+
client.on("logoutSuccess", () => events.push("logoutSuccess"));
|
|
210
|
+
client.connectAnonymous();
|
|
211
|
+
const ws = socket();
|
|
212
|
+
ws.triggerOpen();
|
|
213
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
214
|
+
client.logout();
|
|
215
|
+
const sent = parseClientMessage(ws.sent[1]);
|
|
216
|
+
expect(sent.type).toBe("Logout");
|
|
217
|
+
ws.triggerMessage({ type: "LogoutSuccess", data: {} });
|
|
218
|
+
expect(events).toEqual(["logoutSuccess"]);
|
|
219
|
+
});
|
|
220
|
+
it("adds request_id to public request messages", () => {
|
|
221
|
+
const { client, socket } = makeHarness();
|
|
222
|
+
client.connectAnonymous();
|
|
223
|
+
const ws = socket();
|
|
224
|
+
ws.triggerOpen();
|
|
225
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
226
|
+
const getMarketsId = client.getMarkets();
|
|
227
|
+
const getMarketDescriptorsId = client.getMarketDescriptors();
|
|
228
|
+
const getTokensId = client.getTokens();
|
|
229
|
+
const getExpiriesId = client.getExpiries();
|
|
230
|
+
const getActiveRfqsId = client.getActiveRfqs();
|
|
231
|
+
const getIndicativePricesId = client.getIndicativePrices({
|
|
232
|
+
market: "market-1",
|
|
233
|
+
position_type: "covered_call",
|
|
234
|
+
});
|
|
235
|
+
const sent = ws.sent.slice(1).map(parseClientMessage);
|
|
236
|
+
const withRequestId = sent.filter((msg) => msg.type !== "Logout");
|
|
237
|
+
for (const msg of withRequestId) {
|
|
238
|
+
if ("data" in msg) {
|
|
239
|
+
const data = msg.data;
|
|
240
|
+
expect(typeof data.request_id).toBe("string");
|
|
241
|
+
expect(data.request_id.length).toBeGreaterThan(0);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
expect(sent[0].data.request_id).toBe(getMarketsId);
|
|
245
|
+
expect(sent[1].data.request_id).toBe(getMarketDescriptorsId);
|
|
246
|
+
expect(sent[2].data.request_id).toBe(getTokensId);
|
|
247
|
+
expect(sent[3].data.request_id).toBe(getExpiriesId);
|
|
248
|
+
expect(sent[4].data.request_id).toBe(getActiveRfqsId);
|
|
249
|
+
expect(sent[5].data.request_id).toBe(getIndicativePricesId);
|
|
250
|
+
});
|
|
251
|
+
it("adds request_id to authenticated request messages", () => {
|
|
252
|
+
const { client, socket } = makeHarness();
|
|
253
|
+
client.connectAnonymous();
|
|
254
|
+
const ws = socket();
|
|
255
|
+
ws.triggerOpen();
|
|
256
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
257
|
+
client.handleMessage({
|
|
258
|
+
type: "AuthSuccess",
|
|
259
|
+
data: { session_id: "session-id", expires_at: 1_710_086_400 },
|
|
260
|
+
});
|
|
261
|
+
const getPositionsId = client.getPositions();
|
|
262
|
+
const getMyActiveRfqsId = client.getMyActiveRfqs();
|
|
263
|
+
const getOrderStatusId = client.getOrderStatus("11".repeat(32));
|
|
264
|
+
const cancelRfqId = client.cancelRfq("rfq-1");
|
|
265
|
+
const cancelQuoteId = client.cancelQuote("rfq-2");
|
|
266
|
+
const sent = ws.sent.slice(1).map(parseClientMessage);
|
|
267
|
+
const authRequests = sent.filter((msg) => [
|
|
268
|
+
"GetPositions",
|
|
269
|
+
"GetMyActiveRfqs",
|
|
270
|
+
"GetOrderStatus",
|
|
271
|
+
"CancelRfq",
|
|
272
|
+
"CancelQuote",
|
|
273
|
+
].includes(msg.type));
|
|
274
|
+
expect(authRequests).toHaveLength(5);
|
|
275
|
+
for (const msg of authRequests) {
|
|
276
|
+
if ("data" in msg) {
|
|
277
|
+
const data = msg.data;
|
|
278
|
+
expect(typeof data.request_id).toBe("string");
|
|
279
|
+
expect(data.request_id.length).toBeGreaterThan(0);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
expect(authRequests[0].data.request_id).toBe(getPositionsId);
|
|
283
|
+
expect(authRequests[1].data.request_id).toBe(getMyActiveRfqsId);
|
|
284
|
+
expect(authRequests[2].data.request_id).toBe(getOrderStatusId);
|
|
285
|
+
expect(authRequests[3].data.request_id).toBe(cancelRfqId);
|
|
286
|
+
expect(authRequests[4].data.request_id).toBe(cancelQuoteId);
|
|
177
287
|
});
|
|
178
288
|
it("drop_oldest policy keeps the latest queued messages", () => {
|
|
179
289
|
const { client, socket } = makeHarness({
|
package/dist/ws/types.d.ts
CHANGED
|
@@ -83,6 +83,8 @@ export type ClientMessage = {
|
|
|
83
83
|
data: {
|
|
84
84
|
session_id: string;
|
|
85
85
|
};
|
|
86
|
+
} | {
|
|
87
|
+
type: "Logout";
|
|
86
88
|
} | {
|
|
87
89
|
type: "AuthChallenge";
|
|
88
90
|
data: AuthChallengeData;
|
|
@@ -93,6 +95,7 @@ export type ClientMessage = {
|
|
|
93
95
|
type: "CancelQuote";
|
|
94
96
|
data: {
|
|
95
97
|
rfq_id: UuidString;
|
|
98
|
+
request_id: RequestId;
|
|
96
99
|
};
|
|
97
100
|
} | {
|
|
98
101
|
type: "IndicativePricesResponse";
|
|
@@ -113,14 +116,17 @@ export type ClientMessage = {
|
|
|
113
116
|
type: "CancelRfq";
|
|
114
117
|
data: {
|
|
115
118
|
rfq_id: UuidString;
|
|
119
|
+
request_id: RequestId;
|
|
116
120
|
};
|
|
117
121
|
} | {
|
|
118
122
|
type: "GetIndicativePrices";
|
|
119
123
|
data: GetIndicativePricesMessage;
|
|
120
124
|
} | {
|
|
121
125
|
type: "GetPositions";
|
|
126
|
+
data: GetPositionsMessage;
|
|
122
127
|
} | {
|
|
123
128
|
type: "GetMarkets";
|
|
129
|
+
data: GetMarketsMessage;
|
|
124
130
|
} | {
|
|
125
131
|
type: "GetMarketDescriptors";
|
|
126
132
|
data: GetMarketDescriptorsMessage;
|
|
@@ -132,11 +138,13 @@ export type ClientMessage = {
|
|
|
132
138
|
data: GetTokensMessage;
|
|
133
139
|
} | {
|
|
134
140
|
type: "GetMyActiveRfqs";
|
|
141
|
+
data: GetMyActiveRfqsMessage;
|
|
135
142
|
} | {
|
|
136
143
|
type: "GetOrderStatus";
|
|
137
144
|
data: GetOrderStatusMessage;
|
|
138
145
|
} | {
|
|
139
146
|
type: "GetActiveRfqs";
|
|
147
|
+
data: GetActiveRfqsMessage;
|
|
140
148
|
} | {
|
|
141
149
|
type: "GetMakerPositions";
|
|
142
150
|
data: GetMakerPositionsMessage;
|
|
@@ -148,8 +156,10 @@ export type ClientMessage = {
|
|
|
148
156
|
data: GetMarketsForMakerMessage;
|
|
149
157
|
} | {
|
|
150
158
|
type: "GetMakerBalances";
|
|
159
|
+
data: GetMakerBalancesMessage;
|
|
151
160
|
} | {
|
|
152
161
|
type: "GetSubscriptions";
|
|
162
|
+
data: GetSubscriptionsMessage;
|
|
153
163
|
} | {
|
|
154
164
|
type: "CancelAllQuotes";
|
|
155
165
|
data: CancelAllQuotesMessage;
|
|
@@ -168,29 +178,54 @@ export type ClientMessage = {
|
|
|
168
178
|
};
|
|
169
179
|
};
|
|
170
180
|
export type GetMarketDescriptorsMessage = {
|
|
181
|
+
request_id: RequestId;
|
|
171
182
|
active_only?: boolean;
|
|
172
183
|
};
|
|
173
184
|
export type GetTokensMessage = {
|
|
185
|
+
request_id: RequestId;
|
|
174
186
|
active_only?: boolean;
|
|
175
187
|
};
|
|
176
188
|
export type GetExpiriesMessage = {
|
|
189
|
+
request_id: RequestId;
|
|
177
190
|
underlying_mint?: Address<string>;
|
|
178
191
|
quote_mint?: Address<string>;
|
|
179
192
|
is_put?: boolean | null;
|
|
180
193
|
};
|
|
194
|
+
export type GetPositionsMessage = {
|
|
195
|
+
request_id: RequestId;
|
|
196
|
+
};
|
|
197
|
+
export type GetMarketsMessage = {
|
|
198
|
+
request_id: RequestId;
|
|
199
|
+
};
|
|
200
|
+
export type GetMyActiveRfqsMessage = {
|
|
201
|
+
request_id: RequestId;
|
|
202
|
+
};
|
|
203
|
+
export type GetActiveRfqsMessage = {
|
|
204
|
+
request_id: RequestId;
|
|
205
|
+
};
|
|
206
|
+
export type GetMakerBalancesMessage = {
|
|
207
|
+
request_id: RequestId;
|
|
208
|
+
};
|
|
209
|
+
export type GetSubscriptionsMessage = {
|
|
210
|
+
request_id: RequestId;
|
|
211
|
+
};
|
|
181
212
|
export type GetOrderStatusMessage = {
|
|
213
|
+
request_id: RequestId;
|
|
182
214
|
order_id: OrderIdHex32;
|
|
183
215
|
};
|
|
184
216
|
export type GetMakerPositionsMessage = {
|
|
217
|
+
request_id: RequestId;
|
|
185
218
|
market?: string;
|
|
186
219
|
underlying_mint?: string;
|
|
187
220
|
status?: string[];
|
|
188
221
|
min_expiry_ts?: WsU64;
|
|
189
222
|
};
|
|
190
223
|
export type GetMyQuotesMessage = {
|
|
224
|
+
request_id: RequestId;
|
|
191
225
|
active_only?: boolean;
|
|
192
226
|
};
|
|
193
227
|
export type GetMarketsForMakerMessage = {
|
|
228
|
+
request_id: RequestId;
|
|
194
229
|
underlying_mints?: string[];
|
|
195
230
|
quote_mints?: string[];
|
|
196
231
|
min_expiry_ts?: WsU64;
|
|
@@ -199,6 +234,7 @@ export type GetMarketsForMakerMessage = {
|
|
|
199
234
|
include_stats?: boolean;
|
|
200
235
|
};
|
|
201
236
|
export type CancelAllQuotesMessage = {
|
|
237
|
+
request_id: RequestId;
|
|
202
238
|
market?: string;
|
|
203
239
|
};
|
|
204
240
|
export type AuthChallengeData = {
|
|
@@ -268,6 +304,9 @@ export type ServerMessage = {
|
|
|
268
304
|
reason: ErrorMessage;
|
|
269
305
|
message?: string;
|
|
270
306
|
};
|
|
307
|
+
} | {
|
|
308
|
+
type: "LogoutSuccess";
|
|
309
|
+
data: Record<string, never>;
|
|
271
310
|
} | {
|
|
272
311
|
type: "RfqCreated";
|
|
273
312
|
data: RfqCreatedMessage;
|
|
@@ -319,6 +358,7 @@ export type ServerMessage = {
|
|
|
319
358
|
} | {
|
|
320
359
|
type: "ActiveRfqs";
|
|
321
360
|
data: {
|
|
361
|
+
request_id: RequestId;
|
|
322
362
|
rfqs: ActiveRfqInfo[];
|
|
323
363
|
};
|
|
324
364
|
} | {
|
|
@@ -625,11 +665,14 @@ export type QuoteExpiredMessage = {
|
|
|
625
665
|
reason: QuoteExpiredReason;
|
|
626
666
|
};
|
|
627
667
|
export type MakerPositionsMessage = {
|
|
668
|
+
request_id: RequestId;
|
|
628
669
|
positions: MakerPositionInfo[];
|
|
629
670
|
};
|
|
630
671
|
export type MakerPositionInfo = {
|
|
631
672
|
pda: string;
|
|
632
673
|
market: string;
|
|
674
|
+
underlying_mint: string;
|
|
675
|
+
quote_mint: string;
|
|
633
676
|
position_type: PositionType;
|
|
634
677
|
status: PositionStatus;
|
|
635
678
|
strike: WsU64;
|
|
@@ -642,6 +685,7 @@ export type MakerPositionInfo = {
|
|
|
642
685
|
expiry_ts: WsU64;
|
|
643
686
|
};
|
|
644
687
|
export type MyQuotesMessage = {
|
|
688
|
+
request_id: RequestId;
|
|
645
689
|
quotes: MakerQuoteInfo[];
|
|
646
690
|
};
|
|
647
691
|
export type MakerQuoteInfo = {
|
|
@@ -657,6 +701,7 @@ export type MakerQuoteInfo = {
|
|
|
657
701
|
created_at: WsU64;
|
|
658
702
|
};
|
|
659
703
|
export type MakerMarketsMessage = {
|
|
704
|
+
request_id: RequestId;
|
|
660
705
|
markets: MakerMarketInfo[];
|
|
661
706
|
};
|
|
662
707
|
export type MakerMarketInfo = {
|
|
@@ -675,6 +720,7 @@ export type MarketStats = {
|
|
|
675
720
|
trades_24h: WsU32;
|
|
676
721
|
};
|
|
677
722
|
export type MakerBalancesMessage = {
|
|
723
|
+
request_id: RequestId;
|
|
678
724
|
balances_by_mint: Record<string, MakerMintBalance>;
|
|
679
725
|
};
|
|
680
726
|
export type MakerMintBalance = {
|
|
@@ -683,6 +729,7 @@ export type MakerMintBalance = {
|
|
|
683
729
|
available: WsU64;
|
|
684
730
|
};
|
|
685
731
|
export type SubscriptionsMessage = {
|
|
732
|
+
request_id: RequestId;
|
|
686
733
|
channels: WsChannel[];
|
|
687
734
|
markets?: string[];
|
|
688
735
|
};
|
|
@@ -702,10 +749,12 @@ export type IndicativePricesResponseMessage = {
|
|
|
702
749
|
}>;
|
|
703
750
|
};
|
|
704
751
|
export type GetIndicativePricesMessage = {
|
|
752
|
+
request_id: RequestId;
|
|
705
753
|
market: Address<string>;
|
|
706
754
|
position_type: PositionType;
|
|
707
755
|
};
|
|
708
756
|
export type IndicativePricesMessage = {
|
|
757
|
+
request_id: RequestId;
|
|
709
758
|
market: Address<string>;
|
|
710
759
|
position_type: PositionType;
|
|
711
760
|
updated_at: WsU64;
|
|
@@ -746,18 +795,23 @@ export type QuotesUpdateMessage = {
|
|
|
746
795
|
quotes: QuoteReceivedMessage[];
|
|
747
796
|
};
|
|
748
797
|
export type PositionsMessage = {
|
|
798
|
+
request_id: RequestId;
|
|
749
799
|
positions: PositionInfo[];
|
|
750
800
|
};
|
|
751
801
|
export type MarketsMessage = {
|
|
802
|
+
request_id: RequestId;
|
|
752
803
|
markets: MarketInfo[];
|
|
753
804
|
};
|
|
754
805
|
export type MarketDescriptorsMessage = {
|
|
806
|
+
request_id: RequestId;
|
|
755
807
|
markets: MarketDescriptorInfo[];
|
|
756
808
|
};
|
|
757
809
|
export type ExpiriesMessage = {
|
|
810
|
+
request_id: RequestId;
|
|
758
811
|
expiries_ts: WsU64[];
|
|
759
812
|
};
|
|
760
813
|
export type TokensMessage = {
|
|
814
|
+
request_id: RequestId;
|
|
761
815
|
underlyings: TokenInfo[];
|
|
762
816
|
quotes_by_underlying: Record<Address<string>, TokenInfo[]>;
|
|
763
817
|
};
|
|
@@ -769,9 +823,11 @@ export type SnapshotMessage = {
|
|
|
769
823
|
markets: MarketInfo[];
|
|
770
824
|
};
|
|
771
825
|
export type MyActiveRfqsMessage = {
|
|
826
|
+
request_id: RequestId;
|
|
772
827
|
rfqs: MyActiveRfqInfo[];
|
|
773
828
|
};
|
|
774
829
|
export type OrderStatusMessage = {
|
|
830
|
+
request_id: RequestId;
|
|
775
831
|
order_id: OrderIdHex32;
|
|
776
832
|
status: OrderStatusValue;
|
|
777
833
|
rfq_id?: UuidString | null;
|
|
@@ -817,6 +873,8 @@ export type RfqOrderOption = {
|
|
|
817
873
|
export type PositionInfo = {
|
|
818
874
|
pda: string;
|
|
819
875
|
market: string;
|
|
876
|
+
underlying_mint: string;
|
|
877
|
+
quote_mint: string;
|
|
820
878
|
position_type: PositionType;
|
|
821
879
|
status: PositionStatus;
|
|
822
880
|
strike: WsU64;
|