@acta-markets/ts-sdk 0.0.1-beta → 0.0.3-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/dist/actaClient.d.ts +3 -1
- package/dist/actaClient.js +2 -2
- package/dist/chain/fetch.js +24 -26
- package/dist/chain/fetch.test.js +30 -1
- package/dist/cjs/actaClient.js +2 -2
- package/dist/cjs/chain/fetch.js +24 -26
- package/dist/cjs/chain/fetch.test.js +29 -0
- package/dist/cjs/ws/client.js +45 -9
- package/dist/cjs/ws/client.test.js +56 -0
- package/dist/ws/client.d.ts +9 -2
- package/dist/ws/client.js +45 -9
- package/dist/ws/client.test.js +56 -0
- package/dist/ws/types.d.ts +11 -0
- package/package.json +1 -1
package/dist/actaClient.d.ts
CHANGED
|
@@ -38,7 +38,9 @@ export declare class ActaClient {
|
|
|
38
38
|
/** Convenience: `client.connectWs(auth)` instead of `client.ws!.connect(auth)`. */
|
|
39
39
|
connectWs(authProvider: AuthProvider): this;
|
|
40
40
|
/** Convenience: connect and trigger auth flow in one call. */
|
|
41
|
-
connectWsAndAuthenticate(authProvider: AuthProvider
|
|
41
|
+
connectWsAndAuthenticate(authProvider: AuthProvider, options?: {
|
|
42
|
+
sessionId?: string;
|
|
43
|
+
}): this;
|
|
42
44
|
/** Convenience: connect WS without triggering auth (anonymous browsing). */
|
|
43
45
|
connectWsAnonymous(): this;
|
|
44
46
|
/** Convenience: disconnect WS (and stop reconnect timers). */
|
package/dist/actaClient.js
CHANGED
|
@@ -82,8 +82,8 @@ export class ActaClient {
|
|
|
82
82
|
return this;
|
|
83
83
|
}
|
|
84
84
|
/** Convenience: connect and trigger auth flow in one call. */
|
|
85
|
-
connectWsAndAuthenticate(authProvider) {
|
|
86
|
-
this.assertWs().connectAndAuthenticate(authProvider);
|
|
85
|
+
connectWsAndAuthenticate(authProvider, options) {
|
|
86
|
+
this.assertWs().connectAndAuthenticate(authProvider, options);
|
|
87
87
|
return this;
|
|
88
88
|
}
|
|
89
89
|
/** Convenience: connect WS without triggering auth (anonymous browsing). */
|
package/dist/chain/fetch.js
CHANGED
|
@@ -86,6 +86,13 @@ async function fetchAndDecode(rpc, config, decode) {
|
|
|
86
86
|
function dataSize(bytes) {
|
|
87
87
|
return { dataSize: BigInt(bytes) };
|
|
88
88
|
}
|
|
89
|
+
function marketAccountFilters(...extra) {
|
|
90
|
+
return [
|
|
91
|
+
dataSize(MARKET_ACCOUNT_SIZE_BYTES),
|
|
92
|
+
memcmp(0, new Uint8Array([MARKET_ACCOUNT_DISCRIMINATOR])),
|
|
93
|
+
...extra,
|
|
94
|
+
];
|
|
95
|
+
}
|
|
89
96
|
// --- Positions ---
|
|
90
97
|
export async function fetchPositionsByMaker(rpc, makerOwner, options) {
|
|
91
98
|
return rpc
|
|
@@ -236,10 +243,7 @@ export async function fetchAllMarketsDecoded(rpc, options) {
|
|
|
236
243
|
return fetchAndDecode(rpc, {
|
|
237
244
|
commitment: options?.commitment,
|
|
238
245
|
dataSlice: options?.dataSlice,
|
|
239
|
-
filters:
|
|
240
|
-
dataSize(MARKET_ACCOUNT_SIZE_BYTES),
|
|
241
|
-
memcmp(0, new Uint8Array([MARKET_ACCOUNT_DISCRIMINATOR])),
|
|
242
|
-
],
|
|
246
|
+
filters: marketAccountFilters(),
|
|
243
247
|
}, decodeMarketAccount);
|
|
244
248
|
}
|
|
245
249
|
export async function fetchAllOraclesDecoded(rpc, options) {
|
|
@@ -268,7 +272,7 @@ export async function fetchMarketsByUnderlying(rpc, underlying, options) {
|
|
|
268
272
|
.getProgramAccounts(getActaProgramId(), {
|
|
269
273
|
commitment: options?.commitment,
|
|
270
274
|
dataSlice: options?.dataSlice,
|
|
271
|
-
filters:
|
|
275
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying))),
|
|
272
276
|
})
|
|
273
277
|
.send();
|
|
274
278
|
}
|
|
@@ -276,7 +280,7 @@ export async function fetchMarketsByUnderlyingDecoded(rpc, underlying, options)
|
|
|
276
280
|
return fetchAndDecode(rpc, {
|
|
277
281
|
commitment: options?.commitment,
|
|
278
282
|
dataSlice: options?.dataSlice,
|
|
279
|
-
filters:
|
|
283
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying))),
|
|
280
284
|
}, decodeMarketAccount);
|
|
281
285
|
}
|
|
282
286
|
export async function fetchMarketsByQuote(rpc, quote, options) {
|
|
@@ -284,7 +288,7 @@ export async function fetchMarketsByQuote(rpc, quote, options) {
|
|
|
284
288
|
.getProgramAccounts(getActaProgramId(), {
|
|
285
289
|
commitment: options?.commitment,
|
|
286
290
|
dataSlice: options?.dataSlice,
|
|
287
|
-
filters:
|
|
291
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_QUOTE_MINT, addrBytes(quote))),
|
|
288
292
|
})
|
|
289
293
|
.send();
|
|
290
294
|
}
|
|
@@ -292,7 +296,7 @@ export async function fetchMarketsByQuoteDecoded(rpc, quote, options) {
|
|
|
292
296
|
return fetchAndDecode(rpc, {
|
|
293
297
|
commitment: options?.commitment,
|
|
294
298
|
dataSlice: options?.dataSlice,
|
|
295
|
-
filters:
|
|
299
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_QUOTE_MINT, addrBytes(quote))),
|
|
296
300
|
}, decodeMarketAccount);
|
|
297
301
|
}
|
|
298
302
|
export async function fetchMarketsByExpiry(rpc, expiryTs, options) {
|
|
@@ -300,7 +304,7 @@ export async function fetchMarketsByExpiry(rpc, expiryTs, options) {
|
|
|
300
304
|
.getProgramAccounts(getActaProgramId(), {
|
|
301
305
|
commitment: options?.commitment,
|
|
302
306
|
dataSlice: options?.dataSlice,
|
|
303
|
-
filters:
|
|
307
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
304
308
|
})
|
|
305
309
|
.send();
|
|
306
310
|
}
|
|
@@ -308,7 +312,7 @@ export async function fetchMarketsByExpiryDecoded(rpc, expiryTs, options) {
|
|
|
308
312
|
return fetchAndDecode(rpc, {
|
|
309
313
|
commitment: options?.commitment,
|
|
310
314
|
dataSlice: options?.dataSlice,
|
|
311
|
-
filters:
|
|
315
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
312
316
|
}, decodeMarketAccount);
|
|
313
317
|
}
|
|
314
318
|
export async function fetchMarketsByQuoteAndExpiry(rpc, quote, expiryTs, options) {
|
|
@@ -316,10 +320,7 @@ export async function fetchMarketsByQuoteAndExpiry(rpc, quote, expiryTs, options
|
|
|
316
320
|
.getProgramAccounts(getActaProgramId(), {
|
|
317
321
|
commitment: options?.commitment,
|
|
318
322
|
dataSlice: options?.dataSlice,
|
|
319
|
-
filters:
|
|
320
|
-
memcmp(MARKET_OFFSET_QUOTE_MINT, addrBytes(quote)),
|
|
321
|
-
memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs)),
|
|
322
|
-
],
|
|
323
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_QUOTE_MINT, addrBytes(quote)), memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
323
324
|
})
|
|
324
325
|
.send();
|
|
325
326
|
}
|
|
@@ -327,10 +328,7 @@ export async function fetchMarketsByQuoteAndExpiryDecoded(rpc, quote, expiryTs,
|
|
|
327
328
|
return fetchAndDecode(rpc, {
|
|
328
329
|
commitment: options?.commitment,
|
|
329
330
|
dataSlice: options?.dataSlice,
|
|
330
|
-
filters:
|
|
331
|
-
memcmp(MARKET_OFFSET_QUOTE_MINT, addrBytes(quote)),
|
|
332
|
-
memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs)),
|
|
333
|
-
],
|
|
331
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_QUOTE_MINT, addrBytes(quote)), memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
334
332
|
}, decodeMarketAccount);
|
|
335
333
|
}
|
|
336
334
|
export async function fetchMarketsByUnderlyingAndExpiry(rpc, underlying, expiryTs, options) {
|
|
@@ -338,10 +336,7 @@ export async function fetchMarketsByUnderlyingAndExpiry(rpc, underlying, expiryT
|
|
|
338
336
|
.getProgramAccounts(getActaProgramId(), {
|
|
339
337
|
commitment: options?.commitment,
|
|
340
338
|
dataSlice: options?.dataSlice,
|
|
341
|
-
filters:
|
|
342
|
-
memcmp(MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying)),
|
|
343
|
-
memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs)),
|
|
344
|
-
],
|
|
339
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying)), memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
345
340
|
})
|
|
346
341
|
.send();
|
|
347
342
|
}
|
|
@@ -368,10 +363,7 @@ export async function fetchMarketsByUnderlyingAndExpiryDecoded(rpc, underlying,
|
|
|
368
363
|
return fetchAndDecode(rpc, {
|
|
369
364
|
commitment: options?.commitment,
|
|
370
365
|
dataSlice: options?.dataSlice,
|
|
371
|
-
filters:
|
|
372
|
-
memcmp(MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying)),
|
|
373
|
-
memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs)),
|
|
374
|
-
],
|
|
366
|
+
filters: marketAccountFilters(memcmp(MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying)), memcmp(MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
375
367
|
}, decodeMarketAccount);
|
|
376
368
|
}
|
|
377
369
|
// --- Decode helpers ---
|
|
@@ -379,6 +371,12 @@ export function decodePositionAccount(data) {
|
|
|
379
371
|
return getPositionDecoder().decode(data);
|
|
380
372
|
}
|
|
381
373
|
export function decodeMarketAccount(data) {
|
|
374
|
+
if (data.length < MARKET_ACCOUNT_SIZE_BYTES) {
|
|
375
|
+
throw new Error(`Invalid Market account size: expected >= ${MARKET_ACCOUNT_SIZE_BYTES}, got ${data.length}`);
|
|
376
|
+
}
|
|
377
|
+
if (data[0] !== MARKET_ACCOUNT_DISCRIMINATOR) {
|
|
378
|
+
throw new Error(`Invalid Market account discriminator: expected ${MARKET_ACCOUNT_DISCRIMINATOR}, got ${data[0]}`);
|
|
379
|
+
}
|
|
382
380
|
return getMarketDecoder().decode(data);
|
|
383
381
|
}
|
|
384
382
|
export function decodeConfigAccount(data) {
|
package/dist/chain/fetch.test.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MAKER_OFFSET_MAKER_OWNER, MAKER_OFFSET_QUOTE_SIGNING, MARKET_OFFSET_EXPIRY_TS, MARKET_OFFSET_FLAGS, MARKET_OFFSET_SETTLEMENT_PRICE, MARKET_OFFSET_UNDERLYING_DECIMALS, MARKET_OFFSET_QUOTE_DECIMALS, MARKET_OFFSET_QUOTE_MINT, MARKET_OFFSET_QUOTE_ORACLE_ADDRESS, MARKET_OFFSET_UNDERLYING_MINT, MARKET_OFFSET_UNDERLYING_ORACLE_ADDRESS, POSITION_OFFSET_MAKER_OWNER, POSITION_OFFSET_MARKET, POSITION_OFFSET_STATUS, POSITION_OFFSET_TAKER_OWNER, } from "./fetch";
|
|
1
|
+
import { decodeMarketAccount, MAKER_OFFSET_MAKER_OWNER, MAKER_OFFSET_QUOTE_SIGNING, MARKET_OFFSET_EXPIRY_TS, MARKET_OFFSET_FLAGS, MARKET_OFFSET_SETTLEMENT_PRICE, MARKET_OFFSET_UNDERLYING_DECIMALS, MARKET_OFFSET_QUOTE_DECIMALS, MARKET_OFFSET_QUOTE_MINT, MARKET_OFFSET_QUOTE_ORACLE_ADDRESS, MARKET_OFFSET_UNDERLYING_MINT, MARKET_OFFSET_UNDERLYING_ORACLE_ADDRESS, POSITION_OFFSET_MAKER_OWNER, POSITION_OFFSET_MARKET, POSITION_OFFSET_STATUS, POSITION_OFFSET_TAKER_OWNER, } from "./fetch";
|
|
2
2
|
import { getAddressDecoder } from "@solana/addresses";
|
|
3
3
|
import { encodeOrderPreimage } from "./orders";
|
|
4
4
|
import { getMakerEncoder, getMakerSize } from "../generated/accounts/maker";
|
|
@@ -155,4 +155,33 @@ describe("fetch offsets and order encode", () => {
|
|
|
155
155
|
expect(preimage.slice(base + 66, base + 98)).toEqual(makerBytes);
|
|
156
156
|
expect(preimage.slice(base + 98, base + 130)).toEqual(takerBytes);
|
|
157
157
|
});
|
|
158
|
+
it("decodeMarketAccount rejects non-market discriminator and short data", () => {
|
|
159
|
+
const decoder = getAddressDecoder();
|
|
160
|
+
const marketBytes = new Uint8Array(Array(32).fill(1));
|
|
161
|
+
const makerBytes = new Uint8Array(Array(32).fill(7));
|
|
162
|
+
const takerBytes = new Uint8Array(Array(32).fill(8));
|
|
163
|
+
const market = decoder.decode(marketBytes);
|
|
164
|
+
const makerOwner = decoder.decode(makerBytes);
|
|
165
|
+
const takerOwner = decoder.decode(takerBytes);
|
|
166
|
+
const positionData = getPositionEncoder().encode({
|
|
167
|
+
discriminator: 1,
|
|
168
|
+
version: 1,
|
|
169
|
+
bump: 7,
|
|
170
|
+
positionType: 0,
|
|
171
|
+
status: 1,
|
|
172
|
+
flags: 0,
|
|
173
|
+
flags2: 0,
|
|
174
|
+
flags3: 0,
|
|
175
|
+
takerOwner,
|
|
176
|
+
makerOwner,
|
|
177
|
+
market,
|
|
178
|
+
strike: 1n,
|
|
179
|
+
quantity: 1n,
|
|
180
|
+
totalPremium: 1n,
|
|
181
|
+
orderId: new Uint8Array(32).fill(0x11),
|
|
182
|
+
reserved: Array(32).fill(0n),
|
|
183
|
+
});
|
|
184
|
+
expect(() => decodeMarketAccount(Buffer.from(positionData))).toThrow("Invalid Market account discriminator");
|
|
185
|
+
expect(() => decodeMarketAccount(Buffer.alloc(8))).toThrow("Invalid Market account size");
|
|
186
|
+
});
|
|
158
187
|
});
|
package/dist/cjs/actaClient.js
CHANGED
|
@@ -85,8 +85,8 @@ class ActaClient {
|
|
|
85
85
|
return this;
|
|
86
86
|
}
|
|
87
87
|
/** Convenience: connect and trigger auth flow in one call. */
|
|
88
|
-
connectWsAndAuthenticate(authProvider) {
|
|
89
|
-
this.assertWs().connectAndAuthenticate(authProvider);
|
|
88
|
+
connectWsAndAuthenticate(authProvider, options) {
|
|
89
|
+
this.assertWs().connectAndAuthenticate(authProvider, options);
|
|
90
90
|
return this;
|
|
91
91
|
}
|
|
92
92
|
/** Convenience: connect WS without triggering auth (anonymous browsing). */
|
package/dist/cjs/chain/fetch.js
CHANGED
|
@@ -125,6 +125,13 @@ async function fetchAndDecode(rpc, config, decode) {
|
|
|
125
125
|
function dataSize(bytes) {
|
|
126
126
|
return { dataSize: BigInt(bytes) };
|
|
127
127
|
}
|
|
128
|
+
function marketAccountFilters(...extra) {
|
|
129
|
+
return [
|
|
130
|
+
dataSize(exports.MARKET_ACCOUNT_SIZE_BYTES),
|
|
131
|
+
memcmp(0, new Uint8Array([exports.MARKET_ACCOUNT_DISCRIMINATOR])),
|
|
132
|
+
...extra,
|
|
133
|
+
];
|
|
134
|
+
}
|
|
128
135
|
// --- Positions ---
|
|
129
136
|
async function fetchPositionsByMaker(rpc, makerOwner, options) {
|
|
130
137
|
return rpc
|
|
@@ -275,10 +282,7 @@ async function fetchAllMarketsDecoded(rpc, options) {
|
|
|
275
282
|
return fetchAndDecode(rpc, {
|
|
276
283
|
commitment: options?.commitment,
|
|
277
284
|
dataSlice: options?.dataSlice,
|
|
278
|
-
filters:
|
|
279
|
-
dataSize(exports.MARKET_ACCOUNT_SIZE_BYTES),
|
|
280
|
-
memcmp(0, new Uint8Array([exports.MARKET_ACCOUNT_DISCRIMINATOR])),
|
|
281
|
-
],
|
|
285
|
+
filters: marketAccountFilters(),
|
|
282
286
|
}, decodeMarketAccount);
|
|
283
287
|
}
|
|
284
288
|
async function fetchAllOraclesDecoded(rpc, options) {
|
|
@@ -307,7 +311,7 @@ async function fetchMarketsByUnderlying(rpc, underlying, options) {
|
|
|
307
311
|
.getProgramAccounts((0, index_1.getActaProgramId)(), {
|
|
308
312
|
commitment: options?.commitment,
|
|
309
313
|
dataSlice: options?.dataSlice,
|
|
310
|
-
filters:
|
|
314
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying))),
|
|
311
315
|
})
|
|
312
316
|
.send();
|
|
313
317
|
}
|
|
@@ -315,7 +319,7 @@ async function fetchMarketsByUnderlyingDecoded(rpc, underlying, options) {
|
|
|
315
319
|
return fetchAndDecode(rpc, {
|
|
316
320
|
commitment: options?.commitment,
|
|
317
321
|
dataSlice: options?.dataSlice,
|
|
318
|
-
filters:
|
|
322
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying))),
|
|
319
323
|
}, decodeMarketAccount);
|
|
320
324
|
}
|
|
321
325
|
async function fetchMarketsByQuote(rpc, quote, options) {
|
|
@@ -323,7 +327,7 @@ async function fetchMarketsByQuote(rpc, quote, options) {
|
|
|
323
327
|
.getProgramAccounts((0, index_1.getActaProgramId)(), {
|
|
324
328
|
commitment: options?.commitment,
|
|
325
329
|
dataSlice: options?.dataSlice,
|
|
326
|
-
filters:
|
|
330
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_QUOTE_MINT, addrBytes(quote))),
|
|
327
331
|
})
|
|
328
332
|
.send();
|
|
329
333
|
}
|
|
@@ -331,7 +335,7 @@ async function fetchMarketsByQuoteDecoded(rpc, quote, options) {
|
|
|
331
335
|
return fetchAndDecode(rpc, {
|
|
332
336
|
commitment: options?.commitment,
|
|
333
337
|
dataSlice: options?.dataSlice,
|
|
334
|
-
filters:
|
|
338
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_QUOTE_MINT, addrBytes(quote))),
|
|
335
339
|
}, decodeMarketAccount);
|
|
336
340
|
}
|
|
337
341
|
async function fetchMarketsByExpiry(rpc, expiryTs, options) {
|
|
@@ -339,7 +343,7 @@ async function fetchMarketsByExpiry(rpc, expiryTs, options) {
|
|
|
339
343
|
.getProgramAccounts((0, index_1.getActaProgramId)(), {
|
|
340
344
|
commitment: options?.commitment,
|
|
341
345
|
dataSlice: options?.dataSlice,
|
|
342
|
-
filters:
|
|
346
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
343
347
|
})
|
|
344
348
|
.send();
|
|
345
349
|
}
|
|
@@ -347,7 +351,7 @@ async function fetchMarketsByExpiryDecoded(rpc, expiryTs, options) {
|
|
|
347
351
|
return fetchAndDecode(rpc, {
|
|
348
352
|
commitment: options?.commitment,
|
|
349
353
|
dataSlice: options?.dataSlice,
|
|
350
|
-
filters:
|
|
354
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
351
355
|
}, decodeMarketAccount);
|
|
352
356
|
}
|
|
353
357
|
async function fetchMarketsByQuoteAndExpiry(rpc, quote, expiryTs, options) {
|
|
@@ -355,10 +359,7 @@ async function fetchMarketsByQuoteAndExpiry(rpc, quote, expiryTs, options) {
|
|
|
355
359
|
.getProgramAccounts((0, index_1.getActaProgramId)(), {
|
|
356
360
|
commitment: options?.commitment,
|
|
357
361
|
dataSlice: options?.dataSlice,
|
|
358
|
-
filters:
|
|
359
|
-
memcmp(exports.MARKET_OFFSET_QUOTE_MINT, addrBytes(quote)),
|
|
360
|
-
memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs)),
|
|
361
|
-
],
|
|
362
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_QUOTE_MINT, addrBytes(quote)), memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
362
363
|
})
|
|
363
364
|
.send();
|
|
364
365
|
}
|
|
@@ -366,10 +367,7 @@ async function fetchMarketsByQuoteAndExpiryDecoded(rpc, quote, expiryTs, options
|
|
|
366
367
|
return fetchAndDecode(rpc, {
|
|
367
368
|
commitment: options?.commitment,
|
|
368
369
|
dataSlice: options?.dataSlice,
|
|
369
|
-
filters:
|
|
370
|
-
memcmp(exports.MARKET_OFFSET_QUOTE_MINT, addrBytes(quote)),
|
|
371
|
-
memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs)),
|
|
372
|
-
],
|
|
370
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_QUOTE_MINT, addrBytes(quote)), memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
373
371
|
}, decodeMarketAccount);
|
|
374
372
|
}
|
|
375
373
|
async function fetchMarketsByUnderlyingAndExpiry(rpc, underlying, expiryTs, options) {
|
|
@@ -377,10 +375,7 @@ async function fetchMarketsByUnderlyingAndExpiry(rpc, underlying, expiryTs, opti
|
|
|
377
375
|
.getProgramAccounts((0, index_1.getActaProgramId)(), {
|
|
378
376
|
commitment: options?.commitment,
|
|
379
377
|
dataSlice: options?.dataSlice,
|
|
380
|
-
filters:
|
|
381
|
-
memcmp(exports.MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying)),
|
|
382
|
-
memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs)),
|
|
383
|
-
],
|
|
378
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying)), memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
384
379
|
})
|
|
385
380
|
.send();
|
|
386
381
|
}
|
|
@@ -407,10 +402,7 @@ async function fetchMarketsByUnderlyingAndExpiryDecoded(rpc, underlying, expiryT
|
|
|
407
402
|
return fetchAndDecode(rpc, {
|
|
408
403
|
commitment: options?.commitment,
|
|
409
404
|
dataSlice: options?.dataSlice,
|
|
410
|
-
filters:
|
|
411
|
-
memcmp(exports.MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying)),
|
|
412
|
-
memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs)),
|
|
413
|
-
],
|
|
405
|
+
filters: marketAccountFilters(memcmp(exports.MARKET_OFFSET_UNDERLYING_MINT, addrBytes(underlying)), memcmp(exports.MARKET_OFFSET_EXPIRY_TS, u64Le(expiryTs))),
|
|
414
406
|
}, decodeMarketAccount);
|
|
415
407
|
}
|
|
416
408
|
// --- Decode helpers ---
|
|
@@ -418,6 +410,12 @@ function decodePositionAccount(data) {
|
|
|
418
410
|
return (0, position_1.getPositionDecoder)().decode(data);
|
|
419
411
|
}
|
|
420
412
|
function decodeMarketAccount(data) {
|
|
413
|
+
if (data.length < exports.MARKET_ACCOUNT_SIZE_BYTES) {
|
|
414
|
+
throw new Error(`Invalid Market account size: expected >= ${exports.MARKET_ACCOUNT_SIZE_BYTES}, got ${data.length}`);
|
|
415
|
+
}
|
|
416
|
+
if (data[0] !== exports.MARKET_ACCOUNT_DISCRIMINATOR) {
|
|
417
|
+
throw new Error(`Invalid Market account discriminator: expected ${exports.MARKET_ACCOUNT_DISCRIMINATOR}, got ${data[0]}`);
|
|
418
|
+
}
|
|
421
419
|
return (0, market_1.getMarketDecoder)().decode(data);
|
|
422
420
|
}
|
|
423
421
|
function decodeConfigAccount(data) {
|
|
@@ -157,4 +157,33 @@ describe("fetch offsets and order encode", () => {
|
|
|
157
157
|
expect(preimage.slice(base + 66, base + 98)).toEqual(makerBytes);
|
|
158
158
|
expect(preimage.slice(base + 98, base + 130)).toEqual(takerBytes);
|
|
159
159
|
});
|
|
160
|
+
it("decodeMarketAccount rejects non-market discriminator and short data", () => {
|
|
161
|
+
const decoder = (0, addresses_1.getAddressDecoder)();
|
|
162
|
+
const marketBytes = new Uint8Array(Array(32).fill(1));
|
|
163
|
+
const makerBytes = new Uint8Array(Array(32).fill(7));
|
|
164
|
+
const takerBytes = new Uint8Array(Array(32).fill(8));
|
|
165
|
+
const market = decoder.decode(marketBytes);
|
|
166
|
+
const makerOwner = decoder.decode(makerBytes);
|
|
167
|
+
const takerOwner = decoder.decode(takerBytes);
|
|
168
|
+
const positionData = (0, position_1.getPositionEncoder)().encode({
|
|
169
|
+
discriminator: 1,
|
|
170
|
+
version: 1,
|
|
171
|
+
bump: 7,
|
|
172
|
+
positionType: 0,
|
|
173
|
+
status: 1,
|
|
174
|
+
flags: 0,
|
|
175
|
+
flags2: 0,
|
|
176
|
+
flags3: 0,
|
|
177
|
+
takerOwner,
|
|
178
|
+
makerOwner,
|
|
179
|
+
market,
|
|
180
|
+
strike: 1n,
|
|
181
|
+
quantity: 1n,
|
|
182
|
+
totalPremium: 1n,
|
|
183
|
+
orderId: new Uint8Array(32).fill(0x11),
|
|
184
|
+
reserved: Array(32).fill(0n),
|
|
185
|
+
});
|
|
186
|
+
expect(() => (0, fetch_1.decodeMarketAccount)(buffer_1.Buffer.from(positionData))).toThrow("Invalid Market account discriminator");
|
|
187
|
+
expect(() => (0, fetch_1.decodeMarketAccount)(buffer_1.Buffer.alloc(8))).toThrow("Invalid Market account size");
|
|
188
|
+
});
|
|
160
189
|
});
|
package/dist/cjs/ws/client.js
CHANGED
|
@@ -79,6 +79,7 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
79
79
|
authProvider = null;
|
|
80
80
|
authRequested = false;
|
|
81
81
|
startAuthSent = false;
|
|
82
|
+
pendingResumeSessionId = null;
|
|
82
83
|
connectionState = "disconnected";
|
|
83
84
|
sessionId = null;
|
|
84
85
|
helloSent = false;
|
|
@@ -122,22 +123,25 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
122
123
|
connectAnonymous() {
|
|
123
124
|
this.authProvider = null;
|
|
124
125
|
this.authRequested = false;
|
|
126
|
+
this.pendingResumeSessionId = null;
|
|
125
127
|
this.shouldReconnect = this.options.autoReconnect;
|
|
126
128
|
this.doConnect();
|
|
127
129
|
}
|
|
128
130
|
connect(authProvider) {
|
|
129
131
|
this.authProvider = authProvider;
|
|
130
132
|
this.authRequested = false;
|
|
133
|
+
this.pendingResumeSessionId = null;
|
|
131
134
|
this.shouldReconnect = this.options.autoReconnect;
|
|
132
135
|
this.doConnect();
|
|
133
136
|
}
|
|
134
|
-
connectAndAuthenticate(authProvider) {
|
|
137
|
+
connectAndAuthenticate(authProvider, options) {
|
|
135
138
|
this.authProvider = authProvider;
|
|
136
139
|
this.authRequested = true;
|
|
140
|
+
this.pendingResumeSessionId = options?.sessionId ?? null;
|
|
137
141
|
this.shouldReconnect = this.options.autoReconnect;
|
|
138
142
|
if (this.ws?.readyState === WS_OPEN) {
|
|
139
143
|
if (this.welcomeReceived && !this.startAuthSent) {
|
|
140
|
-
void this.
|
|
144
|
+
void this.beginAuthHandshake().catch((err) => {
|
|
141
145
|
this.emit("error", err);
|
|
142
146
|
this.setConnectionState("error");
|
|
143
147
|
});
|
|
@@ -149,6 +153,7 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
149
153
|
async authenticate(authProvider) {
|
|
150
154
|
this.authProvider = authProvider;
|
|
151
155
|
this.authRequested = true;
|
|
156
|
+
this.pendingResumeSessionId = null;
|
|
152
157
|
if (this.ws?.readyState === WS_OPEN) {
|
|
153
158
|
if (this.welcomeReceived && !this.startAuthSent) {
|
|
154
159
|
await this.sendStartAuth();
|
|
@@ -319,6 +324,10 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
319
324
|
this.send({ type: "Ping" });
|
|
320
325
|
}
|
|
321
326
|
}
|
|
327
|
+
resumeAuth(sessionId) {
|
|
328
|
+
this.pendingResumeSessionId = sessionId;
|
|
329
|
+
this.sendResumeAuth(sessionId);
|
|
330
|
+
}
|
|
322
331
|
doConnect() {
|
|
323
332
|
if (this.ws) {
|
|
324
333
|
this.cleanup();
|
|
@@ -379,7 +388,7 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
379
388
|
this.emit("welcome", message.data);
|
|
380
389
|
this.flushPendingMessages();
|
|
381
390
|
if (this.authRequested && this.authProvider && !this.startAuthSent) {
|
|
382
|
-
void this.
|
|
391
|
+
void this.beginAuthHandshake().catch((err) => {
|
|
383
392
|
this.emit("error", err);
|
|
384
393
|
this.setConnectionState("error");
|
|
385
394
|
});
|
|
@@ -404,10 +413,10 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
404
413
|
void this.handleAuthRequest(message.data.challenge);
|
|
405
414
|
break;
|
|
406
415
|
case "AuthSuccess":
|
|
407
|
-
this.handleAuthSuccess(message.data.session_id);
|
|
416
|
+
this.handleAuthSuccess(message.data.session_id, message.data.expires_at);
|
|
408
417
|
break;
|
|
409
418
|
case "AuthError":
|
|
410
|
-
this.handleAuthError(message.data.reason);
|
|
419
|
+
this.handleAuthError(message.data.reason, message.data.message);
|
|
411
420
|
break;
|
|
412
421
|
case "Snapshot":
|
|
413
422
|
this.handleSnapshot(message.data);
|
|
@@ -609,6 +618,13 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
609
618
|
this.setConnectionState("error");
|
|
610
619
|
}
|
|
611
620
|
}
|
|
621
|
+
async beginAuthHandshake() {
|
|
622
|
+
if (this.pendingResumeSessionId) {
|
|
623
|
+
this.sendResumeAuth(this.pendingResumeSessionId);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
await this.sendStartAuth();
|
|
627
|
+
}
|
|
612
628
|
async sendStartAuth() {
|
|
613
629
|
if (!this.authProvider)
|
|
614
630
|
throw new Error("No auth provider configured");
|
|
@@ -616,6 +632,10 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
616
632
|
const pubkey = await this.authProvider.getPublicKey();
|
|
617
633
|
this.send({ type: "StartAuth", data: { pubkey } });
|
|
618
634
|
}
|
|
635
|
+
sendResumeAuth(sessionId) {
|
|
636
|
+
this.startAuthSent = true;
|
|
637
|
+
this.send({ type: "ResumeAuth", data: { session_id: sessionId } });
|
|
638
|
+
}
|
|
619
639
|
sendHello() {
|
|
620
640
|
const hello = {
|
|
621
641
|
protocol_version: this.options.protocolVersion,
|
|
@@ -634,10 +654,11 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
634
654
|
this.send(msg);
|
|
635
655
|
}
|
|
636
656
|
}
|
|
637
|
-
handleAuthSuccess(sessionId) {
|
|
657
|
+
handleAuthSuccess(sessionId, expiresAt) {
|
|
638
658
|
this.sessionId = sessionId;
|
|
659
|
+
this.pendingResumeSessionId = null;
|
|
639
660
|
this.setConnectionState("authenticated");
|
|
640
|
-
this.emit("authenticated", sessionId);
|
|
661
|
+
this.emit("authenticated", sessionId, expiresAt);
|
|
641
662
|
if (this.subscribedChannels.size > 0) {
|
|
642
663
|
const channels = Array.from(this.subscribedChannels);
|
|
643
664
|
const data = this.hasMarketScope
|
|
@@ -646,9 +667,23 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
646
667
|
this.send({ type: "Subscribe", data });
|
|
647
668
|
}
|
|
648
669
|
}
|
|
649
|
-
handleAuthError(reason) {
|
|
670
|
+
handleAuthError(reason, message) {
|
|
671
|
+
this.emit("authError", reason);
|
|
672
|
+
if (reason === "session_expired" &&
|
|
673
|
+
this.authRequested &&
|
|
674
|
+
this.authProvider &&
|
|
675
|
+
this.pendingResumeSessionId) {
|
|
676
|
+
this.pendingResumeSessionId = null;
|
|
677
|
+
this.startAuthSent = false;
|
|
678
|
+
void this.sendStartAuth().catch((err) => {
|
|
679
|
+
this.emit("error", err);
|
|
680
|
+
this.setConnectionState("error");
|
|
681
|
+
});
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
650
684
|
this.setConnectionState("error");
|
|
651
|
-
|
|
685
|
+
const details = message ? `${reason}: ${message}` : reason;
|
|
686
|
+
this.emit("error", new Error(`Authentication failed: ${details}`));
|
|
652
687
|
}
|
|
653
688
|
handleSnapshot(snapshot) {
|
|
654
689
|
this.state.stats = snapshot.stats;
|
|
@@ -848,6 +883,7 @@ class ActaWsClient extends TypedEventEmitter {
|
|
|
848
883
|
this.helloSent = false;
|
|
849
884
|
this.welcomeReceived = false;
|
|
850
885
|
this.startAuthSent = false;
|
|
886
|
+
this.pendingResumeSessionId = null;
|
|
851
887
|
this.pendingMessages = [];
|
|
852
888
|
this.setConnectionState("disconnected");
|
|
853
889
|
}
|
|
@@ -121,6 +121,62 @@ describe("ActaWsClient", () => {
|
|
|
121
121
|
expect(authChallenge.data.challenge).toBe("challenge-text");
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
|
+
it("connectAndAuthenticate sends ResumeAuth when sessionId is provided", async () => {
|
|
125
|
+
const { client, socket } = makeHarness();
|
|
126
|
+
const auth = makeAuthProvider("WalletPubkey", "WalletSignature");
|
|
127
|
+
client.connectAndAuthenticate(auth, { sessionId: "resume-session-id" });
|
|
128
|
+
const ws = socket();
|
|
129
|
+
ws.triggerOpen();
|
|
130
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
131
|
+
await flushMicrotasks();
|
|
132
|
+
const sentTypes = ws.sent.map((payload) => parseClientMessage(payload).type);
|
|
133
|
+
expect(sentTypes).toEqual(["Hello", "ResumeAuth"]);
|
|
134
|
+
const resumeAuth = parseClientMessage(ws.sent[1]);
|
|
135
|
+
expect(resumeAuth.type).toBe("ResumeAuth");
|
|
136
|
+
if (resumeAuth.type === "ResumeAuth") {
|
|
137
|
+
expect(resumeAuth.data.session_id).toBe("resume-session-id");
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
it("falls back to StartAuth when ResumeAuth session_expired", async () => {
|
|
141
|
+
const { client, socket } = makeHarness();
|
|
142
|
+
const auth = makeAuthProvider("WalletPubkey", "WalletSignature");
|
|
143
|
+
client.connectAndAuthenticate(auth, { sessionId: "expired-session" });
|
|
144
|
+
const ws = socket();
|
|
145
|
+
ws.triggerOpen();
|
|
146
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
147
|
+
await flushMicrotasks();
|
|
148
|
+
ws.triggerMessage({
|
|
149
|
+
type: "AuthError",
|
|
150
|
+
data: { reason: "session_expired" },
|
|
151
|
+
});
|
|
152
|
+
await flushMicrotasks();
|
|
153
|
+
const sentTypes = ws.sent.map((payload) => parseClientMessage(payload).type);
|
|
154
|
+
expect(sentTypes).toEqual(["Hello", "ResumeAuth", "StartAuth"]);
|
|
155
|
+
});
|
|
156
|
+
it("emits authenticated with expiresAt from AuthSuccess", () => {
|
|
157
|
+
const { client } = makeHarness();
|
|
158
|
+
const seen = [];
|
|
159
|
+
client.on("authenticated", (sessionId, expiresAt) => {
|
|
160
|
+
seen.push({ sessionId, expiresAt });
|
|
161
|
+
});
|
|
162
|
+
client.handleMessage({
|
|
163
|
+
type: "AuthSuccess",
|
|
164
|
+
data: { session_id: "auth-session", expires_at: 1_710_086_400 },
|
|
165
|
+
});
|
|
166
|
+
expect(seen).toEqual([
|
|
167
|
+
{ sessionId: "auth-session", expiresAt: 1_710_086_400 },
|
|
168
|
+
]);
|
|
169
|
+
});
|
|
170
|
+
it("emits authError reason on AuthError", () => {
|
|
171
|
+
const { client } = makeHarness();
|
|
172
|
+
const reasons = [];
|
|
173
|
+
client.on("authError", (reason) => reasons.push(reason));
|
|
174
|
+
client.handleMessage({
|
|
175
|
+
type: "AuthError",
|
|
176
|
+
data: { reason: "invalid_signature", message: "bad signature bytes" },
|
|
177
|
+
});
|
|
178
|
+
expect(reasons).toEqual(["invalid_signature"]);
|
|
179
|
+
});
|
|
124
180
|
it("drop_oldest policy keeps the latest queued messages", () => {
|
|
125
181
|
const { client, socket } = makeHarness({
|
|
126
182
|
maxPendingMessages: 2,
|
package/dist/ws/client.d.ts
CHANGED
|
@@ -56,7 +56,8 @@ export type ActaWsClientEvents = {
|
|
|
56
56
|
connected: () => void;
|
|
57
57
|
welcome: (msg: WelcomeMessage) => void;
|
|
58
58
|
versionMismatch: (msg: VersionMismatchMessage) => void;
|
|
59
|
-
authenticated: (sessionId: string) => void;
|
|
59
|
+
authenticated: (sessionId: string, expiresAt: number | null) => void;
|
|
60
|
+
authError: (reason: string) => void;
|
|
60
61
|
disconnected: (code: number, reason: string) => void;
|
|
61
62
|
error: (error: Error) => void;
|
|
62
63
|
stateChange: (state: ConnectionState) => void;
|
|
@@ -147,6 +148,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
147
148
|
private authProvider;
|
|
148
149
|
private authRequested;
|
|
149
150
|
private startAuthSent;
|
|
151
|
+
private pendingResumeSessionId;
|
|
150
152
|
private connectionState;
|
|
151
153
|
private sessionId;
|
|
152
154
|
private helloSent;
|
|
@@ -163,7 +165,9 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
163
165
|
constructor(options: ActaWsClientOptions);
|
|
164
166
|
connectAnonymous(): void;
|
|
165
167
|
connect(authProvider: AuthProvider): void;
|
|
166
|
-
connectAndAuthenticate(authProvider: AuthProvider
|
|
168
|
+
connectAndAuthenticate(authProvider: AuthProvider, options?: {
|
|
169
|
+
sessionId?: string;
|
|
170
|
+
}): void;
|
|
167
171
|
authenticate(authProvider: AuthProvider): Promise<void>;
|
|
168
172
|
disconnect(): void;
|
|
169
173
|
getConnectionState(): ConnectionState;
|
|
@@ -228,6 +232,7 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
228
232
|
subscribe(channels: WsChannel[], markets?: string[]): void;
|
|
229
233
|
unsubscribe(channels: WsChannel[]): void;
|
|
230
234
|
ping(): void;
|
|
235
|
+
resumeAuth(sessionId: string): void;
|
|
231
236
|
private doConnect;
|
|
232
237
|
private handleMessage;
|
|
233
238
|
/** Taker-only: request current indicative prices for a market + position_type. */
|
|
@@ -235,7 +240,9 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
235
240
|
/** Maker-only: respond to an indicative request (unsigned, non-binding). */
|
|
236
241
|
sendIndicativePricesResponse(resp: IndicativePricesResponseMessage): void;
|
|
237
242
|
private handleAuthRequest;
|
|
243
|
+
private beginAuthHandshake;
|
|
238
244
|
private sendStartAuth;
|
|
245
|
+
private sendResumeAuth;
|
|
239
246
|
private sendHello;
|
|
240
247
|
private flushPendingMessages;
|
|
241
248
|
private handleAuthSuccess;
|
package/dist/ws/client.js
CHANGED
|
@@ -76,6 +76,7 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
76
76
|
authProvider = null;
|
|
77
77
|
authRequested = false;
|
|
78
78
|
startAuthSent = false;
|
|
79
|
+
pendingResumeSessionId = null;
|
|
79
80
|
connectionState = "disconnected";
|
|
80
81
|
sessionId = null;
|
|
81
82
|
helloSent = false;
|
|
@@ -119,22 +120,25 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
119
120
|
connectAnonymous() {
|
|
120
121
|
this.authProvider = null;
|
|
121
122
|
this.authRequested = false;
|
|
123
|
+
this.pendingResumeSessionId = null;
|
|
122
124
|
this.shouldReconnect = this.options.autoReconnect;
|
|
123
125
|
this.doConnect();
|
|
124
126
|
}
|
|
125
127
|
connect(authProvider) {
|
|
126
128
|
this.authProvider = authProvider;
|
|
127
129
|
this.authRequested = false;
|
|
130
|
+
this.pendingResumeSessionId = null;
|
|
128
131
|
this.shouldReconnect = this.options.autoReconnect;
|
|
129
132
|
this.doConnect();
|
|
130
133
|
}
|
|
131
|
-
connectAndAuthenticate(authProvider) {
|
|
134
|
+
connectAndAuthenticate(authProvider, options) {
|
|
132
135
|
this.authProvider = authProvider;
|
|
133
136
|
this.authRequested = true;
|
|
137
|
+
this.pendingResumeSessionId = options?.sessionId ?? null;
|
|
134
138
|
this.shouldReconnect = this.options.autoReconnect;
|
|
135
139
|
if (this.ws?.readyState === WS_OPEN) {
|
|
136
140
|
if (this.welcomeReceived && !this.startAuthSent) {
|
|
137
|
-
void this.
|
|
141
|
+
void this.beginAuthHandshake().catch((err) => {
|
|
138
142
|
this.emit("error", err);
|
|
139
143
|
this.setConnectionState("error");
|
|
140
144
|
});
|
|
@@ -146,6 +150,7 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
146
150
|
async authenticate(authProvider) {
|
|
147
151
|
this.authProvider = authProvider;
|
|
148
152
|
this.authRequested = true;
|
|
153
|
+
this.pendingResumeSessionId = null;
|
|
149
154
|
if (this.ws?.readyState === WS_OPEN) {
|
|
150
155
|
if (this.welcomeReceived && !this.startAuthSent) {
|
|
151
156
|
await this.sendStartAuth();
|
|
@@ -316,6 +321,10 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
316
321
|
this.send({ type: "Ping" });
|
|
317
322
|
}
|
|
318
323
|
}
|
|
324
|
+
resumeAuth(sessionId) {
|
|
325
|
+
this.pendingResumeSessionId = sessionId;
|
|
326
|
+
this.sendResumeAuth(sessionId);
|
|
327
|
+
}
|
|
319
328
|
doConnect() {
|
|
320
329
|
if (this.ws) {
|
|
321
330
|
this.cleanup();
|
|
@@ -376,7 +385,7 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
376
385
|
this.emit("welcome", message.data);
|
|
377
386
|
this.flushPendingMessages();
|
|
378
387
|
if (this.authRequested && this.authProvider && !this.startAuthSent) {
|
|
379
|
-
void this.
|
|
388
|
+
void this.beginAuthHandshake().catch((err) => {
|
|
380
389
|
this.emit("error", err);
|
|
381
390
|
this.setConnectionState("error");
|
|
382
391
|
});
|
|
@@ -401,10 +410,10 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
401
410
|
void this.handleAuthRequest(message.data.challenge);
|
|
402
411
|
break;
|
|
403
412
|
case "AuthSuccess":
|
|
404
|
-
this.handleAuthSuccess(message.data.session_id);
|
|
413
|
+
this.handleAuthSuccess(message.data.session_id, message.data.expires_at);
|
|
405
414
|
break;
|
|
406
415
|
case "AuthError":
|
|
407
|
-
this.handleAuthError(message.data.reason);
|
|
416
|
+
this.handleAuthError(message.data.reason, message.data.message);
|
|
408
417
|
break;
|
|
409
418
|
case "Snapshot":
|
|
410
419
|
this.handleSnapshot(message.data);
|
|
@@ -606,6 +615,13 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
606
615
|
this.setConnectionState("error");
|
|
607
616
|
}
|
|
608
617
|
}
|
|
618
|
+
async beginAuthHandshake() {
|
|
619
|
+
if (this.pendingResumeSessionId) {
|
|
620
|
+
this.sendResumeAuth(this.pendingResumeSessionId);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
await this.sendStartAuth();
|
|
624
|
+
}
|
|
609
625
|
async sendStartAuth() {
|
|
610
626
|
if (!this.authProvider)
|
|
611
627
|
throw new Error("No auth provider configured");
|
|
@@ -613,6 +629,10 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
613
629
|
const pubkey = await this.authProvider.getPublicKey();
|
|
614
630
|
this.send({ type: "StartAuth", data: { pubkey } });
|
|
615
631
|
}
|
|
632
|
+
sendResumeAuth(sessionId) {
|
|
633
|
+
this.startAuthSent = true;
|
|
634
|
+
this.send({ type: "ResumeAuth", data: { session_id: sessionId } });
|
|
635
|
+
}
|
|
616
636
|
sendHello() {
|
|
617
637
|
const hello = {
|
|
618
638
|
protocol_version: this.options.protocolVersion,
|
|
@@ -631,10 +651,11 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
631
651
|
this.send(msg);
|
|
632
652
|
}
|
|
633
653
|
}
|
|
634
|
-
handleAuthSuccess(sessionId) {
|
|
654
|
+
handleAuthSuccess(sessionId, expiresAt) {
|
|
635
655
|
this.sessionId = sessionId;
|
|
656
|
+
this.pendingResumeSessionId = null;
|
|
636
657
|
this.setConnectionState("authenticated");
|
|
637
|
-
this.emit("authenticated", sessionId);
|
|
658
|
+
this.emit("authenticated", sessionId, expiresAt);
|
|
638
659
|
if (this.subscribedChannels.size > 0) {
|
|
639
660
|
const channels = Array.from(this.subscribedChannels);
|
|
640
661
|
const data = this.hasMarketScope
|
|
@@ -643,9 +664,23 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
643
664
|
this.send({ type: "Subscribe", data });
|
|
644
665
|
}
|
|
645
666
|
}
|
|
646
|
-
handleAuthError(reason) {
|
|
667
|
+
handleAuthError(reason, message) {
|
|
668
|
+
this.emit("authError", reason);
|
|
669
|
+
if (reason === "session_expired" &&
|
|
670
|
+
this.authRequested &&
|
|
671
|
+
this.authProvider &&
|
|
672
|
+
this.pendingResumeSessionId) {
|
|
673
|
+
this.pendingResumeSessionId = null;
|
|
674
|
+
this.startAuthSent = false;
|
|
675
|
+
void this.sendStartAuth().catch((err) => {
|
|
676
|
+
this.emit("error", err);
|
|
677
|
+
this.setConnectionState("error");
|
|
678
|
+
});
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
647
681
|
this.setConnectionState("error");
|
|
648
|
-
|
|
682
|
+
const details = message ? `${reason}: ${message}` : reason;
|
|
683
|
+
this.emit("error", new Error(`Authentication failed: ${details}`));
|
|
649
684
|
}
|
|
650
685
|
handleSnapshot(snapshot) {
|
|
651
686
|
this.state.stats = snapshot.stats;
|
|
@@ -845,6 +880,7 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
845
880
|
this.helloSent = false;
|
|
846
881
|
this.welcomeReceived = false;
|
|
847
882
|
this.startAuthSent = false;
|
|
883
|
+
this.pendingResumeSessionId = null;
|
|
848
884
|
this.pendingMessages = [];
|
|
849
885
|
this.setConnectionState("disconnected");
|
|
850
886
|
}
|
package/dist/ws/client.test.js
CHANGED
|
@@ -119,6 +119,62 @@ describe("ActaWsClient", () => {
|
|
|
119
119
|
expect(authChallenge.data.challenge).toBe("challenge-text");
|
|
120
120
|
}
|
|
121
121
|
});
|
|
122
|
+
it("connectAndAuthenticate sends ResumeAuth when sessionId is provided", async () => {
|
|
123
|
+
const { client, socket } = makeHarness();
|
|
124
|
+
const auth = makeAuthProvider("WalletPubkey", "WalletSignature");
|
|
125
|
+
client.connectAndAuthenticate(auth, { sessionId: "resume-session-id" });
|
|
126
|
+
const ws = socket();
|
|
127
|
+
ws.triggerOpen();
|
|
128
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
129
|
+
await flushMicrotasks();
|
|
130
|
+
const sentTypes = ws.sent.map((payload) => parseClientMessage(payload).type);
|
|
131
|
+
expect(sentTypes).toEqual(["Hello", "ResumeAuth"]);
|
|
132
|
+
const resumeAuth = parseClientMessage(ws.sent[1]);
|
|
133
|
+
expect(resumeAuth.type).toBe("ResumeAuth");
|
|
134
|
+
if (resumeAuth.type === "ResumeAuth") {
|
|
135
|
+
expect(resumeAuth.data.session_id).toBe("resume-session-id");
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
it("falls back to StartAuth when ResumeAuth session_expired", async () => {
|
|
139
|
+
const { client, socket } = makeHarness();
|
|
140
|
+
const auth = makeAuthProvider("WalletPubkey", "WalletSignature");
|
|
141
|
+
client.connectAndAuthenticate(auth, { sessionId: "expired-session" });
|
|
142
|
+
const ws = socket();
|
|
143
|
+
ws.triggerOpen();
|
|
144
|
+
ws.triggerMessage(WELCOME_MESSAGE);
|
|
145
|
+
await flushMicrotasks();
|
|
146
|
+
ws.triggerMessage({
|
|
147
|
+
type: "AuthError",
|
|
148
|
+
data: { reason: "session_expired" },
|
|
149
|
+
});
|
|
150
|
+
await flushMicrotasks();
|
|
151
|
+
const sentTypes = ws.sent.map((payload) => parseClientMessage(payload).type);
|
|
152
|
+
expect(sentTypes).toEqual(["Hello", "ResumeAuth", "StartAuth"]);
|
|
153
|
+
});
|
|
154
|
+
it("emits authenticated with expiresAt from AuthSuccess", () => {
|
|
155
|
+
const { client } = makeHarness();
|
|
156
|
+
const seen = [];
|
|
157
|
+
client.on("authenticated", (sessionId, expiresAt) => {
|
|
158
|
+
seen.push({ sessionId, expiresAt });
|
|
159
|
+
});
|
|
160
|
+
client.handleMessage({
|
|
161
|
+
type: "AuthSuccess",
|
|
162
|
+
data: { session_id: "auth-session", expires_at: 1_710_086_400 },
|
|
163
|
+
});
|
|
164
|
+
expect(seen).toEqual([
|
|
165
|
+
{ sessionId: "auth-session", expiresAt: 1_710_086_400 },
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
168
|
+
it("emits authError reason on AuthError", () => {
|
|
169
|
+
const { client } = makeHarness();
|
|
170
|
+
const reasons = [];
|
|
171
|
+
client.on("authError", (reason) => reasons.push(reason));
|
|
172
|
+
client.handleMessage({
|
|
173
|
+
type: "AuthError",
|
|
174
|
+
data: { reason: "invalid_signature", message: "bad signature bytes" },
|
|
175
|
+
});
|
|
176
|
+
expect(reasons).toEqual(["invalid_signature"]);
|
|
177
|
+
});
|
|
122
178
|
it("drop_oldest policy keeps the latest queued messages", () => {
|
|
123
179
|
const { client, socket } = makeHarness({
|
|
124
180
|
maxPendingMessages: 2,
|
package/dist/ws/types.d.ts
CHANGED
|
@@ -78,6 +78,11 @@ export type ClientMessage = {
|
|
|
78
78
|
data: {
|
|
79
79
|
pubkey: string;
|
|
80
80
|
};
|
|
81
|
+
} | {
|
|
82
|
+
type: "ResumeAuth";
|
|
83
|
+
data: {
|
|
84
|
+
session_id: string;
|
|
85
|
+
};
|
|
81
86
|
} | {
|
|
82
87
|
type: "AuthChallenge";
|
|
83
88
|
data: AuthChallengeData;
|
|
@@ -255,11 +260,13 @@ export type ServerMessage = {
|
|
|
255
260
|
type: "AuthSuccess";
|
|
256
261
|
data: {
|
|
257
262
|
session_id: string;
|
|
263
|
+
expires_at: WsI64 | null;
|
|
258
264
|
};
|
|
259
265
|
} | {
|
|
260
266
|
type: "AuthError";
|
|
261
267
|
data: {
|
|
262
268
|
reason: ErrorMessage;
|
|
269
|
+
message?: string;
|
|
263
270
|
};
|
|
264
271
|
} | {
|
|
265
272
|
type: "RfqCreated";
|
|
@@ -628,6 +635,8 @@ export type MakerPositionInfo = {
|
|
|
628
635
|
strike: WsU64;
|
|
629
636
|
quantity: WsU64;
|
|
630
637
|
price: WsU64;
|
|
638
|
+
/** Net premium amount from on-chain position state (quote token base units). */
|
|
639
|
+
total_premium: WsU64;
|
|
631
640
|
collateral_locked: WsU64;
|
|
632
641
|
created_at: WsU64;
|
|
633
642
|
expiry_ts: WsU64;
|
|
@@ -813,6 +822,8 @@ export type PositionInfo = {
|
|
|
813
822
|
strike: WsU64;
|
|
814
823
|
quantity: WsU64;
|
|
815
824
|
price: WsU64;
|
|
825
|
+
/** Net premium amount from on-chain position state (quote token base units). */
|
|
826
|
+
total_premium: WsU64;
|
|
816
827
|
created_at: WsU64;
|
|
817
828
|
};
|
|
818
829
|
export type TradeInfo = {
|