@acta-markets/ts-sdk 0.0.21-beta → 0.0.23-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/chain/instructions.position.d.ts +1 -0
- package/dist/chain/instructions.position.js +1 -0
- package/dist/cjs/chain/instructions.position.js +1 -0
- package/dist/cjs/generated/errors/actaContract.js +4 -1
- package/dist/cjs/generated/instructions/settlePosition.js +11 -3
- package/dist/cjs/idl/acta_contract.json +11 -1
- package/dist/cjs/idl/hash.js +1 -1
- package/dist/cjs/ws/auth.js +14 -11
- package/dist/cjs/ws/client.js +100 -1
- package/dist/cjs/ws/client.test.js +1 -1
- package/dist/cjs/ws/flows.js +20 -0
- 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/generated/instructions/settlePosition.d.ts +5 -1
- package/dist/generated/instructions/settlePosition.js +11 -3
- package/dist/idl/acta_contract.json +11 -1
- 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 +40 -1
- package/dist/ws/client.js +101 -2
- package/dist/ws/client.test.js +1 -1
- package/dist/ws/flows.d.ts +13 -1
- package/dist/ws/flows.js +19 -0
- 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 +142 -48
- package/package.json +1 -1
package/dist/ws/auth.d.ts
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
|
|
@@ -13,8 +16,8 @@
|
|
|
13
16
|
export interface AuthProvider {
|
|
14
17
|
/** Base58-encoded public key (address). */
|
|
15
18
|
getPublicKey(): Promise<string>;
|
|
16
|
-
/** Base58-encoded ed25519 signature over UTF-8 bytes of the
|
|
17
|
-
|
|
19
|
+
/** Base58-encoded ed25519 signature over UTF-8 bytes of the message. */
|
|
20
|
+
signMessage(message: string): Promise<string>;
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
20
23
|
* Auth provider backed by a `CryptoKeyPair`.
|
|
@@ -31,7 +34,7 @@ export declare class KeypairAuthProvider implements AuthProvider {
|
|
|
31
34
|
/** Create from Solana CLI keypair JSON (array of numbers). */
|
|
32
35
|
static fromJson(json: number[] | string): Promise<KeypairAuthProvider>;
|
|
33
36
|
getPublicKey(): Promise<string>;
|
|
34
|
-
|
|
37
|
+
signMessage(message: string): Promise<string>;
|
|
35
38
|
}
|
|
36
39
|
/**
|
|
37
40
|
* Wallet-like provider (browser wallets, custom signers, etc.)
|
|
@@ -43,7 +46,7 @@ export declare class WalletAuthProvider implements AuthProvider {
|
|
|
43
46
|
private readonly wallet;
|
|
44
47
|
constructor(wallet: WalletLike);
|
|
45
48
|
getPublicKey(): Promise<string>;
|
|
46
|
-
|
|
49
|
+
signMessage(message: string): Promise<string>;
|
|
47
50
|
}
|
|
48
51
|
export type WalletLike = {
|
|
49
52
|
/** Preferred: base58 address string. */
|
|
@@ -60,8 +63,8 @@ export type WalletLike = {
|
|
|
60
63
|
*/
|
|
61
64
|
export declare class CustomAuthProvider implements AuthProvider {
|
|
62
65
|
private readonly getPublicKeyFn;
|
|
63
|
-
private readonly
|
|
64
|
-
constructor(getPublicKey: () => Promise<string>,
|
|
66
|
+
private readonly signMessageFn;
|
|
67
|
+
constructor(getPublicKey: () => Promise<string>, signMessage: (message: string) => Promise<string>);
|
|
65
68
|
getPublicKey(): Promise<string>;
|
|
66
|
-
|
|
69
|
+
signMessage(message: string): Promise<string>;
|
|
67
70
|
}
|
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, MakerBalancesMessage, MakerMarketsMessage, MakerPositionsMessage, MyCapsMessage, MyQuotesMessage, MyTradesMessage, 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, ReplaceQuoteMessage, 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";
|
|
@@ -145,6 +145,10 @@ export type ActaWsClientEvents = {
|
|
|
145
145
|
positionSettled: (event: Extract<ChainEventMessage, {
|
|
146
146
|
event_type: "PositionSettled";
|
|
147
147
|
}>) => void;
|
|
148
|
+
requireInvite: () => void;
|
|
149
|
+
inviteRedeemed: (data: InviteRedeemedData) => void;
|
|
150
|
+
referralCodeClaimed: (data: ReferralCodeClaimedData) => void;
|
|
151
|
+
myReferralInfo: (data: MyReferralInfoData) => void;
|
|
148
152
|
};
|
|
149
153
|
type EventMap = Record<string, (...args: any[]) => void>;
|
|
150
154
|
declare class TypedEventEmitter<TEvents extends EventMap> {
|
|
@@ -277,6 +281,18 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
277
281
|
active_only?: boolean;
|
|
278
282
|
}): RequestId;
|
|
279
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;
|
|
280
296
|
getOrderStatus(orderIdHex: string): RequestId;
|
|
281
297
|
cancelRfq(rfqId: string): RequestId;
|
|
282
298
|
submitQuote(quote: QuoteMessage): void;
|
|
@@ -295,6 +311,27 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
295
311
|
orderId: Uint8Array;
|
|
296
312
|
makerSigner: SignerLike;
|
|
297
313
|
}): Promise<void>;
|
|
314
|
+
/**
|
|
315
|
+
* Replace an in-flight quote on the same RFQ. Server treats this as
|
|
316
|
+
* cancel-old + place-new atomically; maker signs the NEW `order_id`.
|
|
317
|
+
*/
|
|
318
|
+
submitReplaceQuote(msg: ReplaceQuoteMessage): void;
|
|
319
|
+
/** Convenience: compute new `order_id`, sign it, and send `ReplaceQuote`. */
|
|
320
|
+
submitReplaceQuoteSigned(args: {
|
|
321
|
+
oldOrderId: Uint8Array;
|
|
322
|
+
rfqId: string;
|
|
323
|
+
strike: number;
|
|
324
|
+
price: number;
|
|
325
|
+
validUntil: number;
|
|
326
|
+
nonce: number;
|
|
327
|
+
orderId: Uint8Array;
|
|
328
|
+
makerSigner: SignerLike;
|
|
329
|
+
}): Promise<void>;
|
|
330
|
+
/**
|
|
331
|
+
* Send multiple signed quotes in one WS frame. Each entry must already be
|
|
332
|
+
* a fully-built `QuoteMessage` (use `buildSignedQuoteMessage` per quote).
|
|
333
|
+
*/
|
|
334
|
+
submitBatchQuotes(quotes: QuoteMessage[]): void;
|
|
298
335
|
cancelQuote(rfqId: string): RequestId;
|
|
299
336
|
subscribe(channels: WsChannel[], opts?: {
|
|
300
337
|
underlying_mints?: string[];
|
|
@@ -311,6 +348,8 @@ export declare class ActaWsClient extends TypedEventEmitter<ActaWsClientEvents>
|
|
|
311
348
|
}): RequestId;
|
|
312
349
|
addChannels(channels: WsChannel[]): RequestId;
|
|
313
350
|
removeChannels(channels: WsChannel[]): RequestId;
|
|
351
|
+
/** Query the server's view of this session's subscriptions. Response: `subscriptions` event. */
|
|
352
|
+
getSubscriptions(): RequestId;
|
|
314
353
|
ping(): void;
|
|
315
354
|
resumeAuth(sessionId: string): void;
|
|
316
355
|
private doConnect;
|
package/dist/ws/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Acta WebSocket client (rfq-server). */
|
|
2
|
-
import { buildSignedQuoteMessage, buildAcceptQuoteMessage } from "./flows";
|
|
2
|
+
import { buildSignedQuoteMessage, buildSignedReplaceQuoteMessage, 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 } };
|
|
@@ -403,6 +404,51 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
403
404
|
logout() {
|
|
404
405
|
this.send({ type: "Logout" });
|
|
405
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
|
+
}
|
|
406
452
|
getOrderStatus(orderIdHex) {
|
|
407
453
|
this.ensureAuthenticated();
|
|
408
454
|
const requestId = this.nextRequestId();
|
|
@@ -447,6 +493,40 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
447
493
|
});
|
|
448
494
|
this.submitQuote(quote);
|
|
449
495
|
}
|
|
496
|
+
/**
|
|
497
|
+
* Replace an in-flight quote on the same RFQ. Server treats this as
|
|
498
|
+
* cancel-old + place-new atomically; maker signs the NEW `order_id`.
|
|
499
|
+
*/
|
|
500
|
+
submitReplaceQuote(msg) {
|
|
501
|
+
this.ensureAuthenticated();
|
|
502
|
+
this.send({ type: "ReplaceQuote", data: msg });
|
|
503
|
+
}
|
|
504
|
+
/** Convenience: compute new `order_id`, sign it, and send `ReplaceQuote`. */
|
|
505
|
+
async submitReplaceQuoteSigned(args) {
|
|
506
|
+
assertWsU64Safe(args.strike, "strike");
|
|
507
|
+
assertWsU64Safe(args.price, "price");
|
|
508
|
+
assertWsU64Safe(args.validUntil, "validUntil");
|
|
509
|
+
assertWsU64Safe(args.nonce, "nonce");
|
|
510
|
+
const msg = await buildSignedReplaceQuoteMessage({
|
|
511
|
+
oldOrderId: args.oldOrderId,
|
|
512
|
+
rfqId: args.rfqId,
|
|
513
|
+
strike: args.strike,
|
|
514
|
+
price: args.price,
|
|
515
|
+
validUntil: args.validUntil,
|
|
516
|
+
nonce: args.nonce,
|
|
517
|
+
orderId: args.orderId,
|
|
518
|
+
makerSigner: args.makerSigner,
|
|
519
|
+
});
|
|
520
|
+
this.submitReplaceQuote(msg);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Send multiple signed quotes in one WS frame. Each entry must already be
|
|
524
|
+
* a fully-built `QuoteMessage` (use `buildSignedQuoteMessage` per quote).
|
|
525
|
+
*/
|
|
526
|
+
submitBatchQuotes(quotes) {
|
|
527
|
+
this.ensureAuthenticated();
|
|
528
|
+
this.send({ type: "BatchQuotes", data: { quotes } });
|
|
529
|
+
}
|
|
450
530
|
cancelQuote(rfqId) {
|
|
451
531
|
this.ensureAuthenticated();
|
|
452
532
|
const requestId = this.nextRequestId();
|
|
@@ -540,6 +620,13 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
540
620
|
this.send({ type: "RemoveChannels", data: { request_id, channels } });
|
|
541
621
|
return request_id;
|
|
542
622
|
}
|
|
623
|
+
/** Query the server's view of this session's subscriptions. Response: `subscriptions` event. */
|
|
624
|
+
getSubscriptions() {
|
|
625
|
+
this.ensureAuthenticated();
|
|
626
|
+
const request_id = this.nextRequestId();
|
|
627
|
+
this.send({ type: "GetSubscriptions", data: { request_id } });
|
|
628
|
+
return request_id;
|
|
629
|
+
}
|
|
543
630
|
ping() {
|
|
544
631
|
if (this.ws?.readyState === WS_OPEN) {
|
|
545
632
|
this.send({ type: "Ping" });
|
|
@@ -874,6 +961,18 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
874
961
|
this.emit("subscriptionUpdated", d);
|
|
875
962
|
}
|
|
876
963
|
break;
|
|
964
|
+
case "RequireInvite":
|
|
965
|
+
this.emit("requireInvite");
|
|
966
|
+
break;
|
|
967
|
+
case "InviteRedeemed":
|
|
968
|
+
this.emit("inviteRedeemed", message.data);
|
|
969
|
+
break;
|
|
970
|
+
case "ReferralCodeClaimed":
|
|
971
|
+
this.emit("referralCodeClaimed", message.data);
|
|
972
|
+
break;
|
|
973
|
+
case "MyReferralInfo":
|
|
974
|
+
this.emit("myReferralInfo", message.data);
|
|
975
|
+
break;
|
|
877
976
|
}
|
|
878
977
|
}
|
|
879
978
|
/** Taker-only: request current indicative prices for a market + position_type. */
|
|
@@ -899,7 +998,7 @@ export class ActaWsClient extends TypedEventEmitter {
|
|
|
899
998
|
try {
|
|
900
999
|
const [pubkey, signature] = await Promise.all([
|
|
901
1000
|
this.authProvider.getPublicKey(),
|
|
902
|
-
this.authProvider.
|
|
1001
|
+
this.authProvider.signMessage(challenge),
|
|
903
1002
|
]);
|
|
904
1003
|
this.send({
|
|
905
1004
|
type: "AuthChallenge",
|
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/flows.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* - WS wire uses hex string for `order_id` and base58 for signatures
|
|
5
5
|
*/
|
|
6
6
|
import type { Address } from "@solana/addresses";
|
|
7
|
-
import type { AcceptQuoteMessage, QuoteMessage, RfqBroadcastMessage } from "./types";
|
|
7
|
+
import type { AcceptQuoteMessage, QuoteMessage, ReplaceQuoteMessage, RfqBroadcastMessage } from "./types";
|
|
8
8
|
import type { SignerLike } from "../chain/orders";
|
|
9
9
|
export declare function buildSignedQuoteMessage(args: {
|
|
10
10
|
rfqId: string;
|
|
@@ -15,6 +15,18 @@ export declare function buildSignedQuoteMessage(args: {
|
|
|
15
15
|
orderId: Uint8Array;
|
|
16
16
|
makerSigner: SignerLike;
|
|
17
17
|
}): Promise<QuoteMessage>;
|
|
18
|
+
export declare function buildSignedReplaceQuoteMessage(args: {
|
|
19
|
+
/** orderId of the quote being replaced (32 bytes). */
|
|
20
|
+
oldOrderId: Uint8Array;
|
|
21
|
+
rfqId: string;
|
|
22
|
+
strike: number;
|
|
23
|
+
price: number;
|
|
24
|
+
validUntil: number;
|
|
25
|
+
nonce: number;
|
|
26
|
+
/** NEW 32-byte orderId for the replacement quote. */
|
|
27
|
+
orderId: Uint8Array;
|
|
28
|
+
makerSigner: SignerLike;
|
|
29
|
+
}): Promise<ReplaceQuoteMessage>;
|
|
18
30
|
/**
|
|
19
31
|
* Strict maker helper: build a fully-valid quote from a server RFQ broadcast.
|
|
20
32
|
*
|
package/dist/ws/flows.js
CHANGED
|
@@ -24,6 +24,25 @@ export async function buildSignedQuoteMessage(args) {
|
|
|
24
24
|
signature: signatureBase58,
|
|
25
25
|
};
|
|
26
26
|
}
|
|
27
|
+
export async function buildSignedReplaceQuoteMessage(args) {
|
|
28
|
+
assertWsU64Safe(args.strike, "strike");
|
|
29
|
+
assertWsU64Safe(args.price, "price");
|
|
30
|
+
assertWsU64Safe(args.validUntil, "validUntil");
|
|
31
|
+
assertWsU64Safe(args.nonce, "nonce");
|
|
32
|
+
assertOrderId32(args.oldOrderId);
|
|
33
|
+
assertOrderId32(args.orderId);
|
|
34
|
+
const { signatureBase58 } = await signOrderIdBase58(args.makerSigner, args.orderId);
|
|
35
|
+
return {
|
|
36
|
+
old_order_id: orderIdToHex(args.oldOrderId),
|
|
37
|
+
rfq_id: args.rfqId,
|
|
38
|
+
strike: args.strike,
|
|
39
|
+
price: args.price,
|
|
40
|
+
valid_until: args.validUntil,
|
|
41
|
+
nonce: args.nonce,
|
|
42
|
+
order_id: orderIdToHex(args.orderId),
|
|
43
|
+
signature: signatureBase58,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
27
46
|
function positionTypeToU8(positionType) {
|
|
28
47
|
switch (positionType) {
|
|
29
48
|
case "covered_call":
|
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
|
+
});
|