@acta-markets/ts-sdk 0.0.1-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.
Files changed (263) hide show
  1. package/README.md +308 -0
  2. package/dist/actaClient.d.ts +46 -0
  3. package/dist/actaClient.js +99 -0
  4. package/dist/chain/client.d.ts +65 -0
  5. package/dist/chain/client.js +82 -0
  6. package/dist/chain/fetch.d.ts +204 -0
  7. package/dist/chain/fetch.js +392 -0
  8. package/dist/chain/fetch.test.d.ts +1 -0
  9. package/dist/chain/fetch.test.js +158 -0
  10. package/dist/chain/flows/index.d.ts +1 -0
  11. package/dist/chain/flows/index.js +1 -0
  12. package/dist/chain/flows/openPosition.d.ts +48 -0
  13. package/dist/chain/flows/openPosition.js +78 -0
  14. package/dist/chain/flows/openPosition.test.d.ts +1 -0
  15. package/dist/chain/flows/openPosition.test.js +43 -0
  16. package/dist/chain/helpers.d.ts +16 -0
  17. package/dist/chain/helpers.js +60 -0
  18. package/dist/chain/index.d.ts +9 -0
  19. package/dist/chain/index.js +9 -0
  20. package/dist/chain/instructions.admin.d.ts +44 -0
  21. package/dist/chain/instructions.admin.js +113 -0
  22. package/dist/chain/instructions.d.ts +5 -0
  23. package/dist/chain/instructions.js +5 -0
  24. package/dist/chain/instructions.maker.d.ts +34 -0
  25. package/dist/chain/instructions.maker.js +86 -0
  26. package/dist/chain/instructions.market.d.ts +39 -0
  27. package/dist/chain/instructions.market.js +107 -0
  28. package/dist/chain/instructions.oracle.d.ts +38 -0
  29. package/dist/chain/instructions.oracle.js +63 -0
  30. package/dist/chain/instructions.position.d.ts +82 -0
  31. package/dist/chain/instructions.position.js +240 -0
  32. package/dist/chain/instructions.resolve.test.d.ts +1 -0
  33. package/dist/chain/instructions.resolve.test.js +32 -0
  34. package/dist/chain/instructions.shared.d.ts +23 -0
  35. package/dist/chain/instructions.shared.js +55 -0
  36. package/dist/chain/orders.d.ts +40 -0
  37. package/dist/chain/orders.js +117 -0
  38. package/dist/chain/orders.test.d.ts +1 -0
  39. package/dist/chain/orders.test.js +67 -0
  40. package/dist/chain/signers.d.ts +9 -0
  41. package/dist/chain/signers.js +19 -0
  42. package/dist/chain/signers.test.d.ts +1 -0
  43. package/dist/chain/signers.test.js +13 -0
  44. package/dist/chain/token.d.ts +59 -0
  45. package/dist/chain/token.js +136 -0
  46. package/dist/chain/token.test.d.ts +1 -0
  47. package/dist/chain/token.test.js +69 -0
  48. package/dist/chain/tx.d.ts +72 -0
  49. package/dist/chain/tx.js +97 -0
  50. package/dist/cjs/actaClient.js +103 -0
  51. package/dist/cjs/chain/client.js +119 -0
  52. package/dist/cjs/chain/fetch.js +431 -0
  53. package/dist/cjs/chain/fetch.test.js +160 -0
  54. package/dist/cjs/chain/flows/index.js +17 -0
  55. package/dist/cjs/chain/flows/openPosition.js +81 -0
  56. package/dist/cjs/chain/flows/openPosition.test.js +45 -0
  57. package/dist/cjs/chain/helpers.js +67 -0
  58. package/dist/cjs/chain/index.js +48 -0
  59. package/dist/cjs/chain/instructions.admin.js +119 -0
  60. package/dist/cjs/chain/instructions.js +21 -0
  61. package/dist/cjs/chain/instructions.maker.js +92 -0
  62. package/dist/cjs/chain/instructions.market.js +112 -0
  63. package/dist/cjs/chain/instructions.oracle.js +70 -0
  64. package/dist/cjs/chain/instructions.position.js +247 -0
  65. package/dist/cjs/chain/instructions.resolve.test.js +34 -0
  66. package/dist/cjs/chain/instructions.shared.js +64 -0
  67. package/dist/cjs/chain/orders.js +126 -0
  68. package/dist/cjs/chain/orders.test.js +69 -0
  69. package/dist/cjs/chain/signers.js +22 -0
  70. package/dist/cjs/chain/signers.test.js +15 -0
  71. package/dist/cjs/chain/token.js +147 -0
  72. package/dist/cjs/chain/token.test.js +71 -0
  73. package/dist/cjs/chain/tx.js +103 -0
  74. package/dist/cjs/constants.js +6 -0
  75. package/dist/cjs/constants.test.js +31 -0
  76. package/dist/cjs/events.js +75 -0
  77. package/dist/cjs/events.test.js +384 -0
  78. package/dist/cjs/generated/accounts/config.js +79 -0
  79. package/dist/cjs/generated/accounts/index.js +28 -0
  80. package/dist/cjs/generated/accounts/maker.js +71 -0
  81. package/dist/cjs/generated/accounts/market.js +93 -0
  82. package/dist/cjs/generated/accounts/oracle.js +85 -0
  83. package/dist/cjs/generated/accounts/position.js +89 -0
  84. package/dist/cjs/generated/errors/actaContract.js +154 -0
  85. package/dist/cjs/generated/errors/index.js +24 -0
  86. package/dist/cjs/generated/index.js +28 -0
  87. package/dist/cjs/generated/instructions/changeOracleSource.js +81 -0
  88. package/dist/cjs/generated/instructions/closeMarket.js +83 -0
  89. package/dist/cjs/generated/instructions/closeOracle.js +79 -0
  90. package/dist/cjs/generated/instructions/createMarket.js +116 -0
  91. package/dist/cjs/generated/instructions/createOracle.js +99 -0
  92. package/dist/cjs/generated/instructions/depositFundsToPosition.js +101 -0
  93. package/dist/cjs/generated/instructions/depositPremium.js +96 -0
  94. package/dist/cjs/generated/instructions/finalizeMarket.js +91 -0
  95. package/dist/cjs/generated/instructions/index.js +43 -0
  96. package/dist/cjs/generated/instructions/initializeConfig.js +108 -0
  97. package/dist/cjs/generated/instructions/liquidatePosition.js +107 -0
  98. package/dist/cjs/generated/instructions/openPosition.js +154 -0
  99. package/dist/cjs/generated/instructions/registerMaker.js +90 -0
  100. package/dist/cjs/generated/instructions/setOracleConfig.js +80 -0
  101. package/dist/cjs/generated/instructions/settlePosition.js +104 -0
  102. package/dist/cjs/generated/instructions/topupFeeFund.js +96 -0
  103. package/dist/cjs/generated/instructions/updateConfig.js +86 -0
  104. package/dist/cjs/generated/instructions/updateMakerQuoteSigning.js +79 -0
  105. package/dist/cjs/generated/instructions/updateMarketOracles.js +96 -0
  106. package/dist/cjs/generated/instructions/updateOraclePrice.js +89 -0
  107. package/dist/cjs/generated/instructions/withdrawFromFeeFund.js +96 -0
  108. package/dist/cjs/generated/instructions/withdrawPremium.js +96 -0
  109. package/dist/cjs/generated/programs/actaContract.js +108 -0
  110. package/dist/cjs/generated/programs/index.js +24 -0
  111. package/dist/cjs/generated/shared/index.js +94 -0
  112. package/dist/cjs/generated/types/actaEvent.js +342 -0
  113. package/dist/cjs/generated/types/eventKind.js +45 -0
  114. package/dist/cjs/generated/types/index.js +27 -0
  115. package/dist/cjs/generated/types/positionStatus.js +31 -0
  116. package/dist/cjs/generated/types/positionType.js +28 -0
  117. package/dist/cjs/idl/acta_contract.json +2329 -0
  118. package/dist/cjs/idl/hash.js +4 -0
  119. package/dist/cjs/index.js +84 -0
  120. package/dist/cjs/nonce.js +93 -0
  121. package/dist/cjs/package.json +3 -0
  122. package/dist/cjs/types/index.js +18 -0
  123. package/dist/cjs/types/orderId.js +40 -0
  124. package/dist/cjs/types/orders.js +10 -0
  125. package/dist/cjs/ws/apy.js +106 -0
  126. package/dist/cjs/ws/apy.test.js +29 -0
  127. package/dist/cjs/ws/auth.js +104 -0
  128. package/dist/cjs/ws/client.handshake.integration.test.js +69 -0
  129. package/dist/cjs/ws/client.js +861 -0
  130. package/dist/cjs/ws/client.test.js +230 -0
  131. package/dist/cjs/ws/discovery.js +48 -0
  132. package/dist/cjs/ws/flows.js +101 -0
  133. package/dist/cjs/ws/flows.test.js +85 -0
  134. package/dist/cjs/ws/index.js +23 -0
  135. package/dist/cjs/ws/sponsoredTx.js +95 -0
  136. package/dist/cjs/ws/types.js +14 -0
  137. package/dist/cjs/ws/wirePolicy.js +34 -0
  138. package/dist/constants.d.ts +2 -0
  139. package/dist/constants.js +3 -0
  140. package/dist/constants.test.d.ts +1 -0
  141. package/dist/constants.test.js +29 -0
  142. package/dist/events.d.ts +15 -0
  143. package/dist/events.js +64 -0
  144. package/dist/events.test.d.ts +1 -0
  145. package/dist/events.test.js +382 -0
  146. package/dist/generated/accounts/config.d.ts +47 -0
  147. package/dist/generated/accounts/config.js +68 -0
  148. package/dist/generated/accounts/index.d.ts +12 -0
  149. package/dist/generated/accounts/index.js +12 -0
  150. package/dist/generated/accounts/maker.d.ts +39 -0
  151. package/dist/generated/accounts/maker.js +60 -0
  152. package/dist/generated/accounts/market.d.ts +61 -0
  153. package/dist/generated/accounts/market.js +82 -0
  154. package/dist/generated/accounts/oracle.d.ts +53 -0
  155. package/dist/generated/accounts/oracle.js +74 -0
  156. package/dist/generated/accounts/position.d.ts +57 -0
  157. package/dist/generated/accounts/position.js +78 -0
  158. package/dist/generated/errors/actaContract.d.ts +103 -0
  159. package/dist/generated/errors/actaContract.js +149 -0
  160. package/dist/generated/errors/index.d.ts +8 -0
  161. package/dist/generated/errors/index.js +8 -0
  162. package/dist/generated/index.d.ts +12 -0
  163. package/dist/generated/index.js +12 -0
  164. package/dist/generated/instructions/changeOracleSource.d.ts +50 -0
  165. package/dist/generated/instructions/changeOracleSource.js +72 -0
  166. package/dist/generated/instructions/closeMarket.d.ts +58 -0
  167. package/dist/generated/instructions/closeMarket.js +74 -0
  168. package/dist/generated/instructions/closeOracle.d.ts +48 -0
  169. package/dist/generated/instructions/closeOracle.js +70 -0
  170. package/dist/generated/instructions/createMarket.d.ts +90 -0
  171. package/dist/generated/instructions/createMarket.js +107 -0
  172. package/dist/generated/instructions/createOracle.d.ts +71 -0
  173. package/dist/generated/instructions/createOracle.js +90 -0
  174. package/dist/generated/instructions/depositFundsToPosition.d.ts +73 -0
  175. package/dist/generated/instructions/depositFundsToPosition.js +92 -0
  176. package/dist/generated/instructions/depositPremium.d.ts +62 -0
  177. package/dist/generated/instructions/depositPremium.js +87 -0
  178. package/dist/generated/instructions/finalizeMarket.d.ts +63 -0
  179. package/dist/generated/instructions/finalizeMarket.js +82 -0
  180. package/dist/generated/instructions/index.d.ts +27 -0
  181. package/dist/generated/instructions/index.js +27 -0
  182. package/dist/generated/instructions/initializeConfig.d.ts +75 -0
  183. package/dist/generated/instructions/initializeConfig.js +99 -0
  184. package/dist/generated/instructions/liquidatePosition.d.ts +78 -0
  185. package/dist/generated/instructions/liquidatePosition.js +98 -0
  186. package/dist/generated/instructions/openPosition.d.ts +130 -0
  187. package/dist/generated/instructions/openPosition.js +145 -0
  188. package/dist/generated/instructions/registerMaker.d.ts +57 -0
  189. package/dist/generated/instructions/registerMaker.js +81 -0
  190. package/dist/generated/instructions/setOracleConfig.d.ts +53 -0
  191. package/dist/generated/instructions/setOracleConfig.js +71 -0
  192. package/dist/generated/instructions/settlePosition.d.ts +78 -0
  193. package/dist/generated/instructions/settlePosition.js +95 -0
  194. package/dist/generated/instructions/topupFeeFund.d.ts +67 -0
  195. package/dist/generated/instructions/topupFeeFund.js +87 -0
  196. package/dist/generated/instructions/updateConfig.d.ts +58 -0
  197. package/dist/generated/instructions/updateConfig.js +77 -0
  198. package/dist/generated/instructions/updateMakerQuoteSigning.d.ts +47 -0
  199. package/dist/generated/instructions/updateMakerQuoteSigning.js +70 -0
  200. package/dist/generated/instructions/updateMarketOracles.d.ts +65 -0
  201. package/dist/generated/instructions/updateMarketOracles.js +87 -0
  202. package/dist/generated/instructions/updateOraclePrice.d.ts +55 -0
  203. package/dist/generated/instructions/updateOraclePrice.js +80 -0
  204. package/dist/generated/instructions/withdrawFromFeeFund.d.ts +62 -0
  205. package/dist/generated/instructions/withdrawFromFeeFund.js +87 -0
  206. package/dist/generated/instructions/withdrawPremium.d.ts +62 -0
  207. package/dist/generated/instructions/withdrawPremium.js +87 -0
  208. package/dist/generated/programs/actaContract.d.ts +83 -0
  209. package/dist/generated/programs/actaContract.js +104 -0
  210. package/dist/generated/programs/index.d.ts +8 -0
  211. package/dist/generated/programs/index.js +8 -0
  212. package/dist/generated/shared/index.d.ts +49 -0
  213. package/dist/generated/shared/index.js +86 -0
  214. package/dist/generated/types/actaEvent.d.ts +249 -0
  215. package/dist/generated/types/actaEvent.js +335 -0
  216. package/dist/generated/types/eventKind.d.ts +33 -0
  217. package/dist/generated/types/eventKind.js +39 -0
  218. package/dist/generated/types/index.d.ts +11 -0
  219. package/dist/generated/types/index.js +11 -0
  220. package/dist/generated/types/positionStatus.d.ts +19 -0
  221. package/dist/generated/types/positionStatus.js +25 -0
  222. package/dist/generated/types/positionType.d.ts +16 -0
  223. package/dist/generated/types/positionType.js +22 -0
  224. package/dist/idl/acta_contract.json +2329 -0
  225. package/dist/idl/hash.d.ts +1 -0
  226. package/dist/idl/hash.js +1 -0
  227. package/dist/index.d.ts +109 -0
  228. package/dist/index.js +34 -0
  229. package/dist/nonce.d.ts +29 -0
  230. package/dist/nonce.js +89 -0
  231. package/dist/types/index.d.ts +2 -0
  232. package/dist/types/index.js +2 -0
  233. package/dist/types/orderId.d.ts +13 -0
  234. package/dist/types/orderId.js +35 -0
  235. package/dist/types/orders.d.ts +9 -0
  236. package/dist/types/orders.js +9 -0
  237. package/dist/ws/apy.d.ts +79 -0
  238. package/dist/ws/apy.js +98 -0
  239. package/dist/ws/apy.test.d.ts +1 -0
  240. package/dist/ws/apy.test.js +27 -0
  241. package/dist/ws/auth.d.ts +67 -0
  242. package/dist/ws/auth.js +98 -0
  243. package/dist/ws/client.d.ts +263 -0
  244. package/dist/ws/client.handshake.integration.test.d.ts +1 -0
  245. package/dist/ws/client.handshake.integration.test.js +64 -0
  246. package/dist/ws/client.js +857 -0
  247. package/dist/ws/client.test.d.ts +1 -0
  248. package/dist/ws/client.test.js +228 -0
  249. package/dist/ws/discovery.d.ts +13 -0
  250. package/dist/ws/discovery.js +44 -0
  251. package/dist/ws/flows.d.ts +44 -0
  252. package/dist/ws/flows.js +96 -0
  253. package/dist/ws/flows.test.d.ts +1 -0
  254. package/dist/ws/flows.test.js +83 -0
  255. package/dist/ws/index.d.ts +7 -0
  256. package/dist/ws/index.js +7 -0
  257. package/dist/ws/sponsoredTx.d.ts +39 -0
  258. package/dist/ws/sponsoredTx.js +92 -0
  259. package/dist/ws/types.d.ts +900 -0
  260. package/dist/ws/types.js +13 -0
  261. package/dist/ws/wirePolicy.d.ts +12 -0
  262. package/dist/ws/wirePolicy.js +30 -0
  263. package/package.json +87 -0
@@ -0,0 +1,857 @@
1
+ /** Acta WebSocket client (rfq-server). */
2
+ import { buildSignedQuoteMessage, buildAcceptQuoteMessage } from "./flows";
3
+ import { assertWsU64Safe } from "./wirePolicy";
4
+ function formatServerError(error) {
5
+ if (error.type === "generic") {
6
+ return `${error.data.code}: ${error.data.message}`;
7
+ }
8
+ if ("data" in error) {
9
+ const payload = typeof error.data === "string" ? error.data : JSON.stringify(error.data);
10
+ return `${error.type}: ${payload}`;
11
+ }
12
+ return error.type;
13
+ }
14
+ class TypedEventEmitter {
15
+ listeners = new Map();
16
+ on(event, listener) {
17
+ const set = this.listeners.get(event) ?? new Set();
18
+ set.add(listener);
19
+ this.listeners.set(event, set);
20
+ return this;
21
+ }
22
+ once(event, listener) {
23
+ const wrapped = ((...args) => {
24
+ this.off(event, wrapped);
25
+ listener(...args);
26
+ });
27
+ return this.on(event, wrapped);
28
+ }
29
+ off(event, listener) {
30
+ const set = this.listeners.get(event);
31
+ set?.delete(listener);
32
+ if (set && set.size === 0)
33
+ this.listeners.delete(event);
34
+ return this;
35
+ }
36
+ removeAllListeners(event) {
37
+ if (event === undefined) {
38
+ this.listeners.clear();
39
+ }
40
+ else {
41
+ this.listeners.delete(event);
42
+ }
43
+ return this;
44
+ }
45
+ emit(event, ...args) {
46
+ const set = this.listeners.get(event);
47
+ if (!set || set.size === 0)
48
+ return false;
49
+ for (const listener of Array.from(set)) {
50
+ listener(...args);
51
+ }
52
+ return true;
53
+ }
54
+ }
55
+ const WS_CONNECTING = 0;
56
+ const WS_OPEN = 1;
57
+ function defaultSocketFactory() {
58
+ const WsCtor = globalThis.WebSocket;
59
+ if (!WsCtor) {
60
+ throw new Error("No global WebSocket available. Provide `makeSocket` (e.g. using the `ws` package in Node).");
61
+ }
62
+ return (url) => new WsCtor(url);
63
+ }
64
+ function getCloseInfo(ev) {
65
+ if (!ev || typeof ev !== "object")
66
+ return {};
67
+ const rec = ev;
68
+ return {
69
+ code: typeof rec.code === "number" ? rec.code : undefined,
70
+ reason: typeof rec.reason === "string" ? rec.reason : undefined,
71
+ };
72
+ }
73
+ export class ActaWsClient extends TypedEventEmitter {
74
+ ws = null;
75
+ options;
76
+ authProvider = null;
77
+ authRequested = false;
78
+ startAuthSent = false;
79
+ connectionState = "disconnected";
80
+ sessionId = null;
81
+ helloSent = false;
82
+ welcomeReceived = false;
83
+ pendingMessages = [];
84
+ reconnectAttempts = 0;
85
+ reconnectTimer = null;
86
+ pingTimer = null;
87
+ shouldReconnect = true;
88
+ subscribedChannels = new Set(["rfqs"]);
89
+ subscribedMarkets = new Set();
90
+ hasMarketScope = false;
91
+ state = {
92
+ stats: null,
93
+ activeRfqs: new Map(),
94
+ myActiveRfqs: new Map(),
95
+ positions: new Map(),
96
+ recentTrades: [],
97
+ markets: new Map(),
98
+ };
99
+ constructor(options) {
100
+ super();
101
+ this.options = {
102
+ protocolVersion: "1.0.0",
103
+ features: [],
104
+ autoReconnect: true,
105
+ reconnectDelay: 1000,
106
+ maxReconnectDelay: 30_000,
107
+ reconnectJitterRatio: 0.2,
108
+ pingInterval: 30_000,
109
+ maxPendingMessages: 100,
110
+ pendingMessagesOverflowPolicy: "drop_oldest",
111
+ debug: false,
112
+ makeSocket: options.makeSocket ?? defaultSocketFactory(),
113
+ ...options,
114
+ };
115
+ this.options.maxPendingMessages = Math.max(0, Math.floor(this.options.maxPendingMessages));
116
+ this.options.reconnectJitterRatio = Math.min(1, Math.max(0, this.options.reconnectJitterRatio));
117
+ this.shouldReconnect = this.options.autoReconnect;
118
+ }
119
+ connectAnonymous() {
120
+ this.authProvider = null;
121
+ this.authRequested = false;
122
+ this.shouldReconnect = this.options.autoReconnect;
123
+ this.doConnect();
124
+ }
125
+ connect(authProvider) {
126
+ this.authProvider = authProvider;
127
+ this.authRequested = false;
128
+ this.shouldReconnect = this.options.autoReconnect;
129
+ this.doConnect();
130
+ }
131
+ connectAndAuthenticate(authProvider) {
132
+ this.authProvider = authProvider;
133
+ this.authRequested = true;
134
+ this.shouldReconnect = this.options.autoReconnect;
135
+ if (this.ws?.readyState === WS_OPEN) {
136
+ if (this.welcomeReceived && !this.startAuthSent) {
137
+ void this.sendStartAuth().catch((err) => {
138
+ this.emit("error", err);
139
+ this.setConnectionState("error");
140
+ });
141
+ }
142
+ return;
143
+ }
144
+ this.doConnect();
145
+ }
146
+ async authenticate(authProvider) {
147
+ this.authProvider = authProvider;
148
+ this.authRequested = true;
149
+ if (this.ws?.readyState === WS_OPEN) {
150
+ if (this.welcomeReceived && !this.startAuthSent) {
151
+ await this.sendStartAuth();
152
+ }
153
+ return;
154
+ }
155
+ if (!this.ws) {
156
+ throw new Error("WebSocket is not connected");
157
+ }
158
+ }
159
+ disconnect() {
160
+ this.shouldReconnect = false;
161
+ this.cleanup();
162
+ }
163
+ getConnectionState() {
164
+ return this.connectionState;
165
+ }
166
+ isAuthenticated() {
167
+ return this.connectionState === "authenticated";
168
+ }
169
+ getSessionId() {
170
+ return this.sessionId;
171
+ }
172
+ async createRfq(request) {
173
+ this.ensureAuthenticated();
174
+ this.send({
175
+ type: "RfqRequest",
176
+ data: {
177
+ market: request.market,
178
+ position_type: request.position_type,
179
+ strike: request.strike,
180
+ quantity: request.quantity,
181
+ timeout_seconds: request.timeoutSeconds ?? 30,
182
+ client_request_id: request.clientRequestId,
183
+ },
184
+ });
185
+ }
186
+ async acceptQuote(rfqId, maker, orderIdHex) {
187
+ this.ensureAuthenticated();
188
+ this.send({
189
+ type: "AcceptQuote",
190
+ data: { rfq_id: rfqId, maker, order_id: orderIdHex },
191
+ });
192
+ }
193
+ /**
194
+ * Convenience: build and send `AcceptQuote` from a raw 32-byte `orderId`.
195
+ */
196
+ async acceptQuoteByOrderId(args) {
197
+ const msg = buildAcceptQuoteMessage({
198
+ rfqId: args.rfqId,
199
+ maker: args.maker,
200
+ orderId: args.orderId,
201
+ });
202
+ return await this.acceptQuote(args.rfqId, args.maker, msg.order_id);
203
+ }
204
+ /**
205
+ * Submit a partially-signed sponsored transaction back to the server.
206
+ *
207
+ * `txBase64` must be a base64-encoded `VersionedTransaction` (v0) signed by the taker.
208
+ */
209
+ async submitSignedSponsoredTx(args) {
210
+ this.ensureAuthenticated();
211
+ this.send({
212
+ type: "SubmitSignedSponsoredTx",
213
+ data: { order_id: args.orderIdHex, tx_base64: args.txBase64 },
214
+ });
215
+ }
216
+ getPositions() {
217
+ this.ensureAuthenticated();
218
+ this.send({ type: "GetPositions" });
219
+ }
220
+ getMarkets() {
221
+ this.send({ type: "GetMarkets" });
222
+ }
223
+ getMarketDescriptors(args) {
224
+ const data = {
225
+ active_only: args?.active_only ?? true,
226
+ };
227
+ this.send({ type: "GetMarketDescriptors", data });
228
+ }
229
+ getExpiries(args) {
230
+ this.send({
231
+ type: "GetExpiries",
232
+ data: {
233
+ underlying_mint: args?.underlying_mint,
234
+ quote_mint: args?.quote_mint,
235
+ is_put: args?.is_put ?? null,
236
+ },
237
+ });
238
+ }
239
+ getTokens(args) {
240
+ const data = {
241
+ active_only: args?.active_only ?? true,
242
+ };
243
+ this.send({ type: "GetTokens", data });
244
+ }
245
+ getMyActiveRfqs() {
246
+ this.ensureAuthenticated();
247
+ this.send({ type: "GetMyActiveRfqs" });
248
+ }
249
+ getActiveRfqs() {
250
+ this.send({ type: "GetActiveRfqs" });
251
+ }
252
+ getOrderStatus(orderIdHex) {
253
+ this.ensureAuthenticated();
254
+ this.send({ type: "GetOrderStatus", data: { order_id: orderIdHex } });
255
+ }
256
+ cancelRfq(rfqId) {
257
+ this.ensureAuthenticated();
258
+ this.send({ type: "CancelRfq", data: { rfq_id: rfqId } });
259
+ }
260
+ submitQuote(quote) {
261
+ this.ensureAuthenticated();
262
+ this.send({ type: "Quote", data: quote });
263
+ }
264
+ /**
265
+ * Convenience: sign 32-byte `orderId` and send `Quote`.
266
+ *
267
+ * Maker constructs the full on-chain order (incl. nonce), computes `orderId`,
268
+ * signs it, and sends the signature + orderId over WS.
269
+ */
270
+ async submitQuoteSigned(args) {
271
+ assertWsU64Safe(args.strike, "strike");
272
+ assertWsU64Safe(args.price, "price");
273
+ assertWsU64Safe(args.validUntil, "validUntil");
274
+ assertWsU64Safe(args.nonce, "nonce");
275
+ const quote = await buildSignedQuoteMessage({
276
+ rfqId: args.rfqId,
277
+ strike: args.strike,
278
+ price: args.price,
279
+ validUntil: args.validUntil,
280
+ nonce: args.nonce,
281
+ orderId: args.orderId,
282
+ makerSigner: args.makerSigner,
283
+ });
284
+ this.submitQuote(quote);
285
+ }
286
+ cancelQuote(rfqId) {
287
+ this.ensureAuthenticated();
288
+ this.send({ type: "CancelQuote", data: { rfq_id: rfqId } });
289
+ }
290
+ subscribe(channels, markets) {
291
+ this.ensureAuthenticated();
292
+ for (const c of channels)
293
+ this.subscribedChannels.add(c);
294
+ if (markets) {
295
+ this.hasMarketScope = true;
296
+ this.subscribedMarkets = new Set(markets);
297
+ }
298
+ this.send({
299
+ type: "Subscribe",
300
+ data: this.hasMarketScope
301
+ ? { channels, markets: Array.from(this.subscribedMarkets) }
302
+ : { channels },
303
+ });
304
+ }
305
+ unsubscribe(channels) {
306
+ this.ensureAuthenticated();
307
+ for (const c of channels)
308
+ this.subscribedChannels.delete(c);
309
+ this.send({
310
+ type: "Unsubscribe",
311
+ data: { channels },
312
+ });
313
+ }
314
+ ping() {
315
+ if (this.ws?.readyState === WS_OPEN) {
316
+ this.send({ type: "Ping" });
317
+ }
318
+ }
319
+ doConnect() {
320
+ if (this.ws) {
321
+ this.cleanup();
322
+ }
323
+ this.setConnectionState("connecting");
324
+ this.emit("connecting");
325
+ const endpoint = this.options.role === "maker" ? "/ws/maker" : "/ws/taker";
326
+ const url = `${this.options.url}${endpoint}`;
327
+ this.log(`Connecting to ${url}`);
328
+ const ws = this.options.makeSocket(url);
329
+ this.ws = ws;
330
+ this.helloSent = false;
331
+ this.welcomeReceived = false;
332
+ this.startAuthSent = false;
333
+ this.pendingMessages = [];
334
+ ws.onopen = () => {
335
+ this.log("WebSocket connected");
336
+ this.reconnectAttempts = 0;
337
+ this.sendHello();
338
+ this.emit("connected");
339
+ this.startPingInterval();
340
+ };
341
+ ws.onmessage = (event) => {
342
+ try {
343
+ const parsed = JSON.parse(event.data);
344
+ this.handleMessage(parsed);
345
+ }
346
+ catch (err) {
347
+ this.log("Failed to parse message:", err);
348
+ }
349
+ };
350
+ ws.onclose = (ev) => {
351
+ const info = getCloseInfo(ev);
352
+ const code = typeof info.code === "number" ? info.code : 1000;
353
+ const reason = typeof info.reason === "string" ? info.reason : "";
354
+ this.log(`WebSocket closed: ${code} ${reason}`);
355
+ this.cleanup();
356
+ this.emit("disconnected", code, reason);
357
+ if (this.shouldReconnect) {
358
+ this.scheduleReconnect();
359
+ }
360
+ };
361
+ ws.onerror = (ev) => {
362
+ this.log("WebSocket error:", ev);
363
+ this.emit("error", new Error("WebSocket error"));
364
+ if (this.shouldReconnect) {
365
+ this.cleanup();
366
+ this.scheduleReconnect();
367
+ }
368
+ };
369
+ }
370
+ handleMessage(message) {
371
+ this.log("Received:", message.type);
372
+ this.emit("message", message);
373
+ switch (message.type) {
374
+ case "Welcome":
375
+ this.welcomeReceived = true;
376
+ this.emit("welcome", message.data);
377
+ this.flushPendingMessages();
378
+ if (this.authRequested && this.authProvider && !this.startAuthSent) {
379
+ void this.sendStartAuth().catch((err) => {
380
+ this.emit("error", err);
381
+ this.setConnectionState("error");
382
+ });
383
+ }
384
+ break;
385
+ case "VersionMismatch":
386
+ this.emit("versionMismatch", message.data);
387
+ this.stopAutoReconnect();
388
+ this.setConnectionState("error");
389
+ if (this.ws &&
390
+ (this.ws.readyState === WS_OPEN ||
391
+ this.ws.readyState === WS_CONNECTING)) {
392
+ try {
393
+ this.ws.close(1002, "version_mismatch");
394
+ }
395
+ catch {
396
+ // ignore
397
+ }
398
+ }
399
+ break;
400
+ case "AuthRequest":
401
+ void this.handleAuthRequest(message.data.challenge);
402
+ break;
403
+ case "AuthSuccess":
404
+ this.handleAuthSuccess(message.data.session_id);
405
+ break;
406
+ case "AuthError":
407
+ this.handleAuthError(message.data.reason);
408
+ break;
409
+ case "Snapshot":
410
+ this.handleSnapshot(message.data);
411
+ break;
412
+ case "Positions":
413
+ this.handlePositions(message.data.positions ?? []);
414
+ break;
415
+ case "Markets":
416
+ this.handleMarkets(message.data.markets ?? []);
417
+ break;
418
+ case "MarketDescriptors":
419
+ this.emit("marketDescriptors", message.data.markets ?? []);
420
+ break;
421
+ case "Expiries":
422
+ this.emit("expiries", message.data.expiries_ts ?? []);
423
+ break;
424
+ case "Tokens":
425
+ this.emit("tokens", {
426
+ underlyings: message.data.underlyings ?? [],
427
+ quotesByUnderlying: message.data.quotes_by_underlying ?? {},
428
+ });
429
+ break;
430
+ case "Subscriptions":
431
+ this.emit("subscriptions", message.data);
432
+ break;
433
+ case "ActiveRfqs":
434
+ this.state.activeRfqs.clear();
435
+ for (const rfq of message.data.rfqs) {
436
+ this.state.activeRfqs.set(rfq.rfq_id, rfq);
437
+ }
438
+ this.emit("activeRfqs", message.data.rfqs);
439
+ break;
440
+ case "MyActiveRfqs":
441
+ this.handleMyActiveRfqs(message.data);
442
+ break;
443
+ case "OrderStatus":
444
+ this.handleOrderStatus(message.data);
445
+ break;
446
+ case "StatsUpdate":
447
+ this.handleStatsUpdate(message.data.stats);
448
+ break;
449
+ case "RfqBroadcast":
450
+ // Keep local RFQ state reasonably fresh even without a new snapshot.
451
+ if (!this.state.activeRfqs.has(message.data.rfq_id)) {
452
+ this.state.activeRfqs.set(message.data.rfq_id, {
453
+ rfq_id: message.data.rfq_id,
454
+ market: message.data.market.market_pda,
455
+ position_type: message.data.position_type,
456
+ strike: message.data.strike,
457
+ quantity: message.data.quantity,
458
+ expires_at: message.data.expires_at,
459
+ quotes_count: 0,
460
+ best_price: null,
461
+ order_options: message.data.order_options,
462
+ });
463
+ }
464
+ this.emit("rfqBroadcast", message.data);
465
+ break;
466
+ case "RfqCreated":
467
+ this.emit("rfqCreated", message.data);
468
+ break;
469
+ case "RfqClosed":
470
+ this.state.activeRfqs.delete(message.data.rfq_id);
471
+ this.emit("rfqClosed", message.data);
472
+ break;
473
+ case "QuoteReceived":
474
+ {
475
+ const rfq = this.state.activeRfqs.get(message.data.rfq_id);
476
+ if (rfq) {
477
+ const nextCount = rfq.quotes_count + 1;
478
+ const nextBest = rfq.best_price === null
479
+ ? message.data.price
480
+ : Math.max(rfq.best_price, message.data.price);
481
+ this.state.activeRfqs.set(message.data.rfq_id, {
482
+ ...rfq,
483
+ quotes_count: nextCount,
484
+ best_price: nextBest,
485
+ });
486
+ }
487
+ this.emit("quoteReceived", message.data);
488
+ }
489
+ break;
490
+ case "QuotesUpdate":
491
+ {
492
+ const rfq = this.state.activeRfqs.get(message.data.rfq_id);
493
+ if (rfq) {
494
+ const prices = message.data.quotes.map((q) => q.price);
495
+ const nextBest = prices.length === 0 ? null : Math.max(...prices);
496
+ this.state.activeRfqs.set(message.data.rfq_id, {
497
+ ...rfq,
498
+ quotes_count: message.data.quotes.length,
499
+ best_price: nextBest,
500
+ });
501
+ }
502
+ this.emit("quotesUpdate", message.data);
503
+ }
504
+ break;
505
+ case "QuoteAcknowledged":
506
+ this.emit("quoteAcknowledged", message.data);
507
+ break;
508
+ case "QuoteBestStatus":
509
+ this.emit("quoteBestStatus", message.data);
510
+ break;
511
+ case "QuoteOutbid":
512
+ this.emit("quoteOutbid", message.data);
513
+ break;
514
+ case "QuoteRefreshRequested":
515
+ this.emit("quoteRefreshRequested", message.data);
516
+ break;
517
+ case "QuoteSelected":
518
+ this.emit("quoteSelected", message.data);
519
+ break;
520
+ case "QuoteFilled":
521
+ this.emit("quoteFilled", message.data);
522
+ break;
523
+ case "QuoteCancelled":
524
+ this.emit("quoteCancelled", message.data);
525
+ break;
526
+ case "RfqAvailableAgain":
527
+ this.emit("rfqAvailableAgain", message.data);
528
+ break;
529
+ case "QuoteExpired":
530
+ this.emit("quoteExpired", message.data);
531
+ break;
532
+ case "IndicativePrices":
533
+ this.emit("indicativePrices", message.data);
534
+ break;
535
+ case "IndicativePricesRequest":
536
+ this.emit("indicativePricesRequest", message.data);
537
+ break;
538
+ case "OrderAccepted":
539
+ this.emit("orderAccepted", message.data.order_id);
540
+ break;
541
+ case "SponsoredTxToSign":
542
+ this.emit("sponsoredTxToSign", message.data.order_id, message.data.tx_base64, message.data.signature_deadline);
543
+ break;
544
+ case "OrderSubmitted":
545
+ this.emit("orderSubmitted", message.data.order_id, message.data.tx_signature);
546
+ break;
547
+ case "OrderConfirmed":
548
+ this.emit("orderConfirmed", message.data.order_id, message.data.position_pda);
549
+ break;
550
+ case "OrderFailed":
551
+ this.emit("orderFailed", message.data.order_id, message.data.reason);
552
+ break;
553
+ case "TradeExecuted":
554
+ this.handleTradeExecuted(message.data);
555
+ break;
556
+ case "PositionUpdated":
557
+ this.handlePositionUpdated(message.data);
558
+ break;
559
+ case "MarketCreated":
560
+ this.state.markets.set(message.data.pda, message.data);
561
+ this.emit("marketCreated", message.data);
562
+ break;
563
+ case "MarketFinalized":
564
+ this.emit("marketFinalized", message.data.market_pda, message.data.settlement_price);
565
+ break;
566
+ case "ChainEvent":
567
+ this.handleChainEvent(message.data);
568
+ break;
569
+ case "Pong":
570
+ break;
571
+ case "Error":
572
+ this.emit("error", new Error(formatServerError(message.data)));
573
+ break;
574
+ }
575
+ }
576
+ /** Taker-only: request current indicative prices for a market + position_type. */
577
+ getIndicativePrices(req) {
578
+ this.send({
579
+ type: "GetIndicativePrices",
580
+ data: req,
581
+ });
582
+ }
583
+ /** Maker-only: respond to an indicative request (unsigned, non-binding). */
584
+ sendIndicativePricesResponse(resp) {
585
+ this.ensureAuthenticated();
586
+ this.send({ type: "IndicativePricesResponse", data: resp });
587
+ }
588
+ async handleAuthRequest(challenge) {
589
+ this.setConnectionState("authenticating");
590
+ if (!this.authProvider) {
591
+ this.emit("error", new Error("No auth provider configured"));
592
+ return;
593
+ }
594
+ try {
595
+ const [pubkey, signature] = await Promise.all([
596
+ this.authProvider.getPublicKey(),
597
+ this.authProvider.signChallenge(challenge),
598
+ ]);
599
+ this.send({
600
+ type: "AuthChallenge",
601
+ data: { challenge, signature, pubkey },
602
+ });
603
+ }
604
+ catch (err) {
605
+ this.emit("error", err);
606
+ this.setConnectionState("error");
607
+ }
608
+ }
609
+ async sendStartAuth() {
610
+ if (!this.authProvider)
611
+ throw new Error("No auth provider configured");
612
+ this.startAuthSent = true;
613
+ const pubkey = await this.authProvider.getPublicKey();
614
+ this.send({ type: "StartAuth", data: { pubkey } });
615
+ }
616
+ sendHello() {
617
+ const hello = {
618
+ protocol_version: this.options.protocolVersion,
619
+ features: this.options.features ?? [],
620
+ client_name: this.options.clientName,
621
+ client_version: this.options.clientVersion,
622
+ };
623
+ this.send({ type: "Hello", data: hello });
624
+ }
625
+ flushPendingMessages() {
626
+ if (!this.welcomeReceived || this.pendingMessages.length === 0)
627
+ return;
628
+ const pending = this.pendingMessages;
629
+ this.pendingMessages = [];
630
+ for (const msg of pending) {
631
+ this.send(msg);
632
+ }
633
+ }
634
+ handleAuthSuccess(sessionId) {
635
+ this.sessionId = sessionId;
636
+ this.setConnectionState("authenticated");
637
+ this.emit("authenticated", sessionId);
638
+ if (this.subscribedChannels.size > 0) {
639
+ const channels = Array.from(this.subscribedChannels);
640
+ const data = this.hasMarketScope
641
+ ? { channels, markets: Array.from(this.subscribedMarkets) }
642
+ : { channels };
643
+ this.send({ type: "Subscribe", data });
644
+ }
645
+ }
646
+ handleAuthError(reason) {
647
+ this.setConnectionState("error");
648
+ this.emit("error", new Error(`Authentication failed: ${reason}`));
649
+ }
650
+ handleSnapshot(snapshot) {
651
+ this.state.stats = snapshot.stats;
652
+ this.state.activeRfqs.clear();
653
+ for (const rfq of snapshot.active_rfqs) {
654
+ this.state.activeRfqs.set(rfq.rfq_id, rfq);
655
+ }
656
+ this.state.myActiveRfqs.clear();
657
+ this.state.positions.clear();
658
+ for (const position of snapshot.positions ?? []) {
659
+ this.state.positions.set(position.pda, position);
660
+ }
661
+ this.state.recentTrades = snapshot.recent_trades ?? [];
662
+ this.state.markets.clear();
663
+ for (const market of snapshot.markets) {
664
+ this.state.markets.set(market.pda, market);
665
+ }
666
+ this.emit("snapshot", snapshot);
667
+ }
668
+ handleMyActiveRfqs(msg) {
669
+ this.state.myActiveRfqs.clear();
670
+ for (const rfq of msg.rfqs) {
671
+ this.state.myActiveRfqs.set(rfq.rfq_id, rfq);
672
+ }
673
+ this.emit("myActiveRfqs", msg);
674
+ }
675
+ handleOrderStatus(msg) {
676
+ this.emit("orderStatus", msg);
677
+ }
678
+ handlePositions(positions) {
679
+ this.state.positions.clear();
680
+ for (const position of positions) {
681
+ this.state.positions.set(position.pda, position);
682
+ }
683
+ this.emit("positions", positions);
684
+ }
685
+ handleMarkets(markets) {
686
+ this.state.markets.clear();
687
+ for (const market of markets) {
688
+ this.state.markets.set(market.pda, market);
689
+ }
690
+ this.emit("markets", markets);
691
+ }
692
+ handleStatsUpdate(stats) {
693
+ this.state.stats = stats;
694
+ this.emit("statsUpdate", stats);
695
+ }
696
+ handleTradeExecuted(data) {
697
+ this.state.recentTrades.unshift(data.trade);
698
+ this.state.recentTrades = this.state.recentTrades.slice(0, 50);
699
+ const delta = data.stats_delta ?? null;
700
+ if (delta && this.state.stats) {
701
+ if (delta.volume_added)
702
+ this.state.stats.total_volume_24h += delta.volume_added;
703
+ if (delta.trades_added)
704
+ this.state.stats.total_trades_24h += delta.trades_added;
705
+ if (delta.price_added)
706
+ this.state.stats.total_price_24h += delta.price_added;
707
+ }
708
+ this.emit("tradeExecuted", data.trade, delta);
709
+ }
710
+ handlePositionUpdated(data) {
711
+ this.state.positions.set(data.position.pda, data.position);
712
+ this.emit("positionUpdated", data.position, data.update_type);
713
+ }
714
+ handleChainEvent(event) {
715
+ this.emit("chainEvent", event);
716
+ switch (event.event_type) {
717
+ case "PositionOpened":
718
+ this.emit("positionOpened", event);
719
+ break;
720
+ case "MakerRegistered":
721
+ this.emit("makerRegistered", event);
722
+ break;
723
+ case "MarketCreated":
724
+ this.emit("chainMarketCreated", event);
725
+ break;
726
+ case "MarketFinalized":
727
+ this.emit("chainMarketFinalized", event);
728
+ break;
729
+ case "PositionSettled":
730
+ this.emit("positionSettled", event);
731
+ break;
732
+ }
733
+ }
734
+ send(message) {
735
+ if (this.ws?.readyState === WS_OPEN) {
736
+ if (!this.welcomeReceived && message.type !== "Hello") {
737
+ if (this.enqueuePendingMessage(message)) {
738
+ this.log("Queued until Welcome:", message.type);
739
+ }
740
+ return;
741
+ }
742
+ if (message.type === "Hello") {
743
+ if (this.helloSent)
744
+ return;
745
+ this.helloSent = true;
746
+ }
747
+ this.log("Sending:", message.type);
748
+ this.ws.send(JSON.stringify(message));
749
+ }
750
+ else {
751
+ this.log("Cannot send, WebSocket not open");
752
+ }
753
+ }
754
+ ensureAuthenticated() {
755
+ if (this.connectionState !== "authenticated") {
756
+ throw new Error("Client is not authenticated");
757
+ }
758
+ }
759
+ setConnectionState(state) {
760
+ if (this.connectionState !== state) {
761
+ this.connectionState = state;
762
+ this.emit("stateChange", state);
763
+ }
764
+ }
765
+ startPingInterval() {
766
+ this.stopPingInterval();
767
+ this.pingTimer = setInterval(() => this.ping(), this.options.pingInterval);
768
+ }
769
+ stopPingInterval() {
770
+ if (this.pingTimer) {
771
+ clearInterval(this.pingTimer);
772
+ this.pingTimer = null;
773
+ }
774
+ }
775
+ enqueuePendingMessage(message) {
776
+ const maxPending = this.options.maxPendingMessages;
777
+ if (this.pendingMessages.length < maxPending) {
778
+ this.pendingMessages.push(message);
779
+ return true;
780
+ }
781
+ switch (this.options.pendingMessagesOverflowPolicy) {
782
+ case "drop_oldest":
783
+ if (maxPending === 0) {
784
+ this.log("Dropping pending message (maxPendingMessages=0):", message.type);
785
+ return false;
786
+ }
787
+ this.pendingMessages.shift();
788
+ this.pendingMessages.push(message);
789
+ this.log("Pending queue full, dropped oldest and queued newest:", message.type);
790
+ return true;
791
+ case "drop_newest":
792
+ this.log("Pending queue full, dropping newest message:", message.type);
793
+ return false;
794
+ case "throw":
795
+ this.emit("error", new Error(`Pending queue overflow (maxPendingMessages=${maxPending}) for message type=${message.type}`));
796
+ return false;
797
+ }
798
+ }
799
+ stopAutoReconnect() {
800
+ this.shouldReconnect = false;
801
+ if (this.reconnectTimer) {
802
+ clearTimeout(this.reconnectTimer);
803
+ this.reconnectTimer = null;
804
+ }
805
+ }
806
+ scheduleReconnect() {
807
+ if (!this.shouldReconnect)
808
+ return;
809
+ if (this.reconnectTimer)
810
+ return;
811
+ const baseDelay = Math.min(this.options.reconnectDelay * Math.pow(2, this.reconnectAttempts), this.options.maxReconnectDelay);
812
+ const jitterRatio = this.options.reconnectJitterRatio;
813
+ const jitterFactor = 1 + (Math.random() * 2 - 1) * jitterRatio;
814
+ const delay = Math.min(this.options.maxReconnectDelay, Math.max(0, Math.round(baseDelay * jitterFactor)));
815
+ this.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1})`);
816
+ this.reconnectTimer = setTimeout(() => {
817
+ this.reconnectTimer = null;
818
+ this.reconnectAttempts += 1;
819
+ this.doConnect();
820
+ }, delay);
821
+ }
822
+ cleanup() {
823
+ this.stopPingInterval();
824
+ if (this.reconnectTimer) {
825
+ clearTimeout(this.reconnectTimer);
826
+ this.reconnectTimer = null;
827
+ }
828
+ if (this.ws) {
829
+ this.ws.onopen = null;
830
+ this.ws.onmessage = null;
831
+ this.ws.onclose = null;
832
+ this.ws.onerror = null;
833
+ if (this.ws.readyState === WS_OPEN ||
834
+ this.ws.readyState === WS_CONNECTING) {
835
+ try {
836
+ this.ws.close();
837
+ }
838
+ catch {
839
+ // ignore
840
+ }
841
+ }
842
+ this.ws = null;
843
+ }
844
+ this.sessionId = null;
845
+ this.helloSent = false;
846
+ this.welcomeReceived = false;
847
+ this.startAuthSent = false;
848
+ this.pendingMessages = [];
849
+ this.setConnectionState("disconnected");
850
+ }
851
+ log(...args) {
852
+ if (this.options.debug) {
853
+ // eslint-disable-next-line no-console
854
+ console.log("[ActaWsClient]", ...args);
855
+ }
856
+ }
857
+ }