openbook-cli 1.0.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.
package/src/index.ts ADDED
@@ -0,0 +1,642 @@
1
+ import { Connection, PublicKey } from "@solana/web3.js";
2
+ import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
3
+ import { Market } from "@project-serum/serum";
4
+
5
+ // Market information
6
+ const MARKET_ADDRESS = "Cw35vJ7ecmnwc2jPumgfhDzUuJ1fmrytuRopBF5JUXrq";
7
+
8
+ // Program IDs
9
+ const OPENBOOK_PROGRAM_ID = "srmqPvymJeFKQ4zGQed1GFppgkRHL9kaELCbyksJtPX";
10
+ const SERUM_PROGRAM_ID = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin";
11
+
12
+ // Connection to Solana
13
+ const connection = new Connection("https://api.mainnet-beta.solana.com", "confirmed");
14
+
15
+ interface OrderBookLevel {
16
+ price: number;
17
+ size: number;
18
+ side: 'bid' | 'ask';
19
+ }
20
+
21
+ interface MarketInfo {
22
+ address: string;
23
+ baseMint: string;
24
+ quoteMint: string;
25
+ baseSymbol: string;
26
+ quoteSymbol: string;
27
+ minOrderSize: number;
28
+ priceTick: number;
29
+ eventQueueLength: number;
30
+ requestQueueLength: number;
31
+ bidsLength: number;
32
+ asksLength: number;
33
+ }
34
+
35
+ interface TokenMetadata {
36
+ symbol: string;
37
+ name: string;
38
+ decimals: number;
39
+ }
40
+
41
+ // Known markets and symbols - will be loaded from JSON file
42
+ let KNOWN_MARKETS: { [key: string]: any } = {};
43
+ let KNOWN_TOKEN_SYMBOLS: { [key: string]: string } = {};
44
+
45
+ async function getTokenMetadata(mintAddress: string): Promise<TokenMetadata> {
46
+ try {
47
+ const mintPubkey = new PublicKey(mintAddress);
48
+
49
+ // First check if we have a known symbol
50
+ const knownSymbol = KNOWN_TOKEN_SYMBOLS[mintAddress as keyof typeof KNOWN_TOKEN_SYMBOLS];
51
+ if (knownSymbol) {
52
+ return {
53
+ symbol: knownSymbol,
54
+ name: knownSymbol,
55
+ decimals: 6 // Default for most tokens
56
+ };
57
+ }
58
+
59
+ // Get token account info to extract decimals
60
+ const tokenInfo = await connection.getParsedAccountInfo(mintPubkey);
61
+
62
+ if (!tokenInfo.value) {
63
+ throw new Error("Token account not found");
64
+ }
65
+
66
+ const parsedData = tokenInfo.value.data as any;
67
+ const decimals = parsedData.parsed.info.decimals;
68
+
69
+ // Try to get Metaplex metadata
70
+ try {
71
+ const [metadataAddress] = PublicKey.findProgramAddressSync(
72
+ [
73
+ Buffer.from("metadata"),
74
+ new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s").toBuffer(),
75
+ mintPubkey.toBuffer()
76
+ ],
77
+ new PublicKey("metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s")
78
+ );
79
+
80
+ const metadataAccount = await connection.getAccountInfo(metadataAddress);
81
+
82
+ if (metadataAccount) {
83
+ // Parse Metaplex metadata
84
+ const metadata = JSON.parse(metadataAccount.data.toString());
85
+ return {
86
+ symbol: metadata.data.symbol || "UNKNOWN",
87
+ name: metadata.data.name || "Unknown Token",
88
+ decimals: decimals
89
+ };
90
+ }
91
+ } catch (error) {
92
+ console.log(`No Metaplex metadata found for ${mintAddress}`);
93
+ }
94
+
95
+ // Fallback: use mint address as symbol
96
+ return {
97
+ symbol: mintAddress.slice(0, 8),
98
+ name: "Unknown Token",
99
+ decimals: decimals
100
+ };
101
+
102
+ } catch (error) {
103
+ console.error(`Error fetching token metadata for ${mintAddress}:`, error);
104
+ return {
105
+ symbol: mintAddress.slice(0, 8),
106
+ name: "Unknown Token",
107
+ decimals: 6
108
+ };
109
+ }
110
+ }
111
+
112
+ async function getMarketInfo(marketAddress: string, useSerum: boolean = false): Promise<MarketInfo> {
113
+ try {
114
+ // First try to get from known markets
115
+ const marketData = KNOWN_MARKETS[marketAddress as keyof typeof KNOWN_MARKETS];
116
+
117
+ if (marketData) {
118
+ // Fetch real token metadata from blockchain
119
+ const baseMetadata = await getTokenMetadata(marketData.baseMint);
120
+ const quoteMetadata = await getTokenMetadata(marketData.quoteMint);
121
+
122
+ return {
123
+ address: marketAddress,
124
+ baseMint: marketData.baseMint,
125
+ quoteMint: marketData.quoteMint,
126
+ baseSymbol: baseMetadata.symbol,
127
+ quoteSymbol: quoteMetadata.symbol,
128
+ minOrderSize: marketData.minOrderSize,
129
+ priceTick: marketData.priceTick,
130
+ eventQueueLength: marketData.eventQueueLength,
131
+ requestQueueLength: marketData.requestQueueLength,
132
+ bidsLength: marketData.bidsLength,
133
+ asksLength: marketData.asksLength
134
+ };
135
+ } else {
136
+ // Load market directly from blockchain
137
+ const market = await loadOpenBookMarket(marketAddress, useSerum);
138
+
139
+ // Fetch real token metadata from blockchain
140
+ const baseMetadata = await getTokenMetadata(market.baseMintAddress.toString());
141
+ const quoteMetadata = await getTokenMetadata(market.quoteMintAddress.toString());
142
+
143
+ return {
144
+ address: marketAddress,
145
+ baseMint: market.baseMintAddress.toString(),
146
+ quoteMint: market.quoteMintAddress.toString(),
147
+ baseSymbol: baseMetadata.symbol,
148
+ quoteSymbol: quoteMetadata.symbol,
149
+ minOrderSize: 1, // Default values for unknown markets
150
+ priceTick: 0.0001,
151
+ eventQueueLength: 2978,
152
+ requestQueueLength: 63,
153
+ bidsLength: 909,
154
+ asksLength: 909
155
+ };
156
+ }
157
+ } catch (error) {
158
+ console.error("Error fetching market info:", error);
159
+ throw error;
160
+ }
161
+ }
162
+
163
+ async function getMarketAccounts(marketAddress: string, useSerum: boolean = false) {
164
+ try {
165
+ const marketPubkey = new PublicKey(marketAddress);
166
+ const marketAccount = await connection.getAccountInfo(marketPubkey);
167
+
168
+ if (!marketAccount) {
169
+ throw new Error("Market account not found");
170
+ }
171
+
172
+ console.log("Market account found!");
173
+ console.log("Owner:", marketAccount.owner.toString());
174
+ console.log("Data length:", marketAccount.data.length);
175
+
176
+ // Check against the appropriate program ID
177
+ const expectedProgramId = useSerum ? SERUM_PROGRAM_ID : OPENBOOK_PROGRAM_ID;
178
+ const programName = useSerum ? "Serum" : "OpenBook";
179
+
180
+ if (marketAccount.owner.toString() !== expectedProgramId) {
181
+ throw new Error(`Market not owned by ${programName} program. Owner: ${marketAccount.owner.toString()}`);
182
+ }
183
+
184
+ return marketAccount;
185
+ } catch (error) {
186
+ console.error("Error fetching market accounts:", error);
187
+ throw error;
188
+ }
189
+ }
190
+
191
+ async function loadOpenBookMarket(marketAddress: string, useSerum: boolean = false): Promise<Market> {
192
+ try {
193
+ const marketPubkey = new PublicKey(marketAddress);
194
+
195
+ // Use the appropriate program ID based on mode
196
+ const programId = useSerum ? SERUM_PROGRAM_ID : OPENBOOK_PROGRAM_ID;
197
+
198
+ // Load the market using the appropriate program ID
199
+ const market = await Market.load(
200
+ connection,
201
+ marketPubkey,
202
+ {},
203
+ new PublicKey(programId)
204
+ );
205
+
206
+ const programName = useSerum ? "Serum" : "OpenBook";
207
+ console.log(`āœ… ${programName} market loaded successfully!`);
208
+
209
+ return market;
210
+ } catch (error) {
211
+ const programName = useSerum ? "Serum" : "OpenBook";
212
+ console.error(`Error loading ${programName} market:`, error);
213
+ throw error;
214
+ }
215
+ }
216
+
217
+ async function getRealOrderBook(marketAddress: string, depth: number = 20, useSerum: boolean = false): Promise<{
218
+ bids: OrderBookLevel[];
219
+ asks: OrderBookLevel[];
220
+ }> {
221
+ try {
222
+ const programName = useSerum ? "Serum" : "OpenBook";
223
+ console.log(`šŸ”„ Fetching real order book from ${programName}...`);
224
+
225
+ // Load the market with appropriate program
226
+ const market = await loadOpenBookMarket(marketAddress, useSerum);
227
+
228
+ // Use the correct Serum SDK methods
229
+ console.log("šŸ“Š Loading bids and asks...");
230
+ const bids = await market.loadBids(connection);
231
+ const asks = await market.loadAsks(connection);
232
+
233
+ // Access the orderbook data
234
+ const bidsArray = bids.getL2(20); // Get top 20 bids
235
+ const asksArray = asks.getL2(20); // Get top 20 asks
236
+
237
+ console.log("šŸ“Š Orderbook loaded successfully!");
238
+ console.log("Bids count:", bidsArray.length);
239
+ console.log("Asks count:", asksArray.length);
240
+
241
+ // Process bids (buy orders)
242
+ const bidOrders: OrderBookLevel[] = bidsArray.map((bid: any) => ({
243
+ price: bid[0], // Price is first element
244
+ size: bid[1], // Size is second element
245
+ side: 'bid' as const
246
+ }));
247
+
248
+ // Process asks (sell orders)
249
+ const askOrders: OrderBookLevel[] = asksArray.map((ask: any) => ({
250
+ price: ask[0], // Price is first element
251
+ size: ask[1], // Size is second element
252
+ side: 'ask' as const
253
+ }));
254
+
255
+ console.log(`āœ… Real order book fetched: ${bidOrders.length} bids, ${askOrders.length} asks`);
256
+
257
+ return { bids: bidOrders, asks: askOrders };
258
+
259
+ } catch (error) {
260
+ console.error("āŒ Error fetching real order book:", error);
261
+ throw error;
262
+ }
263
+ }
264
+
265
+ async function getOrderBook(marketAddress: string, depth: number = 20): Promise<{
266
+ bids: OrderBookLevel[];
267
+ asks: OrderBookLevel[];
268
+ }> {
269
+ try {
270
+ // First, get the market account to verify it's a valid OpenBook market
271
+ const marketAccount = await getMarketAccounts(marketAddress);
272
+
273
+ // Get real order book data from the blockchain
274
+ return await getRealOrderBook(marketAddress, depth);
275
+
276
+ } catch (error) {
277
+ console.error("Error fetching order book from blockchain:", error);
278
+ throw error;
279
+ }
280
+ }
281
+
282
+ async function getMarketStats(marketAddress: string, useSerum: boolean = false): Promise<{
283
+ totalBids: number;
284
+ totalAsks: number;
285
+ bestBid: number | null;
286
+ bestAsk: number | null;
287
+ spread: number | null;
288
+ spreadPercentage: number | null;
289
+ }> {
290
+ try {
291
+ const { bids, asks } = await getRealOrderBook(marketAddress, 1, useSerum);
292
+
293
+ const bestBid = bids.length > 0 ? bids[0].price : null;
294
+ const bestAsk = asks.length > 0 ? asks[0].price : null;
295
+
296
+ const spread = bestBid && bestAsk ? bestAsk - bestBid : null;
297
+ const spreadPercentage = spread && bestBid ? (spread / bestBid) * 100 : null;
298
+
299
+ return {
300
+ totalBids: bids.length,
301
+ totalAsks: asks.length,
302
+ bestBid,
303
+ bestAsk,
304
+ spread,
305
+ spreadPercentage
306
+ };
307
+ } catch (error) {
308
+ console.error("Error fetching market stats:", error);
309
+ throw error;
310
+ }
311
+ }
312
+
313
+ async function displayOrderBook(marketAddress: string, depth: number = 10, useSerum: boolean = false): Promise<void> {
314
+ try {
315
+ const marketData = KNOWN_MARKETS[marketAddress as keyof typeof KNOWN_MARKETS];
316
+ let marketName = marketData?.name || marketAddress;
317
+
318
+ // Try to get real symbols for the market name
319
+ try {
320
+ const marketInfo = await getMarketInfo(marketAddress, useSerum);
321
+ marketName = `${marketInfo.baseSymbol}/${marketInfo.quoteSymbol}`;
322
+ } catch (error) {
323
+ // Use fallback name if metadata fetch fails
324
+ }
325
+
326
+ console.log(`\nšŸ“Š Order Book for Market: ${marketName}`);
327
+ console.log("=" .repeat(60));
328
+
329
+ const { bids, asks } = await getRealOrderBook(marketAddress, depth, useSerum);
330
+
331
+ console.log("\nšŸ”“ ASKS (Sell Orders):");
332
+ console.log("Price\t\tSize");
333
+ console.log("-" .repeat(30));
334
+
335
+ // Display asks in reverse order (highest price first)
336
+ asks.slice().reverse().forEach((ask) => {
337
+ console.log(`${ask.price.toFixed(4)}\t\t${ask.size.toFixed(4)}`);
338
+ });
339
+
340
+ console.log("\n🟢 BIDS (Buy Orders):");
341
+ console.log("Price\t\tSize");
342
+ console.log("-" .repeat(30));
343
+
344
+ bids.forEach((bid) => {
345
+ console.log(`${bid.price.toFixed(4)}\t\t${bid.size.toFixed(4)}`);
346
+ });
347
+
348
+ // Calculate market stats from the order book data we already have
349
+ const bestBid = bids.length > 0 ? bids[0].price : null;
350
+ const bestAsk = asks.length > 0 ? asks[0].price : null;
351
+ const spread = bestBid && bestAsk ? bestAsk - bestBid : null;
352
+ const spreadPercentage = spread && bestBid ? (spread / bestBid) * 100 : null;
353
+
354
+ console.log("\nšŸ“ˆ Market Stats:");
355
+ console.log(`Total Bids: ${bids.length}`);
356
+ console.log(`Total Asks: ${asks.length}`);
357
+ console.log(`Best Bid: ${bestBid?.toFixed(4) || 'N/A'}`);
358
+ console.log(`Best Ask: ${bestAsk?.toFixed(4) || 'N/A'}`);
359
+ console.log(`Spread: ${spread?.toFixed(4) || 'N/A'}`);
360
+ console.log(`Spread %: ${spreadPercentage?.toFixed(2) || 'N/A'}%`);
361
+
362
+ } catch (error) {
363
+ console.error("Error displaying order book:", error);
364
+ }
365
+ }
366
+
367
+ async function displayMarketInfo(marketAddress: string, useSerum: boolean = false): Promise<void> {
368
+ try {
369
+ const marketData = KNOWN_MARKETS[marketAddress as keyof typeof KNOWN_MARKETS];
370
+ let marketName = marketData?.name || marketAddress;
371
+
372
+ // Try to get real symbols for the market name
373
+ try {
374
+ const marketInfo = await getMarketInfo(marketAddress, useSerum);
375
+ marketName = `${marketInfo.baseSymbol}/${marketInfo.quoteSymbol}`;
376
+ } catch (error) {
377
+ // Use fallback name if metadata fetch fails
378
+ }
379
+
380
+ console.log(`\nšŸŖ Market Information for: ${marketName}`);
381
+ console.log("=" .repeat(60));
382
+
383
+ const marketInfo = await getMarketInfo(marketAddress, useSerum);
384
+
385
+ console.log(`Market Address: ${marketInfo.address}`);
386
+ console.log(`Base Mint: ${marketInfo.baseMint} (${marketInfo.baseSymbol})`);
387
+ console.log(`Quote Mint: ${marketInfo.quoteMint} (${marketInfo.quoteSymbol})`);
388
+ console.log(`Min Order Size: ${marketInfo.minOrderSize}`);
389
+ console.log(`Price Tick: ${marketInfo.priceTick}`);
390
+ console.log(`Event Queue Length: ${marketInfo.eventQueueLength}`);
391
+ console.log(`Request Queue Length: ${marketInfo.requestQueueLength}`);
392
+ console.log(`Bids Length: ${marketInfo.bidsLength}`);
393
+ console.log(`Asks Length: ${marketInfo.asksLength}`);
394
+
395
+ // Verify the market account
396
+ try {
397
+ await getMarketAccounts(marketAddress, useSerum);
398
+ const programName = useSerum ? "Serum" : "OpenBook";
399
+ console.log(`āœ… Market verified as ${programName} market`);
400
+ } catch (error) {
401
+ console.log(`āŒ Market verification failed: ${error}`);
402
+ }
403
+
404
+ } catch (error) {
405
+ console.error("Error displaying market info:", error);
406
+ }
407
+ }
408
+
409
+ function listKnownMarkets(useSerum: boolean = false): void {
410
+ const programType = useSerum ? "Serum" : "OpenBook";
411
+ console.log(`\nšŸ“‹ Known ${programType} Markets:`);
412
+ console.log("=" .repeat(60));
413
+
414
+ Object.entries(KNOWN_MARKETS).forEach(([address, market]) => {
415
+ console.log(`${market.name}: ${address}`);
416
+ });
417
+ }
418
+
419
+ // Function to add market to known markets
420
+ async function addMarketToKnownMarkets(marketAddress: string, useSerum: boolean = false): Promise<void> {
421
+ try {
422
+ console.log(`šŸ” Analyzing market: ${marketAddress}`);
423
+
424
+ // Load the market to get base and quote mints
425
+ const market = await loadOpenBookMarket(marketAddress, useSerum);
426
+
427
+ // Get token metadata for base and quote tokens
428
+ const baseMetadata = await getTokenMetadata(market.baseMintAddress.toString());
429
+ const quoteMetadata = await getTokenMetadata(market.quoteMintAddress.toString());
430
+
431
+ // Create market info
432
+ const marketInfo = {
433
+ name: `${baseMetadata.symbol}/${quoteMetadata.symbol}`,
434
+ baseMint: market.baseMintAddress.toString(),
435
+ quoteMint: market.quoteMintAddress.toString(),
436
+ minOrderSize: 1,
437
+ priceTick: 0.0001,
438
+ eventQueueLength: 2978,
439
+ requestQueueLength: 63,
440
+ bidsLength: 909,
441
+ asksLength: 909
442
+ };
443
+
444
+ // Add to known markets
445
+ (KNOWN_MARKETS as any)[marketAddress] = marketInfo;
446
+
447
+ // Add token symbols to known symbols if not already present
448
+ if (!KNOWN_TOKEN_SYMBOLS[market.baseMintAddress.toString() as keyof typeof KNOWN_TOKEN_SYMBOLS]) {
449
+ (KNOWN_TOKEN_SYMBOLS as any)[market.baseMintAddress.toString()] = baseMetadata.symbol;
450
+ }
451
+
452
+ if (!KNOWN_TOKEN_SYMBOLS[market.quoteMintAddress.toString() as keyof typeof KNOWN_TOKEN_SYMBOLS]) {
453
+ (KNOWN_TOKEN_SYMBOLS as any)[market.quoteMintAddress.toString()] = quoteMetadata.symbol;
454
+ }
455
+
456
+ console.log("āœ… Market added to known markets!");
457
+ console.log(`Market: ${marketInfo.name}`);
458
+ console.log(`Base Token: ${baseMetadata.symbol} (${market.baseMintAddress.toString()})`);
459
+ console.log(`Quote Token: ${quoteMetadata.symbol} (${market.quoteMintAddress.toString()})`);
460
+
461
+ // Display the updated known markets
462
+ console.log("\nšŸ“‹ Updated Known Markets:");
463
+ Object.entries(KNOWN_MARKETS).forEach(([address, info]) => {
464
+ console.log(`${info.name}: ${address}`);
465
+ });
466
+
467
+ } catch (error) {
468
+ console.error("āŒ Error adding market to known markets:", error);
469
+ throw error;
470
+ }
471
+ }
472
+
473
+ // Function to save known markets to a file (for persistence)
474
+ function saveKnownMarketsToFile(useSerum: boolean = false): void {
475
+ try {
476
+ const fs = require('fs');
477
+ const path = require('path');
478
+
479
+ const marketsData = {
480
+ markets: KNOWN_MARKETS,
481
+ symbols: KNOWN_TOKEN_SYMBOLS
482
+ };
483
+
484
+ // Choose the appropriate file based on program type
485
+ const fileName = useSerum ? 'known_serum_markets.json' : 'known_openbook_markets.json';
486
+ const filePath = path.join(__dirname, fileName);
487
+ fs.writeFileSync(filePath, JSON.stringify(marketsData, null, 2));
488
+
489
+ const programType = useSerum ? 'Serum' : 'OpenBook';
490
+ console.log(`šŸ’¾ Known ${programType} markets saved to ${fileName}`);
491
+ } catch (error) {
492
+ console.error("āŒ Error saving known markets:", error);
493
+ }
494
+ }
495
+
496
+ // Function to load known markets from file
497
+ function loadKnownMarketsFromFile(useSerum: boolean = false): void {
498
+ try {
499
+ const fs = require('fs');
500
+ const path = require('path');
501
+
502
+ // Choose the appropriate file based on program type
503
+ const fileName = useSerum ? 'known_serum_markets.json' : 'known_openbook_markets.json';
504
+ const filePath = path.join(__dirname, fileName);
505
+
506
+ if (fs.existsSync(filePath)) {
507
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
508
+
509
+ // Load data from file
510
+ KNOWN_MARKETS = data.markets || {};
511
+ KNOWN_TOKEN_SYMBOLS = data.symbols || {};
512
+
513
+ const programType = useSerum ? 'Serum' : 'OpenBook';
514
+ console.log(`šŸ“‚ Loaded known ${programType} markets from file`);
515
+ console.log(`šŸ“Š Found ${Object.keys(KNOWN_MARKETS).length} markets and ${Object.keys(KNOWN_TOKEN_SYMBOLS).length} token symbols`);
516
+ } else {
517
+ const programType = useSerum ? 'Serum' : 'OpenBook';
518
+ console.log(`ā„¹ļø No saved ${programType} markets file found, starting with empty markets`);
519
+ // Initialize with empty objects
520
+ KNOWN_MARKETS = {};
521
+ KNOWN_TOKEN_SYMBOLS = {};
522
+ }
523
+ } catch (error) {
524
+ const programType = useSerum ? 'Serum' : 'OpenBook';
525
+ console.log(`ā„¹ļø Error loading ${programType} markets file, starting with empty markets`);
526
+ KNOWN_MARKETS = {};
527
+ KNOWN_TOKEN_SYMBOLS = {};
528
+ }
529
+ }
530
+
531
+ // Main function to run the fetcher
532
+ async function main() {
533
+ try {
534
+ console.log("šŸš€ OpenBook Order Fetcher (Real Data)");
535
+ console.log("=" .repeat(60));
536
+
537
+ // Check if a specific market address was provided
538
+ const args = process.argv.slice(2);
539
+ const targetMarket = args[0] || MARKET_ADDRESS;
540
+
541
+ // Check for --serum flag first
542
+ const useSerumFlag = args.includes('--serum') || args.includes('-s');
543
+
544
+ // Auto-detect program type based on market ownership
545
+ let useSerum = useSerumFlag;
546
+ let detectedProgram = useSerumFlag ? "Serum" : "OpenBook";
547
+
548
+ // If a specific market is provided, try to detect its program
549
+ if (args[0] && !args.includes('--list') && !args.includes('-l')) {
550
+ try {
551
+ const marketPubkey = new PublicKey(targetMarket);
552
+ const marketAccount = await connection.getAccountInfo(marketPubkey);
553
+
554
+ if (marketAccount) {
555
+ const owner = marketAccount.owner.toString();
556
+ if (owner === SERUM_PROGRAM_ID) {
557
+ useSerum = true;
558
+ detectedProgram = "Serum";
559
+ } else if (owner === OPENBOOK_PROGRAM_ID) {
560
+ useSerum = false;
561
+ detectedProgram = "OpenBook";
562
+ }
563
+ console.log(`šŸ” Auto-detected: ${detectedProgram} market`);
564
+ }
565
+ } catch (error) {
566
+ console.log("ā„¹ļø Could not auto-detect program, using OpenBook as default");
567
+ }
568
+ }
569
+
570
+ // Load known markets from appropriate file
571
+ loadKnownMarketsFromFile(useSerum);
572
+
573
+ if (args.includes('--list') || args.includes('-l')) {
574
+ listKnownMarkets(useSerum);
575
+ return;
576
+ }
577
+
578
+ // Check if --add flag is present
579
+ const shouldAdd = args.includes('--add') || args.includes('-a');
580
+
581
+ // If --add flag is present and a market address is provided
582
+ if (shouldAdd && args[0] && args[0] !== '--add' && args[0] !== '-a' && args[0] !== '--serum' && args[0] !== '-s') {
583
+ console.log("āž• Adding market to known markets...");
584
+ await addMarketToKnownMarkets(targetMarket, useSerum);
585
+
586
+ // Save to appropriate file
587
+ saveKnownMarketsToFile(useSerum);
588
+
589
+ console.log("\nāœ… Market added successfully!");
590
+ return;
591
+ }
592
+
593
+ // Display market information
594
+ await displayMarketInfo(targetMarket, useSerum);
595
+
596
+ // Display order book
597
+ await displayOrderBook(targetMarket, 15, useSerum);
598
+
599
+ console.log("\nāœ… Fetch completed successfully!");
600
+ console.log("\nšŸ“ Note: This implementation uses:");
601
+ console.log("1. Real OpenBook market loading via Serum SDK");
602
+ console.log("2. Direct blockchain order book fetching");
603
+ console.log("3. Real bids and asks from the market");
604
+ console.log("\nUsage:");
605
+ console.log(" npm run openbook # Fetch default market");
606
+ console.log(" npm run openbook <market_address> # Fetch market (auto-detects program)");
607
+ console.log(" npm run openbook <market_address> --add # Add market (auto-detects program)");
608
+ console.log(" npm run openbook --list # List OpenBook markets");
609
+ console.log(" npm run openbook --list --serum # List Serum markets");
610
+ console.log("\nAuto-detection:");
611
+ console.log(" The system automatically detects if a market is OpenBook or Serum");
612
+ console.log(" No need to specify --serum flag for most operations");
613
+ console.log("\nFiles:");
614
+ console.log(" known_openbook_markets.json # OpenBook markets");
615
+ console.log(" known_serum_markets.json # Serum markets");
616
+
617
+ } catch (error) {
618
+ console.error("āŒ Error in main function:", error);
619
+ }
620
+ }
621
+
622
+ // Export functions for use in other modules
623
+ export {
624
+ getMarketInfo,
625
+ getOrderBook,
626
+ getMarketStats,
627
+ displayOrderBook,
628
+ displayMarketInfo,
629
+ listKnownMarkets,
630
+ loadOpenBookMarket,
631
+ getRealOrderBook
632
+ };
633
+
634
+ // Export main function for CLI usage
635
+ export { main };
636
+
637
+ // Run the main function if this file is executed directly
638
+ if (require.main === module) {
639
+ main().catch((error) => {
640
+ console.error("āŒ Error in main function:", error);
641
+ });
642
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true,
15
+ "resolveJsonModule": true
16
+ },
17
+ "include": [
18
+ "src/**/*"
19
+ ],
20
+ "exclude": [
21
+ "node_modules",
22
+ "dist"
23
+ ]
24
+ }