@acta-markets/ts-sdk 0.0.20-beta → 0.0.22-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/cjs/generated/errors/actaContract.js +4 -1
- package/dist/cjs/idl/acta_contract.json +5 -0
- package/dist/cjs/idl/hash.js +1 -1
- package/dist/cjs/ws/auth.js +14 -11
- package/dist/cjs/ws/client.js +225 -16
- package/dist/cjs/ws/client.test.js +1 -1
- package/dist/cjs/ws/index.js +1 -0
- package/dist/cjs/ws/referral.js +57 -0
- package/dist/cjs/ws/referral.test.js +55 -0
- package/dist/generated/errors/actaContract.d.ts +3 -1
- package/dist/generated/errors/actaContract.js +3 -0
- package/dist/idl/acta_contract.json +5 -0
- package/dist/idl/hash.d.ts +1 -1
- package/dist/idl/hash.js +1 -1
- package/dist/ws/auth.d.ts +10 -7
- package/dist/ws/auth.js +14 -11
- package/dist/ws/client.d.ts +76 -4
- package/dist/ws/client.js +225 -16
- package/dist/ws/client.test.js +1 -1
- package/dist/ws/index.d.ts +1 -0
- package/dist/ws/index.js +1 -0
- package/dist/ws/referral.d.ts +53 -0
- package/dist/ws/referral.js +51 -0
- package/dist/ws/referral.test.d.ts +1 -0
- package/dist/ws/referral.test.js +53 -0
- package/dist/ws/types.d.ts +158 -50
- package/package.json +1 -1
package/dist/ws/auth.js
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* - Client signs UTF-8 bytes of that text and responds:
|
|
7
7
|
* `AuthChallenge { challenge, signature: base58(ed25519(utf8(challenge))), pubkey }`
|
|
8
8
|
*
|
|
9
|
+
* The same `signMessage` primitive is reused for any UTF-8-bytes signing
|
|
10
|
+
* (e.g. `acta:redeem:v1:{pubkey}:{code}` for invite redemption).
|
|
11
|
+
*
|
|
9
12
|
* Source of truth (server):
|
|
10
13
|
* - rust-backend/rfq-server/src/server/ws.rs
|
|
11
14
|
* - rust-backend/rfq-server/src/session/handler.rs
|
|
@@ -49,9 +52,9 @@ export class KeypairAuthProvider {
|
|
|
49
52
|
async getPublicKey() {
|
|
50
53
|
return this.address;
|
|
51
54
|
}
|
|
52
|
-
async
|
|
53
|
-
const
|
|
54
|
-
const signatureBytes = await signBytes(this.privateKey,
|
|
55
|
+
async signMessage(message) {
|
|
56
|
+
const messageBytes = utf8ToBytes(message);
|
|
57
|
+
const signatureBytes = await signBytes(this.privateKey, messageBytes);
|
|
55
58
|
return bytesToBase58(signatureBytes);
|
|
56
59
|
}
|
|
57
60
|
}
|
|
@@ -72,10 +75,10 @@ export class WalletAuthProvider {
|
|
|
72
75
|
throw new Error("Wallet not connected (missing public key)");
|
|
73
76
|
return pk;
|
|
74
77
|
}
|
|
75
|
-
async
|
|
76
|
-
const
|
|
78
|
+
async signMessage(message) {
|
|
79
|
+
const messageBytes = utf8ToBytes(message);
|
|
77
80
|
// Some wallet APIs expect a mutable `Uint8Array`; codecs return `ReadonlyUint8Array`.
|
|
78
|
-
const sig = await this.wallet.signMessage(new Uint8Array(
|
|
81
|
+
const sig = await this.wallet.signMessage(new Uint8Array(messageBytes));
|
|
79
82
|
return bytesToBase58(sig);
|
|
80
83
|
}
|
|
81
84
|
}
|
|
@@ -84,15 +87,15 @@ export class WalletAuthProvider {
|
|
|
84
87
|
*/
|
|
85
88
|
export class CustomAuthProvider {
|
|
86
89
|
getPublicKeyFn;
|
|
87
|
-
|
|
88
|
-
constructor(getPublicKey,
|
|
90
|
+
signMessageFn;
|
|
91
|
+
constructor(getPublicKey, signMessage) {
|
|
89
92
|
this.getPublicKeyFn = getPublicKey;
|
|
90
|
-
this.
|
|
93
|
+
this.signMessageFn = signMessage;
|
|
91
94
|
}
|
|
92
95
|
async getPublicKey() {
|
|
93
96
|
return this.getPublicKeyFn();
|
|
94
97
|
}
|
|
95
|
-
async
|
|
96
|
-
return this.
|
|
98
|
+
async signMessage(message) {
|
|
99
|
+
return this.signMessageFn(message);
|
|
97
100
|
}
|
|
98
101
|
}
|
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, EarnSummaryData, TokenMarketsInfoData, GlobalStats, MarketDescriptorInfo, MarketInfo, MyActiveRfqInfo, MyActiveRfqsMessage, OrderStatusMessage, PositionInfo, QuoteAcknowledgedMessage, QuoteBestStatusMessage, QuoteCancelledMessage, QuoteMessage, QuoteRefreshRequestedMessage, QuoteOutbidMessage, QuoteReceivedMessage, QuoteSelectedMessage, QuotesUpdateMessage, RfqBroadcastMessage, RfqClosedMessage, RfqCreatedMessage, RfqSkippedMessage, RfqRequestMessage, RfqAvailableAgainMessage, QuoteExpiredMessage, QuoteFilledMessage, IndicativePricesMessage, IndicativePricesRequestMessage, IndicativePricesResponseMessage, GetIndicativePricesMessage, RequestId, ServerError, ServerMessage, SnapshotMessage, StatsDelta, SubscriptionsMessage, TokenInfo, TradeInfo, UuidString, VersionMismatchMessage, WelcomeMessage, WsChannel } from "./types";
|
|
5
|
+
import type { ActiveRfqInfo, ChainEventMessage, EarnSummaryData, TokenMarketsInfoData, GlobalStats, MarketDescriptorInfo, MarketInfo, MyActiveRfqInfo, MyActiveRfqsMessage, OrderStatusMessage, PositionInfo, QuoteAcknowledgedMessage, QuoteBestStatusMessage, QuoteCancelledMessage, QuoteMessage, QuoteRefreshRequestedMessage, QuoteOutbidMessage, QuoteReceivedMessage, QuoteSelectedMessage, QuotesUpdateMessage, RfqBroadcastMessage, RfqClosedMessage, RfqCreatedMessage, RfqSkippedMessage, RfqRequestMessage, RfqAvailableAgainMessage, QuoteExpiredMessage, QuoteFilledMessage, IndicativePricesMessage, IndicativePricesRequestMessage, IndicativePricesResponseMessage, GetIndicativePricesMessage, MakerBalancesMessage, MakerMarketsMessage, MakerPositionsMessage, MyCapsMessage, MyQuotesMessage, MyTradesMessage, RequestId, ServerError, ServerMessage, SnapshotMessage, StatsDelta, SubscriptionsMessage, TokenInfo, TradeInfo, UuidString, VersionMismatchMessage, WelcomeMessage, WsChannel, InviteRedeemedData, ReferralCodeClaimedData, MyReferralInfoData } 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";
|
|
@@ -88,6 +88,12 @@ export type ActaWsClientEvents = {
|
|
|
88
88
|
earnSummary: (data: EarnSummaryData) => void;
|
|
89
89
|
tokenMarketsInfo: (data: TokenMarketsInfoData) => void;
|
|
90
90
|
subscriptions: (msg: SubscriptionsMessage) => void;
|
|
91
|
+
subscriptionUpdated: (data: {
|
|
92
|
+
request_id: RequestId;
|
|
93
|
+
channels: WsChannel[];
|
|
94
|
+
underlying_mints?: string[];
|
|
95
|
+
quote_mints?: string[];
|
|
96
|
+
}) => void;
|
|
91
97
|
activeRfqs: (rfqs: ActiveRfqInfo[]) => void;
|
|
92
98
|
rfqBroadcast: (rfq: RfqBroadcastMessage) => void;
|
|
93
99
|
rfqSkipped: (msg: RfqSkippedMessage) => void;
|
|
@@ -106,6 +112,12 @@ export type ActaWsClientEvents = {
|
|
|
106
112
|
quoteExpired: (msg: QuoteExpiredMessage) => void;
|
|
107
113
|
indicativePrices: (msg: IndicativePricesMessage) => void;
|
|
108
114
|
indicativePricesRequest: (msg: IndicativePricesRequestMessage) => void;
|
|
115
|
+
makerBalances: (msg: MakerBalancesMessage) => void;
|
|
116
|
+
makerPositions: (msg: MakerPositionsMessage) => void;
|
|
117
|
+
myTrades: (msg: MyTradesMessage) => void;
|
|
118
|
+
myCaps: (msg: MyCapsMessage) => void;
|
|
119
|
+
makerMarkets: (msg: MakerMarketsMessage) => void;
|
|
120
|
+
myQuotes: (msg: MyQuotesMessage) => void;
|
|
109
121
|
myActiveRfqs: (msg: MyActiveRfqsMessage) => void;
|
|
110
122
|
orderStatus: (msg: OrderStatusMessage) => void;
|
|
111
123
|
orderAccepted: (orderId: string) => void;
|
|
@@ -133,6 +145,10 @@ export type ActaWsClientEvents = {
|
|
|
133
145
|
positionSettled: (event: Extract<ChainEventMessage, {
|
|
134
146
|
event_type: "PositionSettled";
|
|
135
147
|
}>) => void;
|
|
148
|
+
requireInvite: () => void;
|
|
149
|
+
inviteRedeemed: (data: InviteRedeemedData) => void;
|
|
150
|
+
referralCodeClaimed: (data: ReferralCodeClaimedData) => void;
|
|
151
|
+
myReferralInfo: (data: MyReferralInfoData) => void;
|
|
136
152
|
};
|
|
137
153
|
type EventMap = Record<string, (...args: any[]) => void>;
|
|
138
154
|
declare class TypedEventEmitter<TEvents extends EventMap> {
|
|
@@ -176,8 +192,8 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
176
192
|
private pingTimer;
|
|
177
193
|
private shouldReconnect;
|
|
178
194
|
private subscribedChannels;
|
|
179
|
-
private
|
|
180
|
-
private
|
|
195
|
+
private underlyingMintScope;
|
|
196
|
+
private quoteMintScope;
|
|
181
197
|
private marketDescriptorsByMarket;
|
|
182
198
|
readonly state: ClientState;
|
|
183
199
|
constructor(options: ActaWsClientOptions);
|
|
@@ -233,7 +249,50 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
233
249
|
}): RequestId;
|
|
234
250
|
getEarnSummary(): RequestId;
|
|
235
251
|
getTokenMarketsInfo(underlyingMint: string): RequestId;
|
|
252
|
+
/** Maker-only: get balances per deposited token (total, locked, available). */
|
|
253
|
+
getMakerBalances(): RequestId;
|
|
254
|
+
/** Maker-only: get open positions with optional filters. */
|
|
255
|
+
getMakerPositions(args?: {
|
|
256
|
+
market?: string;
|
|
257
|
+
underlying_mint?: string;
|
|
258
|
+
status?: string[];
|
|
259
|
+
min_expiry_ts?: number;
|
|
260
|
+
}): RequestId;
|
|
261
|
+
/** Maker-only: get trade history with keyset pagination. */
|
|
262
|
+
getMyTrades(args?: {
|
|
263
|
+
limit?: number;
|
|
264
|
+
cursor?: number;
|
|
265
|
+
cursor_id?: string;
|
|
266
|
+
market?: string;
|
|
267
|
+
}): RequestId;
|
|
268
|
+
/** Maker-only: get position, notional, and balance caps. */
|
|
269
|
+
getMyCaps(): RequestId;
|
|
270
|
+
/** Maker-only: get markets where maker has deposits, with optional filters and stats. */
|
|
271
|
+
getMarketsForMaker(args?: {
|
|
272
|
+
underlying_mints?: string[];
|
|
273
|
+
quote_mints?: string[];
|
|
274
|
+
min_expiry_ts?: number;
|
|
275
|
+
max_expiry_ts?: number;
|
|
276
|
+
is_put?: boolean;
|
|
277
|
+
include_stats?: boolean;
|
|
278
|
+
}): RequestId;
|
|
279
|
+
/** Maker-only: get submitted quotes with status. */
|
|
280
|
+
getMyQuotes(args?: {
|
|
281
|
+
active_only?: boolean;
|
|
282
|
+
}): RequestId;
|
|
236
283
|
logout(): void;
|
|
284
|
+
/**
|
|
285
|
+
* Redeem an invite code. Authentication is proven by the session;
|
|
286
|
+
* no additional signature is required. Validation runs client-side —
|
|
287
|
+
* invalid inputs throw `ReferralCodeError` without a round-trip.
|
|
288
|
+
*/
|
|
289
|
+
redeemInvite(rawCode: string): RequestId;
|
|
290
|
+
/**
|
|
291
|
+
* Claim a vanity referral code (one-shot per taker). Validation
|
|
292
|
+
* runs client-side — invalid inputs throw `ReferralCodeError`.
|
|
293
|
+
*/
|
|
294
|
+
claimReferralCode(rawCode: string): Promise<RequestId>;
|
|
295
|
+
getMyReferralInfo(): RequestId;
|
|
237
296
|
getOrderStatus(orderIdHex: string): RequestId;
|
|
238
297
|
cancelRfq(rfqId: string): RequestId;
|
|
239
298
|
submitQuote(quote: QuoteMessage): void;
|
|
@@ -253,8 +312,21 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
253
312
|
makerSigner: SignerLike;
|
|
254
313
|
}): Promise<void>;
|
|
255
314
|
cancelQuote(rfqId: string): RequestId;
|
|
256
|
-
subscribe(channels: WsChannel[],
|
|
315
|
+
subscribe(channels: WsChannel[], opts?: {
|
|
316
|
+
underlying_mints?: string[];
|
|
317
|
+
quote_mints?: string[];
|
|
318
|
+
}): RequestId;
|
|
257
319
|
unsubscribe(channels: WsChannel[]): RequestId;
|
|
320
|
+
addMints(opts: {
|
|
321
|
+
underlying_mints?: string[];
|
|
322
|
+
quote_mints?: string[];
|
|
323
|
+
}): RequestId;
|
|
324
|
+
removeMints(opts: {
|
|
325
|
+
underlying_mints?: string[];
|
|
326
|
+
quote_mints?: string[];
|
|
327
|
+
}): RequestId;
|
|
328
|
+
addChannels(channels: WsChannel[]): RequestId;
|
|
329
|
+
removeChannels(channels: WsChannel[]): RequestId;
|
|
258
330
|
ping(): void;
|
|
259
331
|
resumeAuth(sessionId: string): void;
|
|
260
332
|
private doConnect;
|
package/dist/ws/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Acta WebSocket client (rfq-server). */
|
|
2
2
|
import { buildSignedQuoteMessage, buildAcceptQuoteMessage } from "./flows";
|
|
3
3
|
import { assertWsU64Safe, validateQuantityBySizeRule } from "./wirePolicy";
|
|
4
|
+
import { parseReferralCode, ReferralCodeError } from "./referral";
|
|
4
5
|
function toGenericServerError(err) {
|
|
5
6
|
const message = err instanceof Error ? err.message : String(err);
|
|
6
7
|
return { type: "generic", data: { code: "client_error", message } };
|
|
@@ -107,8 +108,8 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
107
108
|
pingTimer = null;
|
|
108
109
|
shouldReconnect = true;
|
|
109
110
|
subscribedChannels = new Set(["rfqs"]);
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
underlyingMintScope = null; // null = all
|
|
112
|
+
quoteMintScope = null; // null = all
|
|
112
113
|
marketDescriptorsByMarket = new Map();
|
|
113
114
|
state = {
|
|
114
115
|
stats: null,
|
|
@@ -336,9 +337,118 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
336
337
|
});
|
|
337
338
|
return requestId;
|
|
338
339
|
}
|
|
340
|
+
/** Maker-only: get balances per deposited token (total, locked, available). */
|
|
341
|
+
getMakerBalances() {
|
|
342
|
+
this.ensureAuthenticated();
|
|
343
|
+
const requestId = this.nextRequestId();
|
|
344
|
+
this.send({
|
|
345
|
+
type: "GetMakerBalances",
|
|
346
|
+
data: { request_id: requestId },
|
|
347
|
+
});
|
|
348
|
+
return requestId;
|
|
349
|
+
}
|
|
350
|
+
/** Maker-only: get open positions with optional filters. */
|
|
351
|
+
getMakerPositions(args) {
|
|
352
|
+
this.ensureAuthenticated();
|
|
353
|
+
const requestId = this.nextRequestId();
|
|
354
|
+
const data = {
|
|
355
|
+
request_id: requestId,
|
|
356
|
+
...args,
|
|
357
|
+
};
|
|
358
|
+
this.send({ type: "GetMakerPositions", data });
|
|
359
|
+
return requestId;
|
|
360
|
+
}
|
|
361
|
+
/** Maker-only: get trade history with keyset pagination. */
|
|
362
|
+
getMyTrades(args) {
|
|
363
|
+
this.ensureAuthenticated();
|
|
364
|
+
const requestId = this.nextRequestId();
|
|
365
|
+
const data = {
|
|
366
|
+
request_id: requestId,
|
|
367
|
+
...args,
|
|
368
|
+
};
|
|
369
|
+
this.send({ type: "GetMyTrades", data });
|
|
370
|
+
return requestId;
|
|
371
|
+
}
|
|
372
|
+
/** Maker-only: get position, notional, and balance caps. */
|
|
373
|
+
getMyCaps() {
|
|
374
|
+
this.ensureAuthenticated();
|
|
375
|
+
const requestId = this.nextRequestId();
|
|
376
|
+
this.send({
|
|
377
|
+
type: "GetMyCaps",
|
|
378
|
+
data: { request_id: requestId },
|
|
379
|
+
});
|
|
380
|
+
return requestId;
|
|
381
|
+
}
|
|
382
|
+
/** Maker-only: get markets where maker has deposits, with optional filters and stats. */
|
|
383
|
+
getMarketsForMaker(args) {
|
|
384
|
+
this.ensureAuthenticated();
|
|
385
|
+
const requestId = this.nextRequestId();
|
|
386
|
+
const data = {
|
|
387
|
+
request_id: requestId,
|
|
388
|
+
...args,
|
|
389
|
+
};
|
|
390
|
+
this.send({ type: "GetMarketsForMaker", data });
|
|
391
|
+
return requestId;
|
|
392
|
+
}
|
|
393
|
+
/** Maker-only: get submitted quotes with status. */
|
|
394
|
+
getMyQuotes(args) {
|
|
395
|
+
this.ensureAuthenticated();
|
|
396
|
+
const requestId = this.nextRequestId();
|
|
397
|
+
const data = {
|
|
398
|
+
request_id: requestId,
|
|
399
|
+
active_only: args?.active_only ?? true,
|
|
400
|
+
};
|
|
401
|
+
this.send({ type: "GetMyQuotes", data });
|
|
402
|
+
return requestId;
|
|
403
|
+
}
|
|
339
404
|
logout() {
|
|
340
405
|
this.send({ type: "Logout" });
|
|
341
406
|
}
|
|
407
|
+
// ========================================================================
|
|
408
|
+
// Referral / invite
|
|
409
|
+
// ========================================================================
|
|
410
|
+
/**
|
|
411
|
+
* Redeem an invite code. Authentication is proven by the session;
|
|
412
|
+
* no additional signature is required. Validation runs client-side —
|
|
413
|
+
* invalid inputs throw `ReferralCodeError` without a round-trip.
|
|
414
|
+
*/
|
|
415
|
+
redeemInvite(rawCode) {
|
|
416
|
+
this.ensureAuthenticated();
|
|
417
|
+
const parsed = parseReferralCode(rawCode);
|
|
418
|
+
if (!parsed.ok)
|
|
419
|
+
throw new ReferralCodeError(parsed.error);
|
|
420
|
+
const requestId = this.nextRequestId();
|
|
421
|
+
this.send({
|
|
422
|
+
type: "RedeemInvite",
|
|
423
|
+
data: { request_id: requestId, code: parsed.code },
|
|
424
|
+
});
|
|
425
|
+
return requestId;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Claim a vanity referral code (one-shot per taker). Validation
|
|
429
|
+
* runs client-side — invalid inputs throw `ReferralCodeError`.
|
|
430
|
+
*/
|
|
431
|
+
async claimReferralCode(rawCode) {
|
|
432
|
+
this.ensureAuthenticated();
|
|
433
|
+
const parsed = parseReferralCode(rawCode);
|
|
434
|
+
if (!parsed.ok)
|
|
435
|
+
throw new ReferralCodeError(parsed.error);
|
|
436
|
+
const requestId = this.nextRequestId();
|
|
437
|
+
this.send({
|
|
438
|
+
type: "ClaimReferralCode",
|
|
439
|
+
data: { request_id: requestId, code: parsed.code },
|
|
440
|
+
});
|
|
441
|
+
return requestId;
|
|
442
|
+
}
|
|
443
|
+
getMyReferralInfo() {
|
|
444
|
+
this.ensureAuthenticated();
|
|
445
|
+
const requestId = this.nextRequestId();
|
|
446
|
+
this.send({
|
|
447
|
+
type: "GetMyReferralInfo",
|
|
448
|
+
data: { request_id: requestId },
|
|
449
|
+
});
|
|
450
|
+
return requestId;
|
|
451
|
+
}
|
|
342
452
|
getOrderStatus(orderIdHex) {
|
|
343
453
|
this.ensureAuthenticated();
|
|
344
454
|
const requestId = this.nextRequestId();
|
|
@@ -392,21 +502,25 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
392
502
|
});
|
|
393
503
|
return requestId;
|
|
394
504
|
}
|
|
395
|
-
subscribe(channels,
|
|
505
|
+
subscribe(channels, opts) {
|
|
396
506
|
this.ensureAuthenticated();
|
|
397
507
|
const request_id = this.nextRequestId();
|
|
398
508
|
for (const c of channels)
|
|
399
509
|
this.subscribedChannels.add(c);
|
|
400
|
-
if (
|
|
401
|
-
this.
|
|
402
|
-
this.subscribedMarkets = new Set(markets);
|
|
510
|
+
if (opts?.underlying_mints) {
|
|
511
|
+
this.underlyingMintScope = new Set(opts.underlying_mints);
|
|
403
512
|
}
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
513
|
+
if (opts?.quote_mints) {
|
|
514
|
+
this.quoteMintScope = new Set(opts.quote_mints);
|
|
515
|
+
}
|
|
516
|
+
const data = { request_id, channels };
|
|
517
|
+
if (this.underlyingMintScope) {
|
|
518
|
+
data.underlying_mints = Array.from(this.underlyingMintScope);
|
|
519
|
+
}
|
|
520
|
+
if (this.quoteMintScope) {
|
|
521
|
+
data.quote_mints = Array.from(this.quoteMintScope);
|
|
522
|
+
}
|
|
523
|
+
this.send({ type: "Subscribe", data });
|
|
410
524
|
return request_id;
|
|
411
525
|
}
|
|
412
526
|
unsubscribe(channels) {
|
|
@@ -420,6 +534,58 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
420
534
|
});
|
|
421
535
|
return request_id;
|
|
422
536
|
}
|
|
537
|
+
addMints(opts) {
|
|
538
|
+
this.ensureAuthenticated();
|
|
539
|
+
const request_id = this.nextRequestId();
|
|
540
|
+
if (opts.underlying_mints) {
|
|
541
|
+
if (!this.underlyingMintScope)
|
|
542
|
+
this.underlyingMintScope = new Set();
|
|
543
|
+
for (const m of opts.underlying_mints)
|
|
544
|
+
this.underlyingMintScope.add(m);
|
|
545
|
+
}
|
|
546
|
+
if (opts.quote_mints) {
|
|
547
|
+
if (!this.quoteMintScope)
|
|
548
|
+
this.quoteMintScope = new Set();
|
|
549
|
+
for (const m of opts.quote_mints)
|
|
550
|
+
this.quoteMintScope.add(m);
|
|
551
|
+
}
|
|
552
|
+
this.send({ type: "AddMints", data: { request_id, ...opts } });
|
|
553
|
+
return request_id;
|
|
554
|
+
}
|
|
555
|
+
removeMints(opts) {
|
|
556
|
+
this.ensureAuthenticated();
|
|
557
|
+
const request_id = this.nextRequestId();
|
|
558
|
+
if (opts.underlying_mints && this.underlyingMintScope) {
|
|
559
|
+
for (const m of opts.underlying_mints)
|
|
560
|
+
this.underlyingMintScope.delete(m);
|
|
561
|
+
if (this.underlyingMintScope.size === 0)
|
|
562
|
+
this.underlyingMintScope = null;
|
|
563
|
+
}
|
|
564
|
+
if (opts.quote_mints && this.quoteMintScope) {
|
|
565
|
+
for (const m of opts.quote_mints)
|
|
566
|
+
this.quoteMintScope.delete(m);
|
|
567
|
+
if (this.quoteMintScope.size === 0)
|
|
568
|
+
this.quoteMintScope = null;
|
|
569
|
+
}
|
|
570
|
+
this.send({ type: "RemoveMints", data: { request_id, ...opts } });
|
|
571
|
+
return request_id;
|
|
572
|
+
}
|
|
573
|
+
addChannels(channels) {
|
|
574
|
+
this.ensureAuthenticated();
|
|
575
|
+
const request_id = this.nextRequestId();
|
|
576
|
+
for (const c of channels)
|
|
577
|
+
this.subscribedChannels.add(c);
|
|
578
|
+
this.send({ type: "AddChannels", data: { request_id, channels } });
|
|
579
|
+
return request_id;
|
|
580
|
+
}
|
|
581
|
+
removeChannels(channels) {
|
|
582
|
+
this.ensureAuthenticated();
|
|
583
|
+
const request_id = this.nextRequestId();
|
|
584
|
+
for (const c of channels)
|
|
585
|
+
this.subscribedChannels.delete(c);
|
|
586
|
+
this.send({ type: "RemoveChannels", data: { request_id, channels } });
|
|
587
|
+
return request_id;
|
|
588
|
+
}
|
|
423
589
|
ping() {
|
|
424
590
|
if (this.ws?.readyState === WS_OPEN) {
|
|
425
591
|
this.send({ type: "Ping" });
|
|
@@ -570,6 +736,21 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
570
736
|
case "MyCaps":
|
|
571
737
|
this.emit("myCaps", message.data);
|
|
572
738
|
break;
|
|
739
|
+
case "MakerBalances":
|
|
740
|
+
this.emit("makerBalances", message.data);
|
|
741
|
+
break;
|
|
742
|
+
case "MakerPositions":
|
|
743
|
+
this.emit("makerPositions", message.data);
|
|
744
|
+
break;
|
|
745
|
+
case "MyTrades":
|
|
746
|
+
this.emit("myTrades", message.data);
|
|
747
|
+
break;
|
|
748
|
+
case "MakerMarkets":
|
|
749
|
+
this.emit("makerMarkets", message.data);
|
|
750
|
+
break;
|
|
751
|
+
case "MyQuotes":
|
|
752
|
+
this.emit("myQuotes", message.data);
|
|
753
|
+
break;
|
|
573
754
|
case "MyActiveRfqs":
|
|
574
755
|
this.handleMyActiveRfqs(message.data);
|
|
575
756
|
break;
|
|
@@ -727,6 +908,30 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
727
908
|
case "UnsubscribeAck":
|
|
728
909
|
this.emit("unsubscribeAck", message.data);
|
|
729
910
|
break;
|
|
911
|
+
case "SubscriptionUpdated":
|
|
912
|
+
{
|
|
913
|
+
const d = message.data;
|
|
914
|
+
// Sync local state from the server's authoritative response.
|
|
915
|
+
this.subscribedChannels = new Set(d.channels);
|
|
916
|
+
this.underlyingMintScope =
|
|
917
|
+
d.underlying_mints != null ? new Set(d.underlying_mints) : null;
|
|
918
|
+
this.quoteMintScope =
|
|
919
|
+
d.quote_mints != null ? new Set(d.quote_mints) : null;
|
|
920
|
+
this.emit("subscriptionUpdated", d);
|
|
921
|
+
}
|
|
922
|
+
break;
|
|
923
|
+
case "RequireInvite":
|
|
924
|
+
this.emit("requireInvite");
|
|
925
|
+
break;
|
|
926
|
+
case "InviteRedeemed":
|
|
927
|
+
this.emit("inviteRedeemed", message.data);
|
|
928
|
+
break;
|
|
929
|
+
case "ReferralCodeClaimed":
|
|
930
|
+
this.emit("referralCodeClaimed", message.data);
|
|
931
|
+
break;
|
|
932
|
+
case "MyReferralInfo":
|
|
933
|
+
this.emit("myReferralInfo", message.data);
|
|
934
|
+
break;
|
|
730
935
|
}
|
|
731
936
|
}
|
|
732
937
|
/** Taker-only: request current indicative prices for a market + position_type. */
|
|
@@ -752,7 +957,7 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
752
957
|
try {
|
|
753
958
|
const [pubkey, signature] = await Promise.all([
|
|
754
959
|
this.authProvider.getPublicKey(),
|
|
755
|
-
this.authProvider.
|
|
960
|
+
this.authProvider.signMessage(challenge),
|
|
756
961
|
]);
|
|
757
962
|
this.send({
|
|
758
963
|
type: "AuthChallenge",
|
|
@@ -812,9 +1017,13 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
812
1017
|
if (this.subscribedChannels.size > 0) {
|
|
813
1018
|
const channels = Array.from(this.subscribedChannels);
|
|
814
1019
|
const request_id = this.nextRequestId();
|
|
815
|
-
const data =
|
|
816
|
-
|
|
817
|
-
|
|
1020
|
+
const data = { request_id, channels };
|
|
1021
|
+
if (this.underlyingMintScope) {
|
|
1022
|
+
data.underlying_mints = Array.from(this.underlyingMintScope);
|
|
1023
|
+
}
|
|
1024
|
+
if (this.quoteMintScope) {
|
|
1025
|
+
data.quote_mints = Array.from(this.quoteMintScope);
|
|
1026
|
+
}
|
|
818
1027
|
this.send({ type: "Subscribe", data });
|
|
819
1028
|
}
|
|
820
1029
|
}
|
package/dist/ws/client.test.js
CHANGED
|
@@ -51,7 +51,7 @@ function parseClientMessage(payload) {
|
|
|
51
51
|
function makeAuthProvider(pubkey = "pubkey", signature = "signature") {
|
|
52
52
|
return {
|
|
53
53
|
getPublicKey: jest.fn().mockResolvedValue(pubkey),
|
|
54
|
-
|
|
54
|
+
signMessage: jest.fn().mockResolvedValue(signature),
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
function makeHarness(overrides = {}) {
|
package/dist/ws/index.d.ts
CHANGED
package/dist/ws/index.js
CHANGED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Referral / invite redemption helpers.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the server-side validation defined in
|
|
5
|
+
* rust-backend/acta-types/src/invite.rs (ReferralCode::parse).
|
|
6
|
+
* Client-side validation must stay bit-identical to the server on
|
|
7
|
+
* the same input. A hardcoded fixture table is kept here and on the
|
|
8
|
+
* rust side; if either drifts, the cross-impl test will catch it.
|
|
9
|
+
*/
|
|
10
|
+
export declare const REFERRAL_CODE_MIN_LEN = 4;
|
|
11
|
+
export declare const REFERRAL_CODE_MAX_LEN = 16;
|
|
12
|
+
declare const brand: unique symbol;
|
|
13
|
+
/**
|
|
14
|
+
* A normalized, validated referral code. The only way to obtain one
|
|
15
|
+
* is through `parseReferralCode`, which enforces trim + ASCII
|
|
16
|
+
* uppercase and length/charset bounds identical to the server's
|
|
17
|
+
* `ReferralCode::parse`.
|
|
18
|
+
*/
|
|
19
|
+
export type ReferralCode = string & {
|
|
20
|
+
readonly [brand]: "ReferralCode";
|
|
21
|
+
};
|
|
22
|
+
export type ReferralCodeFormatError = {
|
|
23
|
+
kind: "length";
|
|
24
|
+
min: number;
|
|
25
|
+
max: number;
|
|
26
|
+
} | {
|
|
27
|
+
kind: "charset";
|
|
28
|
+
};
|
|
29
|
+
export declare class ReferralCodeError extends Error {
|
|
30
|
+
readonly detail: ReferralCodeFormatError;
|
|
31
|
+
constructor(detail: ReferralCodeFormatError);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Trim + ASCII uppercase. No length or charset check. Useful for
|
|
35
|
+
* showing a live preview as the user types.
|
|
36
|
+
*/
|
|
37
|
+
export declare function normalizeReferralCode(input: string): string;
|
|
38
|
+
/**
|
|
39
|
+
* Parse + validate + normalize a user-supplied referral code.
|
|
40
|
+
* Mirrors `ReferralCode::parse` on the server. A successful result
|
|
41
|
+
* carries the canonical branded `ReferralCode`.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseReferralCode(input: string): {
|
|
44
|
+
ok: true;
|
|
45
|
+
code: ReferralCode;
|
|
46
|
+
} | {
|
|
47
|
+
ok: false;
|
|
48
|
+
error: ReferralCodeFormatError;
|
|
49
|
+
};
|
|
50
|
+
export type InviteErrorReason = "invalid_code" | "code_exhausted" | "code_expired" | "code_owner_inactive" | "code_owner_blacklisted" | "already_registered" | "internal_error";
|
|
51
|
+
export type ClaimErrorReason = "not_registered" | "invalid_format" | "code_taken" | "reserved" | "internal_error";
|
|
52
|
+
export type TakerStatus = "pending" | "active";
|
|
53
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Referral / invite redemption helpers.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the server-side validation defined in
|
|
5
|
+
* rust-backend/acta-types/src/invite.rs (ReferralCode::parse).
|
|
6
|
+
* Client-side validation must stay bit-identical to the server on
|
|
7
|
+
* the same input. A hardcoded fixture table is kept here and on the
|
|
8
|
+
* rust side; if either drifts, the cross-impl test will catch it.
|
|
9
|
+
*/
|
|
10
|
+
export const REFERRAL_CODE_MIN_LEN = 4;
|
|
11
|
+
export const REFERRAL_CODE_MAX_LEN = 16;
|
|
12
|
+
export class ReferralCodeError extends Error {
|
|
13
|
+
detail;
|
|
14
|
+
constructor(detail) {
|
|
15
|
+
super(detail.kind === "length"
|
|
16
|
+
? `code length must be between ${detail.min} and ${detail.max}`
|
|
17
|
+
: "code must contain only ASCII letters and digits");
|
|
18
|
+
this.detail = detail;
|
|
19
|
+
this.name = "ReferralCodeError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Trim + ASCII uppercase. No length or charset check. Useful for
|
|
24
|
+
* showing a live preview as the user types.
|
|
25
|
+
*/
|
|
26
|
+
export function normalizeReferralCode(input) {
|
|
27
|
+
return input.trim().toUpperCase();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse + validate + normalize a user-supplied referral code.
|
|
31
|
+
* Mirrors `ReferralCode::parse` on the server. A successful result
|
|
32
|
+
* carries the canonical branded `ReferralCode`.
|
|
33
|
+
*/
|
|
34
|
+
export function parseReferralCode(input) {
|
|
35
|
+
const normalized = normalizeReferralCode(input);
|
|
36
|
+
if (normalized.length < REFERRAL_CODE_MIN_LEN ||
|
|
37
|
+
normalized.length > REFERRAL_CODE_MAX_LEN) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
error: {
|
|
41
|
+
kind: "length",
|
|
42
|
+
min: REFERRAL_CODE_MIN_LEN,
|
|
43
|
+
max: REFERRAL_CODE_MAX_LEN,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
if (!/^[A-Z0-9]+$/.test(normalized)) {
|
|
48
|
+
return { ok: false, error: { kind: "charset" } };
|
|
49
|
+
}
|
|
50
|
+
return { ok: true, code: normalized };
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { normalizeReferralCode, parseReferralCode } from "./referral";
|
|
2
|
+
// =============================================================================
|
|
3
|
+
// Cross-impl fixture.
|
|
4
|
+
//
|
|
5
|
+
// Bit-for-bit identical to the fixture in
|
|
6
|
+
// rust-backend/acta-types/src/invite.rs::tests
|
|
7
|
+
// If either side changes, update both.
|
|
8
|
+
// =============================================================================
|
|
9
|
+
const ACCEPT_FIXTURES = [
|
|
10
|
+
["NIKITA", "NIKITA"],
|
|
11
|
+
["nikita", "NIKITA"],
|
|
12
|
+
[" abc1 ", "ABC1"],
|
|
13
|
+
["ABCD", "ABCD"],
|
|
14
|
+
["1234567890ABCDEF", "1234567890ABCDEF"],
|
|
15
|
+
];
|
|
16
|
+
describe("parseReferralCode", () => {
|
|
17
|
+
test.each(ACCEPT_FIXTURES)("accepts %j → %j", (input, expected) => {
|
|
18
|
+
const res = parseReferralCode(input);
|
|
19
|
+
expect(res.ok).toBe(true);
|
|
20
|
+
if (res.ok)
|
|
21
|
+
expect(res.code).toBe(expected);
|
|
22
|
+
});
|
|
23
|
+
test("rejects too short", () => {
|
|
24
|
+
const res = parseReferralCode("abc");
|
|
25
|
+
expect(res.ok).toBe(false);
|
|
26
|
+
if (!res.ok)
|
|
27
|
+
expect(res.error.kind).toBe("length");
|
|
28
|
+
});
|
|
29
|
+
test("rejects too long", () => {
|
|
30
|
+
const res = parseReferralCode("A".repeat(17));
|
|
31
|
+
expect(res.ok).toBe(false);
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
expect(res.error.kind).toBe("length");
|
|
34
|
+
});
|
|
35
|
+
test("rejects non-ascii", () => {
|
|
36
|
+
const res = parseReferralCode("абвгд");
|
|
37
|
+
expect(res.ok).toBe(false);
|
|
38
|
+
// Cyrillic passes length but fails charset.
|
|
39
|
+
if (!res.ok)
|
|
40
|
+
expect(res.error.kind).toBe("charset");
|
|
41
|
+
});
|
|
42
|
+
test("rejects punctuation", () => {
|
|
43
|
+
const res = parseReferralCode("ABC-12");
|
|
44
|
+
expect(res.ok).toBe(false);
|
|
45
|
+
if (!res.ok)
|
|
46
|
+
expect(res.error.kind).toBe("charset");
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
describe("normalizeReferralCode", () => {
|
|
50
|
+
test("trims and uppercases", () => {
|
|
51
|
+
expect(normalizeReferralCode(" nikita ")).toBe("NIKITA");
|
|
52
|
+
});
|
|
53
|
+
});
|