pinpet-sdk 0.1.1

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,1102 @@
1
+
2
+ const { PublicKey } = require('@solana/web3.js');
3
+ const anchor = require('@coral-xyz/anchor');
4
+ const CurveAMM = require('../utils/curve_amm');
5
+ // 统一使用 buffer 包,所有平台一致
6
+ const { Buffer } = require('buffer');
7
+
8
+ /**
9
+ * Chain Data Module
10
+ * When no auxiliary server is available, directly call on-chain data to get transaction parameters
11
+ * The downside is that during peak trading periods, on-chain data may have delays, causing transaction failures
12
+ * Provides functionality to read account data from Solana blockchain
13
+ */
14
+ class ChainModule {
15
+ constructor(sdk) {
16
+ this.sdk = sdk;
17
+ }
18
+
19
+ /**
20
+ * Get complete curve_account (BorrowingBondingCurve) data
21
+ *
22
+ * Read the borrowing liquidity pool account data for a specified token from the blockchain,
23
+ * including addresses and balance information for all related accounts.
24
+ * This function automatically calculates related PDA addresses and concurrently queries all balances,
25
+ * providing complete liquidity pool status.
26
+ *
27
+ * @param {string|PublicKey} mint - Token mint account address
28
+ *
29
+ * @returns {Promise<Object>} Complete BorrowingBondingCurve account data object
30
+ *
31
+ * @returns {Promise<Object>} Return object contains following complete fields:
32
+ *
33
+ * **Core Reserve Data:**
34
+ * @returns {bigint} returns.lpTokenReserve - LP Token reserves, total reserves of liquidity provider tokens
35
+ * @returns {bigint} returns.lpSolReserve - LP SOL reserves, SOL reserves in the liquidity pool
36
+ * @returns {bigint} returns.price - Current token price, calculated based on AMM algorithm
37
+ * @returns {bigint} returns.borrowTokenReserve - Borrowable Token reserves, borrowable token reserves
38
+ * @returns {bigint} returns.borrowSolReserve - Borrowable SOL reserves, borrowable SOL reserves
39
+ *
40
+ * **Fee and Parameter Configuration:**
41
+ * @returns {number} returns.swapFee - Swap fee rate, expressed in basis points (e.g. 100 = 1%)
42
+ * @returns {number} returns.borrowFee - Borrow fee rate, expressed in basis points
43
+ * @returns {boolean} returns.feeDiscountFlag - Fee discount flag, whether fee discounts are enabled
44
+ * @returns {number} returns.feeSplit - Fee split ratio, determines how fees are distributed among different recipients
45
+ * @returns {number} returns.borrowDuration - Borrow duration, in seconds
46
+ * @returns {number} returns.bump - curve_account PDA bump seed
47
+ *
48
+ * **Account Addresses:**
49
+ * @returns {string} returns.baseFeeRecipient - Base fee recipient address, receives base transaction fees
50
+ * @returns {string} returns.feeRecipient - Fee recipient address, receives additional fee income
51
+ * @returns {string} returns.mint - Token mint account address
52
+ * @returns {string|null} returns.upHead - Up order linked list head account address, null if none
53
+ * @returns {string|null} returns.downHead - Down order linked list head account address, null if none
54
+ * @returns {string} returns.poolTokenAccount - Pool token account address, stores tokens in the liquidity pool
55
+ * @returns {string} returns.poolSolAccount - Pool SOL account address, stores native SOL in the liquidity pool
56
+ *
57
+ * **Balance Information:**
58
+ * @returns {number} returns.baseFeeRecipientBalance - SOL balance of base fee recipient address (lamports)
59
+ * @returns {number} returns.feeRecipientBalance - SOL balance of fee recipient address (lamports)
60
+ * @returns {bigint} returns.poolTokenBalance - Token balance of pool token account
61
+ * @returns {number} returns.poolSolBalance - SOL balance of pool SOL account (lamports)
62
+ *
63
+ * **Metadata:**
64
+ * @returns {Object} returns._metadata - Additional metadata information
65
+ * @returns {string} returns._metadata.accountAddress - Complete address of curve_account
66
+ * @returns {string} returns._metadata.mintAddress - Input token mint address
67
+ *
68
+ * @throws {Error} Throws error when curve_account does not exist
69
+ * @throws {Error} Throws error when unable to decode account data
70
+ * @throws {Error} Throws error when network connection fails
71
+ *
72
+ * @example
73
+ * // Basic usage example
74
+ * try {
75
+ * const curveData = await sdk.chain.getCurveAccount('3YggGtxXEGBbjK1WLj2Z79doZC2gkCWXag1ag8BD4cYY');
76
+ *
77
+ * // Display core reserve information
78
+ * console.log('=== Core Reserve Data ===');
79
+ * console.log('LP Token reserves:', curveData.lpTokenReserve.toString());
80
+ * console.log('LP SOL reserves:', curveData.lpSolReserve.toString());
81
+ * console.log('Current price:', curveData.price.toString());
82
+ * console.log('Borrow Token reserves:', curveData.borrowTokenReserve.toString());
83
+ * console.log('Borrow SOL reserves:', curveData.borrowSolReserve.toString());
84
+ *
85
+ * // Display fee configuration
86
+ * console.log('=== Fee Configuration ===');
87
+ * console.log('Swap fee rate:', curveData.swapFee / 100, '%');
88
+ * console.log('Borrow fee rate:', curveData.borrowFee / 100, '%');
89
+ * console.log('Fee discount:', curveData.feeDiscountFlag ? 'Enabled' : 'Disabled');
90
+ * console.log('Borrow duration:', curveData.borrowDuration, 'seconds');
91
+ *
92
+ * // Display account addresses
93
+ * console.log('=== Account Addresses ===');
94
+ * console.log('Base fee recipient address:', curveData.baseFeeRecipient);
95
+ * console.log('Fee recipient address:', curveData.feeRecipient);
96
+ * console.log('Pool token account:', curveData.poolTokenAccount);
97
+ * console.log('Pool SOL account:', curveData.poolSolAccount);
98
+ *
99
+ * // Display balance information
100
+ * console.log('=== Balance Information ===');
101
+ * console.log('Base fee recipient balance:', curveData.baseFeeRecipientBalance / 1e9, 'SOL');
102
+ * console.log('Fee recipient balance:', curveData.feeRecipientBalance / 1e9, 'SOL');
103
+ * console.log('Pool token balance:', curveData.poolTokenBalance.toString());
104
+ * console.log('Pool SOL balance:', curveData.poolSolBalance / 1e9, 'SOL');
105
+ *
106
+ * // Display linked list head information
107
+ * console.log('=== Order Linked Lists ===');
108
+ * console.log('Up order head:', curveData.upHead || 'Empty');
109
+ * console.log('Down order head:', curveData.downHead || 'Empty');
110
+ *
111
+ * } catch (error) {
112
+ * console.error('Failed to get curve account:', error.message);
113
+ * }
114
+ *
115
+ * @example
116
+ * // Pool monitoring example
117
+ * async function monitorPool(mintAddress) {
118
+ * const data = await sdk.chain.getCurveAccount(mintAddress);
119
+ *
120
+ * // Calculate pool utilization
121
+ * const tokenUtilization = Number(data.lpTokenReserve - data.poolTokenBalance) / Number(data.lpTokenReserve);
122
+ * const solUtilization = Number(data.lpSolReserve - BigInt(data.poolSolBalance)) / Number(data.lpSolReserve);
123
+ *
124
+ * console.log('Token utilization:', (tokenUtilization * 100).toFixed(2), '%');
125
+ * console.log('SOL utilization:', (solUtilization * 100).toFixed(2), '%');
126
+ *
127
+ * // Check fee earnings
128
+ * const totalFeeBalance = data.baseFeeRecipientBalance + data.feeRecipientBalance;
129
+ * console.log('Total fee earnings:', totalFeeBalance / 1e9, 'SOL');
130
+ *
131
+ * return {
132
+ * tokenUtilization,
133
+ * solUtilization,
134
+ * totalFeeBalance,
135
+ * currentPrice: data.price
136
+ * };
137
+ * }
138
+ *
139
+ * @since 1.0.0
140
+ * @version 1.1.0 - Added liquidity pool account balance query functionality
141
+ * @author SpinPet SDK Team
142
+ */
143
+ async getCurveAccount(mint) {
144
+ try {
145
+ // Parameter validation and conversion
146
+ const mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
147
+
148
+ // Calculate curve_account PDA address
149
+ // Use the same seeds as in the contract: [b"borrowing_curve", mint_account.key().as_ref()]
150
+ const [curveAccountPDA] = PublicKey.findProgramAddressSync(
151
+ [
152
+ Buffer.from("borrowing_curve"),
153
+ mintPubkey.toBuffer()
154
+ ],
155
+ this.sdk.programId
156
+ );
157
+
158
+ // Use Anchor program to fetch account data directly
159
+ // Method 1: Use program's fetch method
160
+ let decodedData;
161
+ try {
162
+ decodedData = await this.sdk.program.account.borrowingBondingCurve.fetch(curveAccountPDA);
163
+ } catch (fetchError) {
164
+ // Method 2: If fetch fails, use raw method
165
+ const accountInfo = await this.sdk.connection.getAccountInfo(curveAccountPDA);
166
+ if (!accountInfo) {
167
+ throw new Error(`curve_account does not exist`);
168
+ }
169
+
170
+ // Manually decode with BorshAccountsCoder
171
+ const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
172
+
173
+ // Try different account names
174
+ try {
175
+ decodedData = accountsCoder.decode('BorrowingBondingCurve', accountInfo.data);
176
+ } catch (decodeError1) {
177
+ try {
178
+ // Try lowercase name
179
+ decodedData = accountsCoder.decode('borrowingBondingCurve', accountInfo.data);
180
+ } catch (decodeError2) {
181
+ // Both failed, throw original error
182
+ throw new Error(`Cannot decode account data: ${decodeError1.message}`);
183
+ }
184
+ }
185
+ }
186
+
187
+ // Calculate pool account PDA addresses
188
+ const [poolTokenAccountPDA] = PublicKey.findProgramAddressSync(
189
+ [
190
+ Buffer.from("pool_token"),
191
+ mintPubkey.toBuffer()
192
+ ],
193
+ this.sdk.programId
194
+ );
195
+
196
+ const [poolSolAccountPDA] = PublicKey.findProgramAddressSync(
197
+ [
198
+ Buffer.from("pool_sol"),
199
+ mintPubkey.toBuffer()
200
+ ],
201
+ this.sdk.programId
202
+ );
203
+
204
+ // Query all balances concurrently
205
+ const [
206
+ baseFeeRecipientBalance,
207
+ feeRecipientBalance,
208
+ poolTokenBalance,
209
+ poolSolBalance
210
+ ] = await Promise.all([
211
+ this.sdk.connection.getBalance(decodedData.baseFeeRecipient),
212
+ this.sdk.connection.getBalance(decodedData.feeRecipient),
213
+ this.sdk.connection.getTokenAccountBalance(poolTokenAccountPDA).catch(() => ({ value: { amount: '0' } })),
214
+ this.sdk.connection.getBalance(poolSolAccountPDA)
215
+ ]);
216
+
217
+ // Convert data format
218
+ const convertedData = {
219
+ // BN types convert to bigint
220
+ lpTokenReserve: BigInt(decodedData.lpTokenReserve.toString()),
221
+ lpSolReserve: BigInt(decodedData.lpSolReserve.toString()),
222
+ price: BigInt(decodedData.price.toString()),
223
+ borrowTokenReserve: BigInt(decodedData.borrowTokenReserve.toString()),
224
+ borrowSolReserve: BigInt(decodedData.borrowSolReserve.toString()),
225
+
226
+ // Numeric types remain unchanged
227
+ swapFee: decodedData.swapFee,
228
+ borrowFee: decodedData.borrowFee,
229
+ feeDiscountFlag: decodedData.feeDiscountFlag,
230
+ feeSplit: decodedData.feeSplit,
231
+ borrowDuration: decodedData.borrowDuration,
232
+ bump: decodedData.bump,
233
+
234
+ // PublicKey types convert to string
235
+ baseFeeRecipient: decodedData.baseFeeRecipient.toString(),
236
+ feeRecipient: decodedData.feeRecipient.toString(),
237
+ mint: decodedData.mint.toString(),
238
+ upHead: decodedData.upHead ? decodedData.upHead.toString() : null,
239
+ downHead: decodedData.downHead ? decodedData.downHead.toString() : null,
240
+
241
+ // SOL balance information
242
+ baseFeeRecipientBalance: baseFeeRecipientBalance, // Unit: lamports
243
+ feeRecipientBalance: feeRecipientBalance, // Unit: lamports
244
+
245
+ // Pool account information
246
+ poolTokenAccount: poolTokenAccountPDA.toString(), // Pool token account address
247
+ poolSolAccount: poolSolAccountPDA.toString(), // Pool SOL account address
248
+ poolTokenBalance: BigInt(poolTokenBalance.value.amount), // Pool token balance
249
+ poolSolBalance: poolSolBalance, // Pool SOL balance (lamports)
250
+
251
+ // Additional metadata
252
+ _metadata: {
253
+ accountAddress: curveAccountPDA.toString(),
254
+ mintAddress: mintPubkey.toString()
255
+ }
256
+ };
257
+
258
+ // Return converted data
259
+ return convertedData;
260
+
261
+ } catch (error) {
262
+ // Provide concise error information
263
+ if (error.message.includes('Account does not exist')) {
264
+ throw new Error(`curve_account does not exist for mint: ${mint}`);
265
+ } else {
266
+ throw new Error(`Failed to get curve_account: ${error.message}`);
267
+ }
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Batch get multiple tokens' curve_account data
273
+ *
274
+ * @param {Array<string|PublicKey>} mints - Array of token addresses
275
+ * @returns {Promise<Object>} Object containing success and error results
276
+ *
277
+ * @example
278
+ * const curveDataList = await sdk.chain.getCurveAccountBatch([
279
+ * '3YggGtxXEGBbjK1WLj2Z79doZC2gkCWXag1ag8BD4cYY',
280
+ * 'AnotherTokenMintAddress'
281
+ * ]);
282
+ */
283
+ async getCurveAccountBatch(mints) {
284
+ if (!Array.isArray(mints)) {
285
+ throw new Error('mints parameter must be an array');
286
+ }
287
+
288
+ const results = [];
289
+ const errors = [];
290
+
291
+ // Concurrently get all data
292
+ const promises = mints.map(async (mint, index) => {
293
+ try {
294
+ const data = await this.getCurveAccount(mint);
295
+ return { index, success: true, data, mint: mint.toString() };
296
+ } catch (error) {
297
+ return { index, success: false, error: error.message, mint: mint.toString() };
298
+ }
299
+ });
300
+
301
+ const settled = await Promise.allSettled(promises);
302
+
303
+ // Process results
304
+ for (let i = 0; i < settled.length; i++) {
305
+ const result = settled[i];
306
+
307
+ if (result.status === 'fulfilled') {
308
+ const { success, data, error, mint } = result.value;
309
+
310
+ if (success) {
311
+ results.push(data);
312
+ } else {
313
+ errors.push({ mint, error });
314
+ }
315
+ } else {
316
+ errors.push({
317
+ mint: mints[i].toString(),
318
+ error: result.reason?.message || 'Unknown error'
319
+ });
320
+ }
321
+ }
322
+
323
+ return {
324
+ success: results,
325
+ errors: errors,
326
+ total: mints.length,
327
+ successCount: results.length,
328
+ errorCount: errors.length
329
+ };
330
+ }
331
+
332
+ /**
333
+ * Calculate curve_account PDA address
334
+ *
335
+ * @param {string|PublicKey} mint - Token mint address
336
+ * @returns {PublicKey} curve_account PDA address
337
+ *
338
+ * @example
339
+ * const curveAddress = sdk.chain.getCurveAccountAddress('3YggGtxXEGBbjK1WLj2Z79doZC2gkCWXag1ag8BD4cYY');
340
+ * console.log('Curve Account Address:', curveAddress.toString());
341
+ */
342
+ getCurveAccountAddress(mint) {
343
+ const mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
344
+
345
+ const [curveAccountPDA] = PublicKey.findProgramAddressSync(
346
+ [
347
+ Buffer.from("borrowing_curve"),
348
+ mintPubkey.toBuffer()
349
+ ],
350
+ this.sdk.programId
351
+ );
352
+
353
+ return curveAccountPDA;
354
+ }
355
+
356
+ /**
357
+ * Get price data (read price from chain curveAccountPDA)
358
+ * @param {string} mint - Token address
359
+ * @returns {Promise<string>} Latest price string
360
+ *
361
+ * @example
362
+ * // Get latest token price
363
+ * const price = await sdk.chain.price('56hfrQYiyRSUZdRKDuUvsqRik8j2UDW9kCisy7BiRxmg');
364
+ * console.log('Latest price:', price); // "13514066072452801812769"
365
+ */
366
+ async price(mint) {
367
+ // Validate input
368
+ if (!mint || typeof mint !== 'string') {
369
+ throw new Error('price: mint address must be a valid string');
370
+ }
371
+
372
+ try {
373
+ // Parameter validation and conversion
374
+ let mintPubkey;
375
+ try {
376
+ mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
377
+ } catch (pubkeyError) {
378
+ throw new Error(`Invalid mint address: ${mint}`);
379
+ }
380
+
381
+ // Validate mintPubkey
382
+ if (!mintPubkey || typeof mintPubkey.toBuffer !== 'function') {
383
+ throw new Error(`Invalid mintPubkey`);
384
+ }
385
+
386
+ // Calculate curve_account PDA address
387
+ const [curveAccountPDA] = PublicKey.findProgramAddressSync(
388
+ [
389
+ Buffer.from("borrowing_curve"),
390
+ mintPubkey.toBuffer()
391
+ ],
392
+ this.sdk.programId
393
+ );
394
+
395
+ // Use Anchor program to fetch account data directly
396
+ let decodedData;
397
+ try {
398
+ decodedData = await this.sdk.program.account.borrowingBondingCurve.fetch(curveAccountPDA);
399
+ } catch (fetchError) {
400
+ // If fetch fails, use raw method
401
+ const accountInfo = await this.sdk.connection.getAccountInfo(curveAccountPDA);
402
+ if (!accountInfo) {
403
+ throw new Error(`curve_account does not exist`);
404
+ }
405
+
406
+ // Manually decode with BorshAccountsCoder
407
+ const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
408
+
409
+ try {
410
+ decodedData = accountsCoder.decode('BorrowingBondingCurve', accountInfo.data);
411
+ } catch (decodeError1) {
412
+ try {
413
+ decodedData = accountsCoder.decode('borrowingBondingCurve', accountInfo.data);
414
+ } catch (decodeError2) {
415
+ throw new Error(`Cannot decode account data: ${decodeError1.message}`);
416
+ }
417
+ }
418
+ }
419
+
420
+ // Check price data and return
421
+ if (decodedData.price && decodedData.price.toString() !== '0') {
422
+ return decodedData.price.toString();
423
+ } else {
424
+ // If no price data, return initial price
425
+ const initialPrice = CurveAMM.getInitialPrice();
426
+ if (initialPrice === null) {
427
+ throw new Error('price: Unable to calculate initial price');
428
+ }
429
+ return initialPrice.toString();
430
+ }
431
+
432
+ } catch (error) {
433
+ // If getting fails, return initial price
434
+ console.warn(`price: Failed to get chain price, using initial price: ${error.message}`);
435
+
436
+ const initialPrice = CurveAMM.getInitialPrice();
437
+ if (initialPrice === null) {
438
+ throw new Error('price: Unable to calculate initial price');
439
+ }
440
+ return initialPrice.toString();
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Get Orders Data (Read from Chain)
446
+ * @param {string} mint - Token mint address
447
+ * @param {Object} options - Query parameters
448
+ * @param {string} options.type - Order type: "up_orders" (short) or "down_orders" (long)
449
+ * @param {number} options.page - Page number, default 1
450
+ * @param {number} options.limit - Items per page, default 500, max 1000
451
+ * @returns {Promise<Object>} Order data with raw order list
452
+ *
453
+ * @example
454
+ * // Get long orders
455
+ * const ordersData = await sdk.chain.orders('6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee', { type: 'down_orders' });
456
+ *
457
+ * // Return value example:
458
+ * // {
459
+ * // "success": true,
460
+ * // "data": {
461
+ * // "orders": [
462
+ * // {
463
+ * // "order_type": "down_orders", // Order type string (converted)
464
+ * // "mint": "6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee", // Token address
465
+ * // "user": "JD1eNPaJpbtejKfgimbLYLkvpsTHyYzKCCozVLGLS6zu", // User address
466
+ * // "lock_lp_start_price": "46618228118401293964111", // LP start price (string)
467
+ * // "lock_lp_end_price": "45827474968448818396222", // LP end price (string)
468
+ * // "lock_lp_sol_amount": 3299491609, // LP locked SOL amount (lamports)
469
+ * // "lock_lp_token_amount": 713848715669, // LP locked token amount (min unit)
470
+ * // "start_time": 1756352482, // Start time (Unix timestamp)
471
+ * // "end_time": 1756525282, // End time (Unix timestamp)
472
+ * // "margin_sol_amount": 571062973, // Margin SOL amount (lamports)
473
+ * // "borrow_amount": 3860656108, // Borrow amount (lamports)
474
+ * // "position_asset_amount": 713848715669, // Position asset amount (min unit)
475
+ * // "borrow_fee": 300, // Borrow fee (basis points, 300 = 3%)
476
+ * // "order_pda": "5aVwYyzvC5Y2qykDgwG8o7EUwCrL8WgCJpgxoH3mihYb" // Order PDA address
477
+ * // }
478
+ * // ],
479
+ * // "total": 12, // Total order count
480
+ * // "order_type": "down_orders", // Order type (string)
481
+ * // "mint_account": "6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee", // Queried token address
482
+ * // "page": 1, // Current page number
483
+ * // "limit": 50, // Per page limit
484
+ * // "has_next": false, // Whether has next page
485
+ * // "has_prev": false // Whether has previous page
486
+ * // },
487
+ * // "message": "Operation successful" // Operation result message
488
+ * // }
489
+ *
490
+ * // Use utility methods to process data:
491
+ * const lpPairs = sdk.buildLpPairs(ordersData.data.orders); // Build LP pairs array
492
+ * const orderAccounts = sdk.buildOrderAccounts(ordersData.data.orders); // Build order accounts array
493
+ */
494
+ async orders(mint, options = {}) {
495
+ try {
496
+ // Parameter validation
497
+ if (!mint || typeof mint !== 'string') {
498
+ throw new Error('orders: mint address must be a valid string');
499
+ }
500
+
501
+ // Set default parameters
502
+ const orderType = options.type || 'down_orders';
503
+ const page = options.page || 1;
504
+ const limit = Math.min(options.limit || 500, 1000); // Maximum 1000
505
+
506
+ // Validate order type
507
+ if (!['up_orders', 'down_orders'].includes(orderType)) {
508
+ throw new Error('orders: order type must be "up_orders" or "down_orders"');
509
+ }
510
+
511
+ // Convert API type to linked list direction
512
+ // "up_orders" = short orders = upHead
513
+ // "down_orders" = long orders = downHead
514
+ const direction = orderType === 'up_orders' ? 'upHead' : 'downHead';
515
+
516
+ //console.log(`chain.orders: Get ${orderType} orders, mint=${mint}, limit=${limit}`);
517
+
518
+ // Get curve_account data to get linked list head
519
+ const curveData = await this.getCurveAccount(mint);
520
+ const headAddress = curveData[direction];
521
+
522
+ //console.log(`chain.orders: ${direction} linked list head address:`, headAddress || 'null');
523
+
524
+ // If linked list is empty, return empty result
525
+ if (!headAddress) {
526
+ //console.log(`chain.orders: ${direction} linked list is empty`);
527
+ return {
528
+ success: true,
529
+ data: {
530
+ orders: [],
531
+ total: 0,
532
+ order_type: orderType,
533
+ mint_account: mint,
534
+ page: page,
535
+ limit: limit,
536
+ has_next: false,
537
+ has_prev: false
538
+ },
539
+ message: "Operation successful"
540
+ };
541
+ }
542
+
543
+ // Traverse linked list to read orders
544
+ const orders = [];
545
+ let currentAddress = new PublicKey(headAddress);
546
+ let count = 0;
547
+
548
+ //console.log(`chain.orders: Start traversing linked list from ${currentAddress.toString()}`);
549
+
550
+ while (currentAddress && count < limit) {
551
+ try {
552
+ //console.log(`chain.orders: 遍历 Traversing [${count}] ${currentAddress.toString()}`);
553
+
554
+ // Get raw account data
555
+ const accountInfo = await this.sdk.connection.getAccountInfo(currentAddress);
556
+ if (!accountInfo) {
557
+ throw new Error(`Order account ${currentAddress.toString()} does not exist`);
558
+ }
559
+
560
+ // Manually decode with BorshAccountsCoder
561
+ const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
562
+ let orderData;
563
+
564
+ try {
565
+ orderData = accountsCoder.decode('MarginOrder', accountInfo.data);
566
+ } catch (decodeError1) {
567
+ try {
568
+ orderData = accountsCoder.decode('marginOrder', accountInfo.data);
569
+ } catch (decodeError2) {
570
+ console.log("orders 报错时的 accountInfo=", accountInfo);
571
+ console.log("decodeError1=", decodeError1);
572
+ throw new Error(`Cannot decode order account data: ${decodeError2.message}`);
573
+ }
574
+ }
575
+
576
+
577
+
578
+ // try {
579
+ // // 尝试不同的账户类型名称
580
+ // orderData = accountsCoder.decode('MarginOrder', accountInfo.data);
581
+ // } catch (decodeError1) {
582
+ // try {
583
+ // orderData = accountsCoder.decode('marginOrder', accountInfo.data);
584
+ // } catch (decodeError2) {
585
+ // try {
586
+ // // 使用 program.account 直接解码
587
+ // console.log(`使用 program.account 直接解码 PDA: ${pdaAddress}`);
588
+ // orderData = this.sdk.program.account.marginOrder.fetch(currentAddress);
589
+ // } catch (decodeError3) {
590
+ // error = `解码失败: MarginOrder[${decodeError1.message}] marginOrder[${decodeError2.message}] fetch[${decodeError3.message}]`;
591
+ // throw error
592
+ // }
593
+ // }
594
+ // }
595
+
596
+
597
+
598
+
599
+
600
+ // Data transformation
601
+ const convertedOrder = {
602
+ // Convert chain number to API string format
603
+ order_type: orderData.orderType === 1 ? 'down_orders' : 'up_orders', // 1=long=down_orders, 2=short=up_orders
604
+ mint: orderData.mint.toString(),
605
+ user: orderData.user.toString(),
606
+ // Convert BN type to string
607
+ lock_lp_start_price: orderData.lockLpStartPrice.toString(),
608
+ lock_lp_end_price: orderData.lockLpEndPrice.toString(),
609
+ // Keep numeric types unchanged
610
+ lock_lp_sol_amount: orderData.lockLpSolAmount.toNumber(),
611
+ lock_lp_token_amount: orderData.lockLpTokenAmount.toNumber(),
612
+ start_time: orderData.startTime,
613
+ end_time: orderData.endTime,
614
+ margin_init_sol_amount: orderData.marginInitSolAmount.toNumber(),
615
+ margin_sol_amount: orderData.marginSolAmount.toNumber(),
616
+ borrow_amount: orderData.borrowAmount.toNumber(),
617
+ position_asset_amount: orderData.positionAssetAmount.toNumber(),
618
+ borrow_fee: orderData.borrowFee,
619
+ realized_sol_amount: orderData.realizedSolAmount.toNumber(),
620
+ // Add order_pda field
621
+ order_pda: currentAddress.toString()
622
+ };
623
+
624
+ orders.push(convertedOrder);
625
+ count++;
626
+
627
+ //console.log(`chain.orders: Successfully read order ${count}: ${currentAddress.toString()}, type=${convertedOrder.order_type}`);
628
+
629
+ // Move to next node
630
+ if (orderData.nextOrder) {
631
+ currentAddress = orderData.nextOrder;
632
+ //console.log(`chain.orders: 下一个 Next: ${currentAddress.toString()}`);
633
+ } else {
634
+ //console.log(`chain.orders: 链表结束 List ended at node ${count}`);
635
+ break;
636
+ }
637
+
638
+ // Add 50ms delay to avoid calling too fast
639
+ //await new Promise(resolve => setTimeout(resolve, 50));
640
+
641
+ } catch (error) {
642
+ // If order account doesn't exist or read fails, throw error
643
+ console.log("A_chain.orders() err:", error);
644
+ throw new Error(`Failed to read order: ${error.message}`);
645
+ }
646
+ }
647
+
648
+ // Simulate pagination info
649
+ const hasNext = count === limit; // If read limit reached, might have more
650
+ const hasPrev = page > 1;
651
+
652
+ //console.log(`chain.orders: Completed reading, got ${orders.length} orders`);
653
+
654
+ // Return same format as fast.orders
655
+ return {
656
+ success: true,
657
+ data: {
658
+ orders: orders,
659
+ total: orders.length, // Chain can't know total, use current count
660
+ order_type: orderType,
661
+ mint_account: mint,
662
+ page: page,
663
+ limit: limit,
664
+ has_next: hasNext,
665
+ has_prev: hasPrev
666
+ },
667
+ message: "Operation successful"
668
+ };
669
+
670
+ } catch (error) {
671
+ // Error handling
672
+ console.log("chain.orders() err:", error);
673
+ console.error('chain.orders: Failed to get orders', error.message);
674
+ throw new Error(`Failed to get orders: ${error.message}`);
675
+ }
676
+ }
677
+
678
+ /**
679
+ * Get MarginOrder account data by PDA address
680
+ *
681
+ * Read the margin order account data for a specified PDA address from the blockchain,
682
+ * including all order details and status information.
683
+ *
684
+ * @param {string|PublicKey} pda - MarginOrder account PDA address
685
+ *
686
+ * @returns {Promise<Object>} Complete MarginOrder account data object
687
+ *
688
+ * @returns {Promise<Object>} Return object contains following complete fields:
689
+ *
690
+ * **Core Order Data:**
691
+ * @returns {number} returns.order_type - Order type: 1=long(做多), 2=short(做空)
692
+ * @returns {string} returns.mint - Token mint account address
693
+ * @returns {string} returns.user - User account address who created the order
694
+ * @returns {string|null} returns.next_order - Next order PDA address in linked list, null if none
695
+ * @returns {string|null} returns.prev_order - Previous order PDA address in linked list, null if none
696
+ *
697
+ * **Price and Amount Data:**
698
+ * @returns {string} returns.lock_lp_start_price - LP start price when order was created (u128 as string)
699
+ * @returns {string} returns.lock_lp_end_price - LP end price when order was created (u128 as string)
700
+ * @returns {number} returns.lock_lp_sol_amount - LP locked SOL amount (lamports)
701
+ * @returns {number} returns.lock_lp_token_amount - LP locked token amount (min unit)
702
+ * @returns {number} returns.margin_sol_amount - Margin SOL amount (lamports)
703
+ * @returns {number} returns.borrow_amount - Borrowed amount (lamports or min unit)
704
+ * @returns {number} returns.position_asset_amount - Position asset amount (min unit)
705
+ *
706
+ * **Time and Fee Data:**
707
+ * @returns {number} returns.start_time - Order start time (Unix timestamp in seconds)
708
+ * @returns {number} returns.end_time - Order expiry time (Unix timestamp in seconds)
709
+ * @returns {number} returns.borrow_fee - Borrow fee rate (basis points, e.g. 300 = 3%)
710
+ * @returns {string} returns.open_price - Open price when order was created (u128 as string)
711
+ *
712
+ * **Technical Data:**
713
+ * @returns {number} returns.bump - PDA bump seed
714
+ *
715
+ * **Metadata:**
716
+ * @returns {Object} returns._metadata - Additional metadata information
717
+ * @returns {string} returns._metadata.accountAddress - Complete PDA address
718
+ *
719
+ * @throws {Error} Throws error when MarginOrder account does not exist
720
+ * @throws {Error} Throws error when unable to decode account data
721
+ * @throws {Error} Throws error when network connection fails
722
+ * @throws {Error} Throws error when invalid PDA address provided
723
+ *
724
+ * @example
725
+ * // Basic usage example
726
+ * try {
727
+ * const orderData = await sdk.chain.getOrderAccount('FUYU1mKcV5XsJuK4SfcoD4pfXJvQ18pstg8fGqVLYGDG');
728
+ *
729
+ * // Display core order information
730
+ * console.log('=== Order Information ===');
731
+ * console.log('Order type:', orderData.order_type === 1 ? 'Long' : 'Short');
732
+ * console.log('Token mint:', orderData.mint);
733
+ * console.log('User:', orderData.user);
734
+ * console.log('Margin amount:', orderData.margin_sol_amount / 1e9, 'SOL');
735
+ * console.log('Position size:', orderData.position_asset_amount);
736
+ *
737
+ * // Display price information
738
+ * console.log('=== Price Information ===');
739
+ * console.log('Open price:', orderData.open_price);
740
+ * console.log('LP start price:', orderData.lock_lp_start_price);
741
+ * console.log('LP end price:', orderData.lock_lp_end_price);
742
+ *
743
+ * // Display time information
744
+ * console.log('=== Time Information ===');
745
+ * console.log('Start time:', new Date(orderData.start_time * 1000).toLocaleString());
746
+ * console.log('End time:', new Date(orderData.end_time * 1000).toLocaleString());
747
+ * console.log('Borrow fee:', orderData.borrow_fee / 100, '%');
748
+ *
749
+ * // Display linked list connections
750
+ * console.log('=== Linked List Connections ===');
751
+ * console.log('Previous order:', orderData.prev_order || 'None');
752
+ * console.log('Next order:', orderData.next_order || 'None');
753
+ *
754
+ * } catch (error) {
755
+ * console.error('Failed to get order account:', error.message);
756
+ * }
757
+ *
758
+ * @example
759
+ * // Order validation example
760
+ * async function validateOrder(orderPda) {
761
+ * try {
762
+ * const orderData = await sdk.chain.getOrderAccount(orderPda);
763
+ *
764
+ * // Check if order is expired
765
+ * const currentTime = Math.floor(Date.now() / 1000);
766
+ * const isExpired = currentTime > orderData.end_time;
767
+ *
768
+ * // Calculate remaining time
769
+ * const remainingTime = orderData.end_time - currentTime;
770
+ *
771
+ * console.log('Order status:', isExpired ? 'Expired' : 'Active');
772
+ * if (!isExpired) {
773
+ * console.log('Remaining time:', Math.floor(remainingTime / 3600), 'hours');
774
+ * }
775
+ *
776
+ * return {
777
+ * isExpired,
778
+ * remainingTime,
779
+ * orderType: orderData.order_type === 1 ? 'long' : 'short',
780
+ * user: orderData.user,
781
+ * marginAmount: orderData.margin_sol_amount
782
+ * };
783
+ * } catch (error) {
784
+ * console.error('Order validation failed:', error.message);
785
+ * return null;
786
+ * }
787
+ * }
788
+ *
789
+ * @since 1.0.0
790
+ * @author SpinPet SDK Team
791
+ */
792
+ async getOrderAccount(pda) {
793
+ try {
794
+ // Parameter validation and conversion
795
+ let pdaPubkey;
796
+ try {
797
+ pdaPubkey = typeof pda === 'string' ? new PublicKey(pda) : pda;
798
+ } catch (pubkeyError) {
799
+ throw new Error(`Invalid PDA address: ${pda}`);
800
+ }
801
+
802
+ // Validate pdaPubkey
803
+ if (!pdaPubkey || typeof pdaPubkey.toBuffer !== 'function') {
804
+ throw new Error(`Invalid PDA public key`);
805
+ }
806
+
807
+ // Use Anchor program to fetch account data directly
808
+ // Method 1: Use program's fetch method
809
+ let decodedData;
810
+ try {
811
+ decodedData = await this.sdk.program.account.marginOrder.fetch(pdaPubkey);
812
+ } catch (fetchError) {
813
+ // Method 2: If fetch fails, use raw method
814
+ const accountInfo = await this.sdk.connection.getAccountInfo(pdaPubkey);
815
+ if (!accountInfo) {
816
+ throw new Error(`MarginOrder account does not exist`);
817
+ }
818
+
819
+ // Manually decode with BorshAccountsCoder
820
+ const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
821
+
822
+ // Try different account names
823
+ try {
824
+ decodedData = accountsCoder.decode('marginOrder', accountInfo.data);
825
+ } catch (decodeError1) {
826
+ try {
827
+ // Try uppercase name
828
+ decodedData = accountsCoder.decode('MarginOrder', accountInfo.data);
829
+ } catch (decodeError2) {
830
+ // Both failed, throw original error
831
+ throw new Error(`Cannot decode MarginOrder account data: ${decodeError1.message}`);
832
+ }
833
+ }
834
+ }
835
+
836
+ // Convert data format (following the same pattern as getCurveAccount)
837
+ const convertedData = {
838
+ // Numeric types remain unchanged
839
+ order_type: decodedData.orderType,
840
+ start_time: decodedData.startTime,
841
+ end_time: decodedData.endTime,
842
+ lock_lp_sol_amount: decodedData.lockLpSolAmount.toNumber(),
843
+ lock_lp_token_amount: decodedData.lockLpTokenAmount.toNumber(),
844
+ margin_init_sol_amount: decodedData.marginInitSolAmount.toNumber(),
845
+ margin_sol_amount: decodedData.marginSolAmount.toNumber(),
846
+ borrow_amount: decodedData.borrowAmount.toNumber(),
847
+ position_asset_amount: decodedData.positionAssetAmount.toNumber(),
848
+ borrow_fee: decodedData.borrowFee,
849
+ realized_sol_amount: decodedData.realizedSolAmount.toNumber(),
850
+ bump: decodedData.bump,
851
+
852
+ // BN types convert to string
853
+ lock_lp_start_price: decodedData.lockLpStartPrice.toString(),
854
+ lock_lp_end_price: decodedData.lockLpEndPrice.toString(),
855
+ open_price: decodedData.openPrice.toString(),
856
+
857
+ // PublicKey types convert to string
858
+ mint: decodedData.mint.toString(),
859
+ user: decodedData.user.toString(),
860
+ next_order: decodedData.nextOrder ? decodedData.nextOrder.toString() : null,
861
+ prev_order: decodedData.prevOrder ? decodedData.prevOrder.toString() : null,
862
+
863
+ // Additional metadata
864
+ _metadata: {
865
+ accountAddress: pdaPubkey.toString()
866
+ }
867
+ };
868
+
869
+ // Return converted data
870
+ return convertedData;
871
+
872
+ } catch (error) {
873
+ // Provide concise error information
874
+ if (error.message.includes('Account does not exist')) {
875
+ throw new Error(`MarginOrder account does not exist for PDA: ${pda}`);
876
+ } else {
877
+ throw new Error(`Failed to get MarginOrder account: ${error.message}`);
878
+ }
879
+ }
880
+ }
881
+
882
+ /**
883
+ * 获取用户订单 Get User Orders (Read from Chain)
884
+ * @param {string} user - 用户地址
885
+ * @param {string} mint - 代币地址
886
+ * @param {Object} options - 查询参数
887
+ * @param {number} options.page - 页码,默认1
888
+ * @param {number} options.limit - 每页数量,默认200
889
+ * @param {string} options.order_by - 排序方式,默认'start_time_desc'
890
+ * @returns {Promise<Object>} 用户订单数据
891
+ *
892
+ * @example
893
+ * const userOrders = await sdk.chain.user_orders(
894
+ * '8iGFeUkRpyRx8w5uoUMbfZepUr6BfTdPuJmqGoNBntdb',
895
+ * '4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X',
896
+ * { page: 1, limit: 200, order_by: 'start_time_desc' }
897
+ * );
898
+ * // 返回格式:
899
+ * // {
900
+ * // "success": true,
901
+ * // "data": {
902
+ * // "orders": [
903
+ * // {
904
+ * // "order_type": 2,
905
+ * // "mint": "4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X",
906
+ * // "user": "8iGFeUkRpyRx8w5uoUMbfZepUr6BfTdPuJmqGoNBntdb",
907
+ * // "lock_lp_start_price": "753522984132656210522",
908
+ * // "lock_lp_end_price": "833102733432007194898",
909
+ * // "lock_lp_sol_amount": 2535405978,
910
+ * // "lock_lp_token_amount": 32000000000000,
911
+ * // "start_time": 1755964862,
912
+ * // "end_time": 1756137662,
913
+ * // "margin_sol_amount": 1909140052,
914
+ * // "borrow_amount": 32000000000000,
915
+ * // "position_asset_amount": 656690798,
916
+ * // "borrow_fee": 1200,
917
+ * // "order_pda": "59yP5tpDP6DBcyy4mge9wKKKdLmk45Th4sbd6Un9LxVN"
918
+ * // }
919
+ * // ],
920
+ * // "total": 11,
921
+ * // "user": "8iGFeUkRpyRx8w5uoUMbfZepUr6BfTdPuJmqGoNBntdb",
922
+ * // "mint_account": "4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X",
923
+ * // "page": 1,
924
+ * // "limit": 200,
925
+ * // "has_next": false,
926
+ * // "has_prev": false
927
+ * // },
928
+ * // "message": "Operation successful"
929
+ * // }
930
+ *
931
+ * // 使用订单数据:
932
+ * const orders = userOrders.data.orders; // 订单数组
933
+ * const totalCount = userOrders.data.total; // 总数量
934
+ */
935
+ async user_orders(user, mint, options = {}) {
936
+ try {
937
+ // Parameter validation
938
+ if (!user || typeof user !== 'string') {
939
+ throw new Error('user_orders: user address must be a valid string');
940
+ }
941
+ if (!mint || typeof mint !== 'string') {
942
+ throw new Error('user_orders: mint address must be a valid string');
943
+ }
944
+
945
+ // Set default parameters
946
+ const page = options.page || 1;
947
+ const limit = Math.min(options.limit || 200, 1000); // Maximum 1000
948
+ const orderBy = options.order_by || 'start_time_desc';
949
+
950
+ //console.log(`chain.user_orders: Get user orders, user=${user}, mint=${mint}, page=${page}, limit=${limit}`);
951
+
952
+ // Get curve_account data to get both linked list heads
953
+ const curveData = await this.getCurveAccount(mint);
954
+ const upHeadAddress = curveData.upHead; // Short orders (up_orders)
955
+ const downHeadAddress = curveData.downHead; // Long orders (down_orders)
956
+
957
+ //console.log(`chain.user_orders: upHead=${upHeadAddress || 'null'}, downHead=${downHeadAddress || 'null'}`);
958
+
959
+ // Collect all user orders from both linked lists
960
+ const allUserOrders = [];
961
+
962
+ // Helper function to traverse a linked list and collect user orders
963
+ const traverseLinkedList = async (headAddress) => {
964
+ if (!headAddress) return [];
965
+
966
+ const orders = [];
967
+ let currentAddress = new PublicKey(headAddress);
968
+ let count = 0;
969
+ const maxTraverse = 1000; // Limit traversal to avoid infinite loops
970
+
971
+ while (currentAddress && count < maxTraverse) {
972
+ try {
973
+ // Get raw account data
974
+ const accountInfo = await this.sdk.connection.getAccountInfo(currentAddress);
975
+ if (!accountInfo) {
976
+ console.warn(`Order account ${currentAddress.toString()} does not exist, breaking traversal`);
977
+ break;
978
+ }
979
+
980
+ // Manually decode with BorshAccountsCoder
981
+ const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
982
+ let orderData;
983
+
984
+ try {
985
+ orderData = accountsCoder.decode('MarginOrder', accountInfo.data);
986
+ } catch (decodeError1) {
987
+ try {
988
+ orderData = accountsCoder.decode('marginOrder', accountInfo.data);
989
+ } catch (decodeError2) {
990
+ console.warn(`Cannot decode order account ${currentAddress.toString()}: ${decodeError2.message}`);
991
+ break;
992
+ }
993
+ }
994
+
995
+ // Check if this order belongs to the target user
996
+ if (orderData.user.toString() === user) {
997
+ // Data transformation - convert to same format as fast.user_orders
998
+ const convertedOrder = {
999
+ // Convert chain number to API number format (keep as number for compatibility)
1000
+ order_type: orderData.orderType, // 1=long, 2=short
1001
+ mint: orderData.mint.toString(),
1002
+ user: orderData.user.toString(),
1003
+ // Convert BN type to string
1004
+ lock_lp_start_price: orderData.lockLpStartPrice.toString(),
1005
+ lock_lp_end_price: orderData.lockLpEndPrice.toString(),
1006
+ // Keep numeric types unchanged
1007
+ lock_lp_sol_amount: orderData.lockLpSolAmount.toNumber(),
1008
+ lock_lp_token_amount: orderData.lockLpTokenAmount.toNumber(),
1009
+ start_time: orderData.startTime,
1010
+ end_time: orderData.endTime,
1011
+ margin_init_sol_amount: orderData.marginInitSolAmount.toNumber(),
1012
+ margin_sol_amount: orderData.marginSolAmount.toNumber(),
1013
+ borrow_amount: orderData.borrowAmount.toNumber(),
1014
+ position_asset_amount: orderData.positionAssetAmount.toNumber(),
1015
+ borrow_fee: orderData.borrowFee,
1016
+ realized_sol_amount: orderData.realizedSolAmount.toNumber(),
1017
+ // Add order_pda field
1018
+ order_pda: currentAddress.toString()
1019
+ };
1020
+
1021
+ orders.push(convertedOrder);
1022
+ //console.log(`chain.user_orders: Found user order: ${currentAddress.toString()}, type=${convertedOrder.order_type}`);
1023
+ }
1024
+
1025
+ count++;
1026
+
1027
+ // Move to next node
1028
+ if (orderData.nextOrder) {
1029
+ currentAddress = orderData.nextOrder;
1030
+ } else {
1031
+ break;
1032
+ }
1033
+
1034
+ // Add small delay to avoid overloading
1035
+ if (count % 50 === 0) {
1036
+ await new Promise(resolve => setTimeout(resolve, 10));
1037
+ }
1038
+
1039
+ } catch (error) {
1040
+ console.warn(`Error reading order ${currentAddress.toString()}: ${error.message}`);
1041
+ break;
1042
+ }
1043
+ }
1044
+
1045
+ return orders;
1046
+ };
1047
+
1048
+ // Traverse both linked lists in parallel to find user orders
1049
+ const [upOrders, downOrders] = await Promise.all([
1050
+ upHeadAddress ? traverseLinkedList(upHeadAddress) : [], // Short orders
1051
+ downHeadAddress ? traverseLinkedList(downHeadAddress) : [] // Long orders
1052
+ ]);
1053
+
1054
+ // Combine all orders
1055
+ allUserOrders.push(...upOrders, ...downOrders);
1056
+
1057
+ //console.log(`chain.user_orders: Found ${allUserOrders.length} total user orders`);
1058
+
1059
+ // Sort orders by start_time
1060
+ if (orderBy === 'start_time_desc') {
1061
+ allUserOrders.sort((a, b) => b.start_time - a.start_time);
1062
+ } else if (orderBy === 'start_time_asc') {
1063
+ allUserOrders.sort((a, b) => a.start_time - b.start_time);
1064
+ }
1065
+
1066
+ // Implement pagination
1067
+ const totalOrders = allUserOrders.length;
1068
+ const startIndex = (page - 1) * limit;
1069
+ const endIndex = startIndex + limit;
1070
+ const paginatedOrders = allUserOrders.slice(startIndex, endIndex);
1071
+
1072
+ // Calculate pagination info
1073
+ const hasNext = endIndex < totalOrders;
1074
+ const hasPrev = page > 1;
1075
+
1076
+ //console.log(`chain.user_orders: Returning ${paginatedOrders.length} orders (page ${page}/${Math.ceil(totalOrders/limit)})`);
1077
+
1078
+ // Return same format as fast.user_orders
1079
+ return {
1080
+ success: true,
1081
+ data: {
1082
+ orders: paginatedOrders,
1083
+ total: totalOrders,
1084
+ user: user,
1085
+ mint_account: mint,
1086
+ page: page,
1087
+ limit: limit,
1088
+ has_next: hasNext,
1089
+ has_prev: hasPrev
1090
+ },
1091
+ message: "Operation successful"
1092
+ };
1093
+
1094
+ } catch (error) {
1095
+ // Error handling
1096
+ console.error('chain.user_orders: Failed to get user orders', error.message);
1097
+ throw new Error(`Failed to get user orders: ${error.message}`);
1098
+ }
1099
+ }
1100
+ }
1101
+
1102
+ module.exports = ChainModule;