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,465 @@
1
+ const anchor = require('@coral-xyz/anchor');
2
+
3
+ /**
4
+ * 订单数据处理工具模块
5
+ * Order Data Processing Utilities Module
6
+ *
7
+ * 提供订单数据格式转换和处理的纯函数工具方法
8
+ * Provides pure function utilities for order data format conversion and processing
9
+ */
10
+ class OrderUtils {
11
+
12
+ /**
13
+ * 构建 LP 配对数组(用于交易)- 基于价格区间分析
14
+ * Build LP Pairs Array (for trading) - Based on price range analysis
15
+ *
16
+ * @param {Array} orders - 订单数组 Order array
17
+ * @param {string} direction - 方向 'up_orders' (做空订单) 或 'down_orders' (做多订单) Direction: 'up_orders' (short orders) or 'down_orders' (long orders)
18
+ * @param {number} maxCount - 最大区间数量,默认10 Max price ranges count, default 10
19
+ * @returns {Array} LP配对数组,格式: [{ solAmount: BN, tokenAmount: BN }, ...]
20
+ * LP pairs array, format: [{ solAmount: BN, tokenAmount: BN }, ...]
21
+ *
22
+ * @example
23
+ * // 获取做空订单并构建价格区间分析 Get short orders and build price range analysis
24
+ * const ordersData = await sdk.fast.orders(mint, { type: 'up_orders' });
25
+ * const lpPairs = OrderUtils.buildLpPairs(ordersData.data.orders, 'up_orders', 10);
26
+ *
27
+ * // 获取做多订单并构建价格区间分析 Get long orders and build price range analysis
28
+ * const ordersData = await sdk.fast.orders(mint, { type: 'down_orders' });
29
+ * const lpPairs = OrderUtils.buildLpPairs(ordersData.data.orders, 'down_orders', 10);
30
+ *
31
+ * // 返回 Returns: [
32
+ * // { solAmount: new anchor.BN("63947874"), tokenAmount: new anchor.BN("65982364399") },
33
+ * // { solAmount: new anchor.BN("1341732020"), tokenAmount: new anchor.BN("1399566720549") },
34
+ * // ...
35
+ * // { solAmount: new anchor.BN("0"), tokenAmount: new anchor.BN("0") }, // 填充的空区间
36
+ * // ]
37
+ */
38
+ static buildLpPairs(orders, direction, price, maxCount = 10) {
39
+ const CurveAMM = require('./curve_amm');
40
+
41
+ // 参数验证
42
+ if (!Array.isArray(orders)) {
43
+ throw new Error('buildLpPairs: orders 必须是数组 orders must be an array');
44
+ }
45
+
46
+ if (typeof direction !== 'string' || !['up_orders', 'down_orders'].includes(direction)) {
47
+ throw new Error('buildLpPairs: direction 必须是 "up_orders" 或 "down_orders" direction must be "up_orders" or "down_orders"');
48
+ }
49
+
50
+ if (!price) {
51
+ throw new Error('buildLpPairs: price 参数是必需的 price parameter is required');
52
+ }
53
+
54
+ if (!Number.isInteger(maxCount) || maxCount <= 0) {
55
+ throw new Error('buildLpPairs: maxCount 必须是正整数 maxCount must be a positive integer');
56
+ }
57
+
58
+ // 转换价格为 bigint (u128 格式)
59
+ const currentPriceU128 = typeof price === 'bigint' ? price : BigInt(price);
60
+
61
+ const lpPairs = [];
62
+
63
+ // 如果订单为空,创建一个覆盖到最大/最小价格的区间
64
+ if (orders.length === 0) {
65
+ if (direction === 'up_orders') {
66
+ // 做空方向 - 价格上涨
67
+ const buyResult = CurveAMM.buyFromPriceToPrice(currentPriceU128, CurveAMM.MAX_U128_PRICE);
68
+ if (buyResult) {
69
+ const [solAmount, tokenAmount] = buyResult;
70
+ lpPairs.push({
71
+ solAmount: new anchor.BN(solAmount.toString()),
72
+ tokenAmount: new anchor.BN(tokenAmount.toString())
73
+ });
74
+ }
75
+ } else {
76
+ // 做多方向 - 价格下跌
77
+ const sellResult = CurveAMM.sellFromPriceToPrice(currentPriceU128, CurveAMM.MIN_U128_PRICE);
78
+ if (sellResult) {
79
+ const [tokenAmount, solAmount] = sellResult;
80
+ lpPairs.push({
81
+ solAmount: new anchor.BN(solAmount.toString()),
82
+ tokenAmount: new anchor.BN(tokenAmount.toString())
83
+ });
84
+ }
85
+ }
86
+ } else {
87
+ // 有订单的情况,构建价格区间
88
+ const validOrders = orders.filter(order => order !== null);
89
+
90
+ if (direction === 'up_orders') {
91
+ // 做空订单 - 分析价格上涨时的流动性需求
92
+
93
+ // 第一个区间:从当前价格到第一个订单开始价格
94
+ if (validOrders.length > 0) {
95
+ const firstOrderStartPrice = BigInt(validOrders[0].lock_lp_start_price);
96
+ if (currentPriceU128 < firstOrderStartPrice) {
97
+ const buyResult = CurveAMM.buyFromPriceToPrice(currentPriceU128, firstOrderStartPrice - 1n);
98
+ if (buyResult) {
99
+ const [solAmount, tokenAmount] = buyResult;
100
+ lpPairs.push({
101
+ solAmount: new anchor.BN(solAmount.toString()),
102
+ tokenAmount: new anchor.BN(tokenAmount.toString())
103
+ });
104
+ }
105
+ }
106
+ }
107
+
108
+ // 中间区间:订单之间的空隙
109
+ for (let i = 0; i < validOrders.length - 1 && lpPairs.length < maxCount; i++) {
110
+ const currentOrderEndPrice = BigInt(validOrders[i].lock_lp_end_price);
111
+ const nextOrderStartPrice = BigInt(validOrders[i + 1].lock_lp_start_price);
112
+
113
+ if (currentOrderEndPrice + 1n < nextOrderStartPrice) {
114
+ const buyResult = CurveAMM.buyFromPriceToPrice(currentOrderEndPrice + 1n, nextOrderStartPrice - 1n);
115
+ if (buyResult) {
116
+ const [solAmount, tokenAmount] = buyResult;
117
+ lpPairs.push({
118
+ solAmount: new anchor.BN(solAmount.toString()),
119
+ tokenAmount: new anchor.BN(tokenAmount.toString())
120
+ });
121
+ }
122
+ }
123
+ }
124
+
125
+ // 最后一个区间:从最后订单结束价格到最大价格
126
+ if (validOrders.length > 0 && lpPairs.length < maxCount) {
127
+ const lastOrderEndPrice = BigInt(validOrders[validOrders.length - 1].lock_lp_end_price);
128
+ const buyResult = CurveAMM.buyFromPriceToPrice(lastOrderEndPrice + 1n, CurveAMM.MAX_U128_PRICE);
129
+ if (buyResult) {
130
+ const [solAmount, tokenAmount] = buyResult;
131
+ lpPairs.push({
132
+ solAmount: new anchor.BN(solAmount.toString()),
133
+ tokenAmount: new anchor.BN(tokenAmount.toString())
134
+ });
135
+ }
136
+ }
137
+
138
+ } else {
139
+ // 做多订单 - 分析价格下跌时的流动性需求
140
+
141
+ // 第一个区间:从当前价格到第一个订单开始价格
142
+ if (validOrders.length > 0) {
143
+ const firstOrderStartPrice = BigInt(validOrders[0].lock_lp_start_price);
144
+ if (currentPriceU128 > firstOrderStartPrice) {
145
+ const sellResult = CurveAMM.sellFromPriceToPrice(currentPriceU128, firstOrderStartPrice + 1n);
146
+ if (sellResult) {
147
+ const [tokenAmount, solAmount] = sellResult;
148
+ lpPairs.push({
149
+ solAmount: new anchor.BN(solAmount.toString()),
150
+ tokenAmount: new anchor.BN(tokenAmount.toString())
151
+ });
152
+ }
153
+ }
154
+ }
155
+
156
+ // 中间区间:订单之间的空隙
157
+ for (let i = 0; i < validOrders.length - 1 && lpPairs.length < maxCount; i++) {
158
+ const currentOrderEndPrice = BigInt(validOrders[i].lock_lp_end_price);
159
+ const nextOrderStartPrice = BigInt(validOrders[i + 1].lock_lp_start_price);
160
+
161
+ if (currentOrderEndPrice - 1n > nextOrderStartPrice) {
162
+ const sellResult = CurveAMM.sellFromPriceToPrice(currentOrderEndPrice - 1n, nextOrderStartPrice + 1n);
163
+ if (sellResult) {
164
+ const [tokenAmount, solAmount] = sellResult;
165
+ lpPairs.push({
166
+ solAmount: new anchor.BN(solAmount.toString()),
167
+ tokenAmount: new anchor.BN(tokenAmount.toString())
168
+ });
169
+ }
170
+ }
171
+ }
172
+
173
+ // 最后一个区间:从最后订单结束价格到最小价格
174
+ if (validOrders.length > 0 && lpPairs.length < maxCount) {
175
+ const lastOrderEndPrice = BigInt(validOrders[validOrders.length - 1].lock_lp_end_price);
176
+ const sellResult = CurveAMM.sellFromPriceToPrice(lastOrderEndPrice - 1n, CurveAMM.MIN_U128_PRICE);
177
+ if (sellResult) {
178
+ const [tokenAmount, solAmount] = sellResult;
179
+ lpPairs.push({
180
+ solAmount: new anchor.BN(solAmount.toString()),
181
+ tokenAmount: new anchor.BN(tokenAmount.toString())
182
+ });
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ // 补齐到 maxCount 个元素
189
+ while (lpPairs.length < maxCount) {
190
+ lpPairs.push({
191
+ solAmount: new anchor.BN(0),
192
+ tokenAmount: new anchor.BN(0)
193
+ });
194
+ }
195
+
196
+ // 确保不超过 maxCount
197
+ if (lpPairs.length > maxCount) {
198
+ lpPairs.splice(maxCount);
199
+ }
200
+
201
+ return lpPairs;
202
+ }
203
+
204
+ /**
205
+ * 构建订单账户数组(用于交易)
206
+ * Build Order Accounts Array (for trading)
207
+ *
208
+ * @param {Array} orders - 订单数组 Order array
209
+ * @param {number} maxCount - 最大订单数量,默认20 Max order count, default 20
210
+ * @returns {Array} 订单账户地址数组,格式: [string, string, ..., null, null]
211
+ * Order account address array, format: [string, string, ..., null, null]
212
+ *
213
+ * @example
214
+ * const ordersData = await sdk.fast.orders(mint, { type: 'down_orders' });
215
+ * const orderAccounts = OrderUtils.buildOrderAccounts(ordersData.data.orders);
216
+ * // 返回 Returns: [
217
+ * // "4fvsPDNoRRacSzE3PkEuNQeTNWMaeFqGwUxCnEbR1Dzb",
218
+ * // "G4nHBYX8EbrP8r35pk5TfpvJZfGNyLnd4qsfT7ru5vLd",
219
+ * // ...
220
+ * // null, null
221
+ * // ]
222
+ *
223
+ * // 或者指定最大数量 Or specify max count
224
+ * const orderAccounts = OrderUtils.buildOrderAccounts(ordersData.data.orders, 10);
225
+ */
226
+ static buildOrderAccounts(orders, maxCount = 10) {
227
+ // 参数验证 Parameter validation
228
+ if (!Array.isArray(orders)) {
229
+ throw new Error('buildOrderAccounts: orders 必须是数组 orders must be an array');
230
+ }
231
+
232
+ if (!Number.isInteger(maxCount) || maxCount <= 0) {
233
+ throw new Error('buildOrderAccounts: maxCount 必须是正整数 maxCount must be a positive integer');
234
+ }
235
+
236
+ const orderAccounts = [];
237
+
238
+ for (let i = 0; i < maxCount; i++) {
239
+ if (i < orders.length && orders[i] && orders[i].order_pda) {
240
+ // 验证 order_pda 格式 Validate order_pda format
241
+ if (typeof orders[i].order_pda !== 'string' || orders[i].order_pda.trim() === '') {
242
+ console.warn(`buildOrderAccounts: 订单 ${i} 的 order_pda 格式无效: ${orders[i].order_pda}`);
243
+ orderAccounts.push(null);
244
+ } else {
245
+ orderAccounts.push(orders[i].order_pda);
246
+ }
247
+ } else {
248
+ // 填充空值 Fill null values
249
+ orderAccounts.push(null);
250
+ }
251
+ }
252
+
253
+ return orderAccounts;
254
+ }
255
+
256
+ /**
257
+ * 查找订单的前后节点
258
+ * Find Previous and Next Order
259
+ *
260
+ * @param {Array} orders - 订单数组 Order array
261
+ * @param {string} findOrderPda - 要查找的订单PDA地址 Target order PDA address to find
262
+ * @returns {Object} 返回 { prevOrder: Object|null, nextOrder: Object|null }
263
+ * Returns { prevOrder: Object|null, nextOrder: Object|null }
264
+ *
265
+ * @example
266
+ * const ordersData = await sdk.fast.orders(mint, { type: 'down_orders' });
267
+ * const result = OrderUtils.findPrevNext(ordersData.data.orders, 'E2T72D4wZdxHRjELN5VnRdcCvS4FPcYBBT3UBEoaC5cA');
268
+ * // 返回格式 Return format:
269
+ * // {
270
+ * // prevOrder: { order_pda: "...", user: "...", ... } | null,
271
+ * // nextOrder: { order_pda: "...", user: "...", ... } | null
272
+ * // }
273
+ *
274
+ * // 使用返回的数据 Use returned data:
275
+ * if (result.prevOrder) {
276
+ * console.log('前一个订单 Previous Order:', result.prevOrder.order_pda);
277
+ * }
278
+ * if (result.nextOrder) {
279
+ * console.log('后一个订单 Next Order:', result.nextOrder.order_pda);
280
+ * }
281
+ */
282
+ static findPrevNext(orders, findOrderPda) {
283
+ // 参数验证 Parameter validation
284
+ if (!Array.isArray(orders)) {
285
+ throw new Error('findPrevNext: orders 参数必须是数组 orders parameter must be an array');
286
+ }
287
+
288
+ if (!findOrderPda || typeof findOrderPda !== 'string') {
289
+ throw new Error('findPrevNext: findOrderPda 参数必须是有效的字符串 findOrderPda parameter must be a valid string');
290
+ }
291
+
292
+ // 查找目标订单的索引 Find target order index
293
+ let targetIndex = -1;
294
+ for (let i = 0; i < orders.length; i++) {
295
+ if (orders[i] && orders[i].order_pda === findOrderPda) {
296
+ targetIndex = i;
297
+ break;
298
+ }
299
+ }
300
+
301
+ // 如果没找到目标订单 If target order not found
302
+ if (targetIndex === -1) {
303
+ console.log(`findPrevNext: 未找到指定的订单PDA Order PDA not found: ${findOrderPda} orders.length = ${orders.length} `);
304
+ return {
305
+ prevOrder: null,
306
+ nextOrder: null
307
+ };
308
+ }
309
+
310
+ // 获取前一个订单 Get previous order
311
+ let prevOrder = null;
312
+ if (targetIndex > 0 && orders[targetIndex - 1]) {
313
+ prevOrder = orders[targetIndex - 1];
314
+ }
315
+
316
+ // 获取下一个订单 Get next order
317
+ let nextOrder = null;
318
+ if (targetIndex < orders.length - 1 && orders[targetIndex + 1]) {
319
+ nextOrder = orders[targetIndex + 1];
320
+ }
321
+
322
+ console.log(`findPrevNext: 找到目标订单索引 Found target order at index ${targetIndex}`);
323
+ console.log(`findPrevNext: prevOrder = ${prevOrder ? prevOrder.order_pda : 'null'}`);
324
+ console.log(`findPrevNext: nextOrder = ${nextOrder ? nextOrder.order_pda : 'null'}`);
325
+
326
+ return {
327
+ prevOrder,
328
+ nextOrder
329
+ };
330
+ }
331
+
332
+ /**
333
+ * 获取 PDA 地址在订单数组中的位置
334
+ * Get PDA Address Position in Orders Array
335
+ *
336
+ * @param {Array} orders - 订单数组 Order array
337
+ * @param {string|PublicKey} targetOrderPda - 目标订单PDA地址 Target order PDA address
338
+ * @returns {number} PDA地址在数组中的索引位置,如果没有找到返回200
339
+ * Index position of PDA address in array, returns 200 if not found
340
+ *
341
+ * @example
342
+ * // 在 closeLong 或 closeShort 中使用 Usage in closeLong or closeShort:
343
+ * const ordersData = await sdk.data.orders(mint.toString(), {
344
+ * type: 'down_orders',
345
+ * limit: sdk.MAX_ORDERS_COUNT + 1
346
+ * });
347
+ *
348
+ * const closeOrderPubkey = new PublicKey("E2T72D4wZdxHRjELN5VnRdcCvS4FPcYBBT3UBEoaC5cA");
349
+ * const orderIndex = OrderUtils.findOrderIndex(ordersData.data.orders, closeOrderPubkey);
350
+ *
351
+ * if (orderIndex !== 200) {
352
+ * console.log(`订单在数组中的位置:${orderIndex} Order position in array: ${orderIndex}`);
353
+ * } else {
354
+ * console.log('订单未找到 Order not found');
355
+ * }
356
+ *
357
+ * // 也支持字符串格式的PDA地址 Also supports string format PDA address:
358
+ * const orderIndex2 = OrderUtils.findOrderIndex(ordersData.data.orders, "E2T72D4wZdxHRjELN5VnRdcCvS4FPcYBBT3UBEoaC5cA");
359
+ */
360
+ static findOrderIndex(orders, targetOrderPda) {
361
+ // 参数验证 Parameter validation
362
+ if (!Array.isArray(orders)) {
363
+ throw new Error('findOrderIndex: orders 参数必须是数组 orders parameter must be an array');
364
+ }
365
+
366
+ if (!targetOrderPda) {
367
+ console.log('findOrderIndex: targetOrderPda 为空,返回 200 targetOrderPda is empty, returning 200');
368
+ return 200;
369
+ }
370
+
371
+ // 将 PublicKey 类型转换为字符串,如果已经是字符串则保持不变
372
+ // Convert PublicKey type to string, keep unchanged if already string
373
+ const targetPdaString = typeof targetOrderPda === 'string'
374
+ ? targetOrderPda
375
+ : targetOrderPda.toString();
376
+
377
+ // 遍历订单数组查找目标PDA地址
378
+ // Iterate through orders array to find target PDA address
379
+ for (let i = 0; i < orders.length; i++) {
380
+ if (orders[i] && orders[i].order_pda === targetPdaString) {
381
+ console.log(`findOrderIndex: 找到订单位置 Found order at position ${i} for PDA: ${targetPdaString}`);
382
+ return i;
383
+ }
384
+ }
385
+
386
+ // 没有找到时返回200
387
+ // Return 200 when not found
388
+ console.log(`findOrderIndex: 订单未找到 Order not found for PDA: ${targetPdaString} orders.length=${orders.length}`);
389
+ return 200;
390
+ }
391
+
392
+ /**
393
+ * 验证订单数组格式
394
+ * Validate Orders Array Format
395
+ *
396
+ * @param {Array} orders - 订单数组 Order array
397
+ * @param {boolean} throwOnError - 是否抛出错误,默认true Whether to throw error, default true
398
+ * @returns {boolean|Object} 验证结果 Validation result
399
+ *
400
+ * @example
401
+ * const ordersData = await sdk.fast.orders(mint, { type: 'down_orders' });
402
+ * const isValid = OrderUtils.validateOrdersFormat(ordersData.data.orders);
403
+ *
404
+ * // 或者获取详细验证结果 Or get detailed validation result
405
+ * const result = OrderUtils.validateOrdersFormat(ordersData.data.orders, false);
406
+ * // {
407
+ * // valid: true,
408
+ * // errors: [],
409
+ * // warnings: []
410
+ * // }
411
+ */
412
+ static validateOrdersFormat(orders, throwOnError = true) {
413
+ const errors = [];
414
+ const warnings = [];
415
+
416
+ // 基本类型检查 Basic type check
417
+ if (!Array.isArray(orders)) {
418
+ const error = 'orders 必须是数组 orders must be an array';
419
+ if (throwOnError) {
420
+ throw new Error(`validateOrdersFormat: ${error}`);
421
+ }
422
+ errors.push(error);
423
+ return { valid: false, errors, warnings };
424
+ }
425
+
426
+ // 检查每个订单的必需字段 Check required fields for each order
427
+ const requiredFields = [
428
+ 'order_pda', 'user', 'mint', 'order_type',
429
+ 'lock_lp_sol_amount', 'lock_lp_token_amount',
430
+ 'margin_sol_amount', 'borrow_amount', 'position_asset_amount'
431
+ ];
432
+
433
+ orders.forEach((order, index) => {
434
+ if (!order) {
435
+ warnings.push(`订单 ${index} 为空 Order ${index} is null`);
436
+ return;
437
+ }
438
+
439
+ requiredFields.forEach(field => {
440
+ if (!(field in order)) {
441
+ warnings.push(`订单 ${index} 缺少字段 ${field} Order ${index} missing field ${field}`);
442
+ }
443
+ });
444
+
445
+ // 检查 order_pda 格式 Check order_pda format
446
+ if (order.order_pda && typeof order.order_pda !== 'string') {
447
+ warnings.push(`订单 ${index} 的 order_pda 不是字符串 Order ${index} order_pda is not string`);
448
+ }
449
+ });
450
+
451
+ const isValid = errors.length === 0;
452
+
453
+ if (throwOnError && !isValid) {
454
+ throw new Error(`validateOrdersFormat: 验证失败 Validation failed: ${errors.join(', ')}`);
455
+ }
456
+
457
+ return {
458
+ valid: isValid,
459
+ errors,
460
+ warnings
461
+ };
462
+ }
463
+ }
464
+
465
+ module.exports = OrderUtils;