@lasersell/lasersell-sdk 0.1.0

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.
@@ -0,0 +1,3 @@
1
+ export * from "./client.js";
2
+ export * from "./proto.js";
3
+ export * from "./session.js";
@@ -0,0 +1,565 @@
1
+ export type MarketTypeMsg =
2
+ | "pump_fun"
3
+ | "pump_swap"
4
+ | "meteora_dbc"
5
+ | "meteora_damm_v2"
6
+ | "raydium_launchpad"
7
+ | "raydium_cpmm";
8
+
9
+ export interface PumpFunContextMsg {}
10
+
11
+ export interface PumpSwapContextMsg {
12
+ pool: string;
13
+ global_config?: string;
14
+ }
15
+
16
+ export interface MeteoraDbcContextMsg {
17
+ pool: string;
18
+ config: string;
19
+ quote_mint: string;
20
+ }
21
+
22
+ export interface MeteoraDammV2ContextMsg {
23
+ pool: string;
24
+ }
25
+
26
+ export interface RaydiumLaunchpadContextMsg {
27
+ pool: string;
28
+ config: string;
29
+ platform: string;
30
+ quote_mint: string;
31
+ user_quote_account: string;
32
+ }
33
+
34
+ export interface RaydiumCpmmContextMsg {
35
+ pool: string;
36
+ config: string;
37
+ quote_mint: string;
38
+ user_quote_account: string;
39
+ }
40
+
41
+ export interface MarketContextMsg {
42
+ market_type: MarketTypeMsg;
43
+ pumpfun?: PumpFunContextMsg;
44
+ pumpswap?: PumpSwapContextMsg;
45
+ meteora_dbc?: MeteoraDbcContextMsg;
46
+ meteora_damm_v2?: MeteoraDammV2ContextMsg;
47
+ raydium_launchpad?: RaydiumLaunchpadContextMsg;
48
+ raydium_cpmm?: RaydiumCpmmContextMsg;
49
+ }
50
+
51
+ export interface StrategyConfigMsg {
52
+ target_profit_pct: number;
53
+ stop_loss_pct: number;
54
+ }
55
+
56
+ export interface LimitsMsg {
57
+ hi_capacity: number;
58
+ pnl_flush_ms: number;
59
+ max_positions_per_session: number;
60
+ max_wallets_per_session: number;
61
+ max_positions_per_wallet: number;
62
+ max_sessions_per_api_key: number;
63
+ }
64
+
65
+ export interface PingClientMessage {
66
+ type: "ping";
67
+ client_time_ms: number;
68
+ }
69
+
70
+ export interface ConfigureClientMessage {
71
+ type: "configure";
72
+ wallet_pubkeys: string[];
73
+ strategy: StrategyConfigMsg;
74
+ }
75
+
76
+ export interface UpdateStrategyClientMessage {
77
+ type: "update_strategy";
78
+ strategy: StrategyConfigMsg;
79
+ }
80
+
81
+ export interface ClosePositionClientMessage {
82
+ type: "close_position";
83
+ position_id?: number;
84
+ token_account?: string;
85
+ }
86
+
87
+ export interface RequestExitSignalClientMessage {
88
+ type: "request_exit_signal";
89
+ position_id?: number;
90
+ token_account?: string;
91
+ slippage_bps?: number;
92
+ }
93
+
94
+ export type ClientMessage =
95
+ | PingClientMessage
96
+ | ConfigureClientMessage
97
+ | UpdateStrategyClientMessage
98
+ | ClosePositionClientMessage
99
+ | RequestExitSignalClientMessage;
100
+
101
+ export interface HelloOkServerMessage {
102
+ type: "hello_ok";
103
+ session_id: number;
104
+ server_time_ms: number;
105
+ limits: LimitsMsg;
106
+ }
107
+
108
+ export interface PongServerMessage {
109
+ type: "pong";
110
+ server_time_ms: number;
111
+ }
112
+
113
+ export interface ErrorServerMessage {
114
+ type: "error";
115
+ code: string;
116
+ message: string;
117
+ }
118
+
119
+ export interface PnlUpdateServerMessage {
120
+ type: "pnl_update";
121
+ position_id: number;
122
+ profit_units: number;
123
+ proceeds_units: number;
124
+ server_time_ms: number;
125
+ }
126
+
127
+ export interface BalanceUpdateServerMessage {
128
+ type: "balance_update";
129
+ wallet_pubkey: string;
130
+ mint: string;
131
+ token_account?: string;
132
+ token_program?: string;
133
+ tokens: number;
134
+ slot: number;
135
+ }
136
+
137
+ export interface PositionOpenedServerMessage {
138
+ type: "position_opened";
139
+ position_id: number;
140
+ wallet_pubkey: string;
141
+ mint: string;
142
+ token_account: string;
143
+ token_program?: string;
144
+ tokens: number;
145
+ entry_quote_units: number;
146
+ market_context?: MarketContextMsg;
147
+ slot: number;
148
+ }
149
+
150
+ export interface PositionClosedServerMessage {
151
+ type: "position_closed";
152
+ position_id: number;
153
+ wallet_pubkey: string;
154
+ mint: string;
155
+ token_account?: string;
156
+ reason: string;
157
+ slot: number;
158
+ }
159
+
160
+ export interface ExitSignalWithTxServerMessage {
161
+ type: "exit_signal_with_tx";
162
+ session_id: number;
163
+ position_id: number;
164
+ wallet_pubkey: string;
165
+ mint: string;
166
+ token_account?: string;
167
+ token_program?: string;
168
+ position_tokens: number;
169
+ profit_units: number;
170
+ reason: string;
171
+ triggered_at_ms: number;
172
+ market_context?: MarketContextMsg;
173
+ unsigned_tx_b64: string;
174
+ }
175
+
176
+ export type ServerMessage =
177
+ | HelloOkServerMessage
178
+ | PongServerMessage
179
+ | ErrorServerMessage
180
+ | PnlUpdateServerMessage
181
+ | BalanceUpdateServerMessage
182
+ | PositionOpenedServerMessage
183
+ | PositionClosedServerMessage
184
+ | ExitSignalWithTxServerMessage;
185
+
186
+ export function clientMessageFromText(text: string): ClientMessage {
187
+ let parsed: unknown;
188
+ try {
189
+ parsed = JSON.parse(text);
190
+ } catch (error) {
191
+ throw new Error(`decode client message JSON: ${stringifyError(error)}`);
192
+ }
193
+ return clientMessageFromUnknown(parsed);
194
+ }
195
+
196
+ export function clientMessageFromUnknown(value: unknown): ClientMessage {
197
+ const obj = asRecord(value, "client message");
198
+ const type = asString(obj.type, "client message.type");
199
+
200
+ switch (type) {
201
+ case "ping": {
202
+ return {
203
+ type: "ping",
204
+ client_time_ms: asNumber(obj.client_time_ms, "ping.client_time_ms"),
205
+ };
206
+ }
207
+ case "configure": {
208
+ const wallet_pubkeys = parseWalletPubkeys(obj);
209
+ return {
210
+ type: "configure",
211
+ wallet_pubkeys,
212
+ strategy: parseStrategyConfig(obj.strategy),
213
+ };
214
+ }
215
+ case "update_strategy": {
216
+ return {
217
+ type: "update_strategy",
218
+ strategy: parseStrategyConfig(obj.strategy),
219
+ };
220
+ }
221
+ case "close_position": {
222
+ return {
223
+ type: "close_position",
224
+ position_id: optionalNumber(obj.position_id, "close_position.position_id"),
225
+ token_account: optionalString(
226
+ obj.token_account,
227
+ "close_position.token_account",
228
+ ),
229
+ };
230
+ }
231
+ case "sell_now":
232
+ case "request_exit_signal": {
233
+ return {
234
+ type: "request_exit_signal",
235
+ position_id: optionalNumber(
236
+ obj.position_id,
237
+ "request_exit_signal.position_id",
238
+ ),
239
+ token_account: optionalString(
240
+ obj.token_account,
241
+ "request_exit_signal.token_account",
242
+ ),
243
+ slippage_bps: optionalNumber(
244
+ obj.slippage_bps,
245
+ "request_exit_signal.slippage_bps",
246
+ ),
247
+ };
248
+ }
249
+ default:
250
+ throw new Error(`unsupported client message type: ${type}`);
251
+ }
252
+ }
253
+
254
+ export function clientMessageToText(message: ClientMessage): string {
255
+ return JSON.stringify(message);
256
+ }
257
+
258
+ export function serverMessageFromText(text: string): ServerMessage {
259
+ let parsed: unknown;
260
+ try {
261
+ parsed = JSON.parse(text);
262
+ } catch (error) {
263
+ throw new Error(`decode server message JSON: ${stringifyError(error)}`);
264
+ }
265
+ return serverMessageFromUnknown(parsed);
266
+ }
267
+
268
+ export function serverMessageFromUnknown(value: unknown): ServerMessage {
269
+ const obj = asRecord(value, "server message");
270
+ const type = asString(obj.type, "server message.type");
271
+
272
+ switch (type) {
273
+ case "hello_ok": {
274
+ return {
275
+ type: "hello_ok",
276
+ session_id: asNumber(obj.session_id, "hello_ok.session_id"),
277
+ server_time_ms: asNumber(obj.server_time_ms, "hello_ok.server_time_ms"),
278
+ limits: parseLimits(obj.limits),
279
+ };
280
+ }
281
+ case "pong": {
282
+ return {
283
+ type: "pong",
284
+ server_time_ms: asNumber(obj.server_time_ms, "pong.server_time_ms"),
285
+ };
286
+ }
287
+ case "error": {
288
+ return {
289
+ type: "error",
290
+ code: asString(obj.code, "error.code"),
291
+ message: asString(obj.message, "error.message"),
292
+ };
293
+ }
294
+ case "pnl_update": {
295
+ return {
296
+ type: "pnl_update",
297
+ position_id: asNumber(obj.position_id, "pnl_update.position_id"),
298
+ profit_units: asNumber(obj.profit_units, "pnl_update.profit_units"),
299
+ proceeds_units: asNumber(obj.proceeds_units, "pnl_update.proceeds_units"),
300
+ server_time_ms: asNumber(obj.server_time_ms, "pnl_update.server_time_ms"),
301
+ };
302
+ }
303
+ case "balance_update": {
304
+ return {
305
+ type: "balance_update",
306
+ wallet_pubkey: asString(obj.wallet_pubkey, "balance_update.wallet_pubkey"),
307
+ mint: asString(obj.mint, "balance_update.mint"),
308
+ token_account: optionalString(
309
+ obj.token_account,
310
+ "balance_update.token_account",
311
+ ),
312
+ token_program: optionalString(
313
+ obj.token_program,
314
+ "balance_update.token_program",
315
+ ),
316
+ tokens: asNumber(obj.tokens, "balance_update.tokens"),
317
+ slot: asNumber(obj.slot, "balance_update.slot"),
318
+ };
319
+ }
320
+ case "position_opened": {
321
+ return {
322
+ type: "position_opened",
323
+ position_id: asNumber(obj.position_id, "position_opened.position_id"),
324
+ wallet_pubkey: asString(
325
+ obj.wallet_pubkey,
326
+ "position_opened.wallet_pubkey",
327
+ ),
328
+ mint: asString(obj.mint, "position_opened.mint"),
329
+ token_account: asString(obj.token_account, "position_opened.token_account"),
330
+ token_program: optionalString(
331
+ obj.token_program,
332
+ "position_opened.token_program",
333
+ ),
334
+ tokens: asNumber(obj.tokens, "position_opened.tokens"),
335
+ entry_quote_units: asNumber(
336
+ obj.entry_quote_units,
337
+ "position_opened.entry_quote_units",
338
+ ),
339
+ market_context: optionalMarketContext(
340
+ obj.market_context,
341
+ "position_opened.market_context",
342
+ ),
343
+ slot: asNumber(obj.slot, "position_opened.slot"),
344
+ };
345
+ }
346
+ case "position_closed": {
347
+ return {
348
+ type: "position_closed",
349
+ position_id: asNumber(obj.position_id, "position_closed.position_id"),
350
+ wallet_pubkey: asString(
351
+ obj.wallet_pubkey,
352
+ "position_closed.wallet_pubkey",
353
+ ),
354
+ mint: asString(obj.mint, "position_closed.mint"),
355
+ token_account: optionalString(
356
+ obj.token_account,
357
+ "position_closed.token_account",
358
+ ),
359
+ reason: asString(obj.reason, "position_closed.reason"),
360
+ slot: asNumber(obj.slot, "position_closed.slot"),
361
+ };
362
+ }
363
+ case "exit_signal_with_tx": {
364
+ return {
365
+ type: "exit_signal_with_tx",
366
+ session_id: asNumber(obj.session_id, "exit_signal_with_tx.session_id"),
367
+ position_id: asNumber(obj.position_id, "exit_signal_with_tx.position_id"),
368
+ wallet_pubkey: asString(
369
+ obj.wallet_pubkey,
370
+ "exit_signal_with_tx.wallet_pubkey",
371
+ ),
372
+ mint: asString(obj.mint, "exit_signal_with_tx.mint"),
373
+ token_account: optionalString(
374
+ obj.token_account,
375
+ "exit_signal_with_tx.token_account",
376
+ ),
377
+ token_program: optionalString(
378
+ obj.token_program,
379
+ "exit_signal_with_tx.token_program",
380
+ ),
381
+ position_tokens: asNumber(
382
+ obj.position_tokens,
383
+ "exit_signal_with_tx.position_tokens",
384
+ ),
385
+ profit_units: asNumber(obj.profit_units, "exit_signal_with_tx.profit_units"),
386
+ reason: asString(obj.reason, "exit_signal_with_tx.reason"),
387
+ triggered_at_ms: asNumber(
388
+ obj.triggered_at_ms,
389
+ "exit_signal_with_tx.triggered_at_ms",
390
+ ),
391
+ market_context: optionalMarketContext(
392
+ obj.market_context,
393
+ "exit_signal_with_tx.market_context",
394
+ ),
395
+ unsigned_tx_b64: asString(
396
+ obj.unsigned_tx_b64,
397
+ "exit_signal_with_tx.unsigned_tx_b64",
398
+ ),
399
+ };
400
+ }
401
+ default:
402
+ throw new Error(`unsupported server message type: ${type}`);
403
+ }
404
+ }
405
+
406
+ export function serverMessageToText(message: ServerMessage): string {
407
+ return JSON.stringify(message);
408
+ }
409
+
410
+ function parseWalletPubkeys(obj: Record<string, unknown>): string[] {
411
+ const legacyWalletPubkey = obj.wallet_pubkey;
412
+ const wallets = obj.wallet_pubkeys ?? legacyWalletPubkey;
413
+
414
+ if (typeof wallets === "string") {
415
+ return [wallets];
416
+ }
417
+
418
+ if (Array.isArray(wallets)) {
419
+ const parsed = wallets.map((item, idx) =>
420
+ asString(item, `configure.wallet_pubkeys[${idx}]`),
421
+ );
422
+ return parsed;
423
+ }
424
+
425
+ throw new Error(
426
+ "configure.wallet_pubkeys must be a string or string[] (legacy wallet_pubkey is supported)",
427
+ );
428
+ }
429
+
430
+ function parseStrategyConfig(value: unknown): StrategyConfigMsg {
431
+ const obj = asRecord(value, "strategy");
432
+
433
+ return {
434
+ target_profit_pct: asNumber(
435
+ obj.target_profit_pct,
436
+ "strategy.target_profit_pct",
437
+ ),
438
+ stop_loss_pct: asNumber(obj.stop_loss_pct, "strategy.stop_loss_pct"),
439
+ };
440
+ }
441
+
442
+ function parseLimits(value: unknown): LimitsMsg {
443
+ const obj = asRecord(value, "limits");
444
+
445
+ return {
446
+ hi_capacity: asNumber(obj.hi_capacity, "limits.hi_capacity"),
447
+ pnl_flush_ms: asNumber(obj.pnl_flush_ms, "limits.pnl_flush_ms"),
448
+ max_positions_per_session: asNumber(
449
+ obj.max_positions_per_session,
450
+ "limits.max_positions_per_session",
451
+ ),
452
+ max_wallets_per_session: optionalNumber(
453
+ obj.max_wallets_per_session,
454
+ "limits.max_wallets_per_session",
455
+ ) ?? 0,
456
+ max_positions_per_wallet:
457
+ optionalNumber(
458
+ obj.max_positions_per_wallet,
459
+ "limits.max_positions_per_wallet",
460
+ ) ?? 0,
461
+ max_sessions_per_api_key:
462
+ optionalNumber(
463
+ obj.max_sessions_per_api_key,
464
+ "limits.max_sessions_per_api_key",
465
+ ) ?? 0,
466
+ };
467
+ }
468
+
469
+ function optionalMarketContext(
470
+ value: unknown,
471
+ path: string,
472
+ ): MarketContextMsg | undefined {
473
+ if (value === undefined || value === null) {
474
+ return undefined;
475
+ }
476
+
477
+ return parseMarketContext(value, path);
478
+ }
479
+
480
+ function parseMarketContext(value: unknown, path: string): MarketContextMsg {
481
+ const obj = asRecord(value, path);
482
+
483
+ return {
484
+ market_type: parseMarketType(obj.market_type, `${path}.market_type`),
485
+ pumpfun: optionalObject(obj.pumpfun, `${path}.pumpfun`),
486
+ pumpswap: optionalObject(obj.pumpswap, `${path}.pumpswap`),
487
+ meteora_dbc: optionalObject(obj.meteora_dbc, `${path}.meteora_dbc`),
488
+ meteora_damm_v2: optionalObject(
489
+ obj.meteora_damm_v2,
490
+ `${path}.meteora_damm_v2`,
491
+ ),
492
+ raydium_launchpad: optionalObject(
493
+ obj.raydium_launchpad,
494
+ `${path}.raydium_launchpad`,
495
+ ),
496
+ raydium_cpmm: optionalObject(obj.raydium_cpmm, `${path}.raydium_cpmm`),
497
+ };
498
+ }
499
+
500
+ function parseMarketType(value: unknown, path: string): MarketTypeMsg {
501
+ const str = asString(value, path);
502
+ switch (str) {
503
+ case "pump_fun":
504
+ case "pump_swap":
505
+ case "meteora_dbc":
506
+ case "meteora_damm_v2":
507
+ case "raydium_launchpad":
508
+ case "raydium_cpmm":
509
+ return str;
510
+ default:
511
+ throw new Error(`invalid market type at ${path}: ${str}`);
512
+ }
513
+ }
514
+
515
+ function optionalString(value: unknown, path: string): string | undefined {
516
+ if (value === undefined || value === null) {
517
+ return undefined;
518
+ }
519
+ return asString(value, path);
520
+ }
521
+
522
+ function optionalNumber(value: unknown, path: string): number | undefined {
523
+ if (value === undefined || value === null) {
524
+ return undefined;
525
+ }
526
+ return asNumber(value, path);
527
+ }
528
+
529
+ function optionalObject<T extends object>(
530
+ value: unknown,
531
+ path: string,
532
+ ): T | undefined {
533
+ if (value === undefined || value === null) {
534
+ return undefined;
535
+ }
536
+ return asRecord(value, path) as T;
537
+ }
538
+
539
+ function asRecord(value: unknown, path: string): Record<string, unknown> {
540
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
541
+ throw new Error(`${path} must be an object`);
542
+ }
543
+ return value as Record<string, unknown>;
544
+ }
545
+
546
+ function asString(value: unknown, path: string): string {
547
+ if (typeof value !== "string") {
548
+ throw new Error(`${path} must be a string`);
549
+ }
550
+ return value;
551
+ }
552
+
553
+ function asNumber(value: unknown, path: string): number {
554
+ if (typeof value !== "number" || !Number.isFinite(value)) {
555
+ throw new Error(`${path} must be a finite number`);
556
+ }
557
+ return value;
558
+ }
559
+
560
+ function stringifyError(error: unknown): string {
561
+ if (error instanceof Error) {
562
+ return `${error.name}: ${error.message}`;
563
+ }
564
+ return String(error);
565
+ }