100x-sdk 1.0.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,848 @@
1
+ const { ComputeBudgetProgram, PublicKey, Transaction, SystemProgram, SYSVAR_RENT_PUBKEY } = require('@solana/web3.js');
2
+ const { createAssociatedTokenAccountInstruction, getAssociatedTokenAddress, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } = require('@solana/spl-token');
3
+ const anchor = require('@coral-xyz/anchor');
4
+ // 统一使用 buffer 包,所有平台一致
5
+ const { Buffer } = require('buffer');
6
+ const { MAX_CANDIDATE_INDICES } = require('./simulator/utils');
7
+
8
+ // 环境检测和条件加载
9
+ const IS_NODE = typeof process !== 'undefined' && process.versions && process.versions.node;
10
+
11
+ /**
12
+ * Trading Module
13
+ * Handles buy/sell and long/short trading operations
14
+ */
15
+ class TradingModule {
16
+ constructor(sdk) {
17
+ this.sdk = sdk;
18
+ }
19
+
20
+ /**
21
+ * Buy tokens
22
+ * @param {Object} params - Buy parameters
23
+ * @param {string|PublicKey} params.mintAccount - Token mint account address
24
+ * @param {anchor.BN} params.buyTokenAmount - Amount of tokens to buy
25
+ * @param {anchor.BN} params.maxSolAmount - Maximum SOL to spend
26
+ * @param {PublicKey} params.payer - Payer public key
27
+ * @param {Object} options - Optional parameters
28
+ * @param {number} options.computeUnits - Compute units limit, default 1400000
29
+ * @returns {Promise<Object>} Object containing transaction, signers and account info
30
+ *
31
+ * @example
32
+ * const result = await sdk.trading.buy({
33
+ * mintAccount: "HZBos3RNhExDcAtzmdKXhTd4sVcQFBiT3FDBgmBBMk7",
34
+ * buyTokenAmount: new anchor.BN("1000000000"),
35
+ * maxSolAmount: new anchor.BN("2000000000"),
36
+ * payer: wallet.publicKey
37
+ * });
38
+ */
39
+ async buy({ mintAccount, buyTokenAmount, maxSolAmount, payer }, options = {}) {
40
+ const { computeUnits = 1400000 } = options;
41
+
42
+ // 1. Parameter validation and conversion
43
+ const mint = typeof mintAccount === 'string' ? new PublicKey(mintAccount) : mintAccount;
44
+
45
+ if (!anchor.BN.isBN(buyTokenAmount) || !anchor.BN.isBN(maxSolAmount)) {
46
+ throw new Error('buyTokenAmount and maxSolAmount must be anchor.BN type');
47
+ }
48
+
49
+ // 2. Calculate PDA accounts
50
+ const accounts = this._calculatePDAAccounts(mint);
51
+
52
+ // 3. Calculate orderbook PDAs
53
+ const [upOrderbook] = PublicKey.findProgramAddressSync(
54
+ [Buffer.from('up_orderbook'), mint.toBuffer()],
55
+ this.sdk.programId
56
+ );
57
+
58
+ const [downOrderbook] = PublicKey.findProgramAddressSync(
59
+ [Buffer.from('down_orderbook'), mint.toBuffer()],
60
+ this.sdk.programId
61
+ );
62
+
63
+ // 4. Get user token account
64
+ const userTokenAccount = await getAssociatedTokenAddress(
65
+ mint,
66
+ payer
67
+ );
68
+
69
+ // 5. Check if user token account exists, create if not
70
+ const userTokenAccountInfo = await this.sdk.connection.getAccountInfo(userTokenAccount);
71
+ const createAtaIx = userTokenAccountInfo === null
72
+ ? createAssociatedTokenAccountInstruction(
73
+ payer,
74
+ userTokenAccount,
75
+ payer,
76
+ mint,
77
+ TOKEN_PROGRAM_ID,
78
+ ASSOCIATED_TOKEN_PROGRAM_ID
79
+ )
80
+ : null;
81
+
82
+ // 6. Get fee recipient accounts from curve account (skipBalances: only need addresses)
83
+ const curveAccountInfo = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
84
+ const feeRecipientAccount = new PublicKey(curveAccountInfo.feeRecipient);
85
+ const baseFeeRecipientAccount = new PublicKey(curveAccountInfo.baseFeeRecipient);
86
+
87
+ // 6.5. Calculate cooldown PDA
88
+ const [cooldownPDA] = PublicKey.findProgramAddressSync(
89
+ [
90
+ Buffer.from('trade_cooldown'),
91
+ mint.toBuffer(),
92
+ payer.toBuffer()
93
+ ],
94
+ this.sdk.programId
95
+ );
96
+
97
+ // 7. Build transaction instructions
98
+ const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
99
+ units: computeUnits
100
+ });
101
+
102
+ const buyIx = await this.sdk.program.methods
103
+ .buy(buyTokenAmount, maxSolAmount)
104
+ .accounts({
105
+ payer: payer,
106
+ mintAccount: mint,
107
+ curveAccount: accounts.curveAccount,
108
+ poolTokenAccount: accounts.poolTokenAccount,
109
+ poolSolAccount: accounts.poolSolAccount,
110
+ upOrderbook: upOrderbook,
111
+ downOrderbook: downOrderbook,
112
+ userTokenAccount: userTokenAccount,
113
+ tokenProgram: TOKEN_PROGRAM_ID,
114
+ systemProgram: SystemProgram.programId,
115
+ rent: SYSVAR_RENT_PUBKEY,
116
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
117
+ feeRecipientAccount: feeRecipientAccount,
118
+ baseFeeRecipientAccount: baseFeeRecipientAccount,
119
+ cooldown: cooldownPDA
120
+ })
121
+ .instruction();
122
+
123
+ // 8. Create transaction and add instructions
124
+ const transaction = new Transaction();
125
+ transaction.add(modifyComputeUnits);
126
+
127
+ // If user token account doesn't exist, create it first
128
+ if (createAtaIx) {
129
+ transaction.add(createAtaIx);
130
+ }
131
+
132
+ transaction.add(buyIx);
133
+
134
+ // 9. Return transaction object and related info
135
+ return {
136
+ transaction,
137
+ signers: [], // Buy transaction doesn't need additional signers, only payer signature
138
+ accounts: {
139
+ mint: mint,
140
+ curveAccount: accounts.curveAccount,
141
+ poolTokenAccount: accounts.poolTokenAccount,
142
+ poolSolAccount: accounts.poolSolAccount,
143
+ upOrderbook: upOrderbook,
144
+ downOrderbook: downOrderbook,
145
+ userTokenAccount: userTokenAccount,
146
+ payer: payer,
147
+ feeRecipientAccount: feeRecipientAccount,
148
+ baseFeeRecipientAccount: baseFeeRecipientAccount,
149
+ cooldown: cooldownPDA
150
+ }
151
+ };
152
+ }
153
+
154
+ /**
155
+
156
+ /**
157
+ * Sell tokens
158
+ * @param {Object} params - Sell parameters
159
+ * @param {string|PublicKey} params.mintAccount - Token mint account address
160
+ * @param {anchor.BN} params.sellTokenAmount - Amount of tokens to sell
161
+ * @param {anchor.BN} params.minSolOutput - Minimum SOL output
162
+ * @param {PublicKey} params.payer - Payer public key
163
+ * @param {Object} options - Optional parameters
164
+ * @param {number} options.computeUnits - Compute units limit, default 1400000
165
+ * @returns {Promise<Object>} Object containing transaction, signers and account info
166
+ *
167
+ * @example
168
+ * const result = await sdk.trading.sell({
169
+ * mintAccount: "HZBos3RNhExDcAtzmdKXhTd4sVcQFBiT3FDBgmBBMk7",
170
+ * sellTokenAmount: new anchor.BN("1000000000"),
171
+ * minSolOutput: new anchor.BN("2000000000"),
172
+ * payer: wallet.publicKey
173
+ * });
174
+ */
175
+ async sell({ mintAccount, sellTokenAmount, minSolOutput, payer }, options = {}) {
176
+ const { computeUnits = 1400000 } = options;
177
+
178
+ // 1. Parameter validation and conversion
179
+ const mint = typeof mintAccount === 'string' ? new PublicKey(mintAccount) : mintAccount;
180
+
181
+ if (!anchor.BN.isBN(sellTokenAmount) || !anchor.BN.isBN(minSolOutput)) {
182
+ throw new Error('sellTokenAmount and minSolOutput must be anchor.BN type');
183
+ }
184
+
185
+ // 2. Calculate PDA accounts
186
+ const accounts = this._calculatePDAAccounts(mint);
187
+
188
+ // 3. Calculate orderbook PDAs
189
+ const [upOrderbook] = PublicKey.findProgramAddressSync(
190
+ [Buffer.from('up_orderbook'), mint.toBuffer()],
191
+ this.sdk.programId
192
+ );
193
+
194
+ const [downOrderbook] = PublicKey.findProgramAddressSync(
195
+ [Buffer.from('down_orderbook'), mint.toBuffer()],
196
+ this.sdk.programId
197
+ );
198
+
199
+ // 4. Get user token account
200
+ const userTokenAccount = await getAssociatedTokenAddress(
201
+ mint,
202
+ payer
203
+ );
204
+
205
+ // 5. Check if user token account exists, create if not
206
+ const userTokenAccountInfo = await this.sdk.connection.getAccountInfo(userTokenAccount);
207
+ const createAtaIx = userTokenAccountInfo === null
208
+ ? createAssociatedTokenAccountInstruction(
209
+ payer,
210
+ userTokenAccount,
211
+ payer,
212
+ mint,
213
+ TOKEN_PROGRAM_ID,
214
+ ASSOCIATED_TOKEN_PROGRAM_ID
215
+ )
216
+ : null;
217
+
218
+ // 6. Get fee recipient accounts from curve account (skipBalances: only need addresses)
219
+ const curveAccountInfo = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
220
+ const feeRecipientAccount = new PublicKey(curveAccountInfo.feeRecipient);
221
+ const baseFeeRecipientAccount = new PublicKey(curveAccountInfo.baseFeeRecipient);
222
+
223
+ // 6.5. Calculate cooldown PDA
224
+ const [cooldownPDA] = PublicKey.findProgramAddressSync(
225
+ [
226
+ Buffer.from('trade_cooldown'),
227
+ mint.toBuffer(),
228
+ payer.toBuffer()
229
+ ],
230
+ this.sdk.programId
231
+ );
232
+
233
+ // 7. Build transaction instructions
234
+ const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
235
+ units: computeUnits
236
+ });
237
+
238
+ const sellIx = await this.sdk.program.methods
239
+ .sell(sellTokenAmount, minSolOutput)
240
+ .accounts({
241
+ payer: payer,
242
+ mintAccount: mint,
243
+ curveAccount: accounts.curveAccount,
244
+ poolTokenAccount: accounts.poolTokenAccount,
245
+ poolSolAccount: accounts.poolSolAccount,
246
+ upOrderbook: upOrderbook,
247
+ downOrderbook: downOrderbook,
248
+ userTokenAccount: userTokenAccount,
249
+ tokenProgram: TOKEN_PROGRAM_ID,
250
+ systemProgram: SystemProgram.programId,
251
+ rent: SYSVAR_RENT_PUBKEY,
252
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
253
+ feeRecipientAccount: feeRecipientAccount,
254
+ baseFeeRecipientAccount: baseFeeRecipientAccount,
255
+ cooldown: cooldownPDA
256
+ })
257
+ .instruction();
258
+
259
+ // 8. Create transaction and add instructions
260
+ const transaction = new Transaction();
261
+ transaction.add(modifyComputeUnits);
262
+
263
+ // If user token account doesn't exist, create it first
264
+ if (createAtaIx) {
265
+ transaction.add(createAtaIx);
266
+ }
267
+
268
+ transaction.add(sellIx);
269
+
270
+ // 9. Return transaction object and related info
271
+ return {
272
+ transaction,
273
+ signers: [], // Sell transaction doesn't need additional signers, only payer signature
274
+ accounts: {
275
+ mint: mint,
276
+ curveAccount: accounts.curveAccount,
277
+ poolTokenAccount: accounts.poolTokenAccount,
278
+ poolSolAccount: accounts.poolSolAccount,
279
+ upOrderbook: upOrderbook,
280
+ downOrderbook: downOrderbook,
281
+ userTokenAccount: userTokenAccount,
282
+ payer: payer,
283
+ feeRecipientAccount: feeRecipientAccount,
284
+ baseFeeRecipientAccount: baseFeeRecipientAccount,
285
+ cooldown: cooldownPDA
286
+ }
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Margin Long
292
+ * @param {Object} params - Long parameters
293
+ * @param {string|PublicKey} params.mintAccount - Token mint account address
294
+ * @param {anchor.BN} params.buyTokenAmount - Amount of tokens to buy (确定值)
295
+ * @param {anchor.BN} params.maxSolAmount - Maximum SOL to spend (实际有可能会少点)
296
+ * @param {anchor.BN} params.marginSol - Margin amount (保证金数量 SOL)
297
+ * @param {anchor.BN} params.closePrice - Close price (平仓价格/止损价格)
298
+ * @param {Array<number>} params.closeInsertIndices - Close insert indices array (平仓时插入订单簿的位置索引数组)
299
+ * @param {PublicKey} params.payer - Payer public key
300
+ * @param {Object} options - Optional parameters
301
+ * @param {number} options.computeUnits - Compute units limit, default 1400000
302
+ * @returns {Promise<Object>} Object containing transaction, signers and account info
303
+ *
304
+ * @example
305
+ * const result = await sdk.trading.long({
306
+ * mintAccount: "HZBos3RNhExDcAtzmdKXhTd4sVcQFBiT3FDBgmBBMk7",
307
+ * buyTokenAmount: new anchor.BN("10000000"),
308
+ * maxSolAmount: new anchor.BN("1100000000"),
309
+ * marginSol: new anchor.BN("2200000000"),
310
+ * closePrice: new anchor.BN("1000000000000000"),
311
+ * closeInsertIndices: [0, 1, 2], // 插入位置索引数组
312
+ * payer: wallet.publicKey
313
+ * });
314
+ */
315
+ async long({ mintAccount, buyTokenAmount, maxSolAmount, marginSol, closePrice, closeInsertIndices, payer }, options = {}) {
316
+ const { computeUnits = 1400000 } = options;
317
+
318
+ // 1. 参数验证和转换
319
+ const mint = typeof mintAccount === 'string' ? new PublicKey(mintAccount) : mintAccount;
320
+
321
+ if (!anchor.BN.isBN(buyTokenAmount) || !anchor.BN.isBN(maxSolAmount) ||
322
+ !anchor.BN.isBN(marginSol) || !anchor.BN.isBN(closePrice)) {
323
+ throw new Error('All amount parameters must be anchor.BN type');
324
+ }
325
+
326
+ if (!Array.isArray(closeInsertIndices) || closeInsertIndices.length === 0) {
327
+ throw new Error('closeInsertIndices must be a non-empty array');
328
+ }
329
+
330
+ if (closeInsertIndices.length > 20) {
331
+ throw new Error('closeInsertIndices array cannot exceed 20 elements');
332
+ }
333
+
334
+ // 2. Calculate PDA accounts
335
+ const accounts = this._calculatePDAAccounts(mint);
336
+
337
+ // 3. Calculate OrderBook PDA addresses
338
+ const [upOrderbook] = PublicKey.findProgramAddressSync(
339
+ [Buffer.from("up_orderbook"), mint.toBuffer()],
340
+ this.sdk.programId
341
+ );
342
+
343
+ const [downOrderbook] = PublicKey.findProgramAddressSync(
344
+ [Buffer.from("down_orderbook"), mint.toBuffer()],
345
+ this.sdk.programId
346
+ );
347
+
348
+ // 4. Build transaction instructions
349
+ const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
350
+ units: computeUnits
351
+ });
352
+
353
+ const longIx = await this.sdk.program.methods
354
+ .long(
355
+ buyTokenAmount,
356
+ maxSolAmount,
357
+ marginSol,
358
+ closePrice,
359
+ closeInsertIndices
360
+ )
361
+ .accounts({
362
+ payer: payer,
363
+ mintAccount: mint,
364
+ curveAccount: accounts.curveAccount,
365
+ poolTokenAccount: accounts.poolTokenAccount,
366
+ poolSolAccount: accounts.poolSolAccount,
367
+ tokenProgram: TOKEN_PROGRAM_ID,
368
+ systemProgram: SystemProgram.programId,
369
+ rent: SYSVAR_RENT_PUBKEY,
370
+ feeRecipientAccount: this.sdk.feeRecipient,
371
+ baseFeeRecipientAccount: this.sdk.baseFeeRecipient,
372
+ upOrderbook: upOrderbook,
373
+ downOrderbook: downOrderbook
374
+ })
375
+ .instruction();
376
+
377
+ // 5. Create transaction and add instructions
378
+ const transaction = new Transaction();
379
+ transaction.add(modifyComputeUnits);
380
+ transaction.add(longIx);
381
+
382
+ // 6. Return transaction object and related info
383
+ return {
384
+ transaction,
385
+ signers: [], // Long transaction doesn't need additional signers, only payer signature
386
+ accounts: {
387
+ mint: mint,
388
+ curveAccount: accounts.curveAccount,
389
+ poolTokenAccount: accounts.poolTokenAccount,
390
+ poolSolAccount: accounts.poolSolAccount,
391
+ payer: payer,
392
+ upOrderbook: upOrderbook,
393
+ downOrderbook: downOrderbook
394
+ },
395
+ orderData: {
396
+ closeInsertIndices: closeInsertIndices
397
+ }
398
+ };
399
+ }
400
+
401
+ /**
402
+ * 保证金做空 / Margin Short
403
+ * @param {Object} params - 做空参数 / Short parameters
404
+ * @param {string|PublicKey} params.mintAccount - 代币铸造账户地址 / Token mint account address
405
+ * @param {anchor.BN} params.borrowSellTokenAmount - 借出卖出的代币数量 (希望卖出的token数量) / Borrowed token amount to sell
406
+ * @param {anchor.BN} params.minSolOutput - 最小 SOL 输出 (卖出后最少得到的sol数量) / Minimum SOL output
407
+ * @param {anchor.BN} params.marginSol - 保证金数量 (SOL) / Margin amount
408
+ * @param {anchor.BN} params.closePrice - 平仓价格 (止损价格) / Close price
409
+ * @param {Array<number>} params.closeInsertIndices - Close insert indices array (平仓时插入订单簿的位置索引数组)
410
+ * @param {PublicKey} params.payer - 支付者公钥 / Payer public key
411
+ * @param {Object} options - 可选参数 / Optional parameters
412
+ * @param {number} options.computeUnits - 计算单元限制,默认 1400000 / Compute units limit, default 1400000
413
+ * @returns {Promise<Object>} 包含交易对象、签名者和账户信息的对象 / Object containing transaction, signers and account info
414
+ *
415
+ * @example
416
+ * const result = await sdk.trading.short({
417
+ * mintAccount: "HZBos3RNhExDcAtzmdKXhTd4sVcQFBiT3FDBgmBBMk7",
418
+ * borrowSellTokenAmount: new anchor.BN("1000000000"),
419
+ * minSolOutput: new anchor.BN("100"),
420
+ * marginSol: new anchor.BN("2200000000"),
421
+ * closePrice: new anchor.BN("1000000000000000"),
422
+ * closeInsertIndices: [0, 1, 2], // 插入位置索引数组
423
+ * payer: wallet.publicKey
424
+ * });
425
+ */
426
+ async short({ mintAccount, borrowSellTokenAmount, minSolOutput, marginSol, closePrice, closeInsertIndices, payer }, options = {}) {
427
+ const { computeUnits = 1400000 } = options;
428
+
429
+ // 1. 参数验证和转换 / Parameter validation and conversion
430
+ const mint = typeof mintAccount === 'string' ? new PublicKey(mintAccount) : mintAccount;
431
+
432
+ if (!anchor.BN.isBN(borrowSellTokenAmount) || !anchor.BN.isBN(minSolOutput) ||
433
+ !anchor.BN.isBN(marginSol) || !anchor.BN.isBN(closePrice)) {
434
+ throw new Error('所有金额参数必须是 anchor.BN 类型 / All amount parameters must be anchor.BN type');
435
+ }
436
+
437
+ if (!Array.isArray(closeInsertIndices) || closeInsertIndices.length === 0) {
438
+ throw new Error('closeInsertIndices must be a non-empty array');
439
+ }
440
+
441
+ if (closeInsertIndices.length > 20) {
442
+ throw new Error('closeInsertIndices array cannot exceed 20 elements');
443
+ }
444
+
445
+ // 2. 计算 PDA 账户 / Calculate PDA accounts
446
+ const accounts = this._calculatePDAAccounts(mint);
447
+
448
+ // 3. Calculate OrderBook PDA addresses
449
+ const [upOrderbook] = PublicKey.findProgramAddressSync(
450
+ [Buffer.from("up_orderbook"), mint.toBuffer()],
451
+ this.sdk.programId
452
+ );
453
+
454
+ const [downOrderbook] = PublicKey.findProgramAddressSync(
455
+ [Buffer.from("down_orderbook"), mint.toBuffer()],
456
+ this.sdk.programId
457
+ );
458
+
459
+ // 4. 构建交易指令 / Build transaction instructions
460
+ const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
461
+ units: computeUnits
462
+ });
463
+
464
+ const shortIx = await this.sdk.program.methods
465
+ .short(
466
+ borrowSellTokenAmount,
467
+ minSolOutput,
468
+ marginSol,
469
+ closePrice,
470
+ closeInsertIndices
471
+ )
472
+ .accounts({
473
+ payer: payer,
474
+ mintAccount: mint,
475
+ curveAccount: accounts.curveAccount,
476
+ poolTokenAccount: accounts.poolTokenAccount,
477
+ poolSolAccount: accounts.poolSolAccount,
478
+ tokenProgram: TOKEN_PROGRAM_ID,
479
+ systemProgram: SystemProgram.programId,
480
+ rent: SYSVAR_RENT_PUBKEY,
481
+ feeRecipientAccount: this.sdk.feeRecipient,
482
+ baseFeeRecipientAccount: this.sdk.baseFeeRecipient,
483
+ upOrderbook: upOrderbook,
484
+ downOrderbook: downOrderbook
485
+ })
486
+ .instruction();
487
+
488
+ // 5. 创建交易并添加指令 / Create transaction and add instructions
489
+ const transaction = new Transaction();
490
+ transaction.add(modifyComputeUnits);
491
+ transaction.add(shortIx);
492
+
493
+ // 6. 返回交易对象和相关信息 / Return transaction object and related info
494
+ return {
495
+ transaction,
496
+ signers: [], // 做空交易不需要额外的签名者,只需要 payer 签名 / Short transaction doesn't need additional signers, only payer signature
497
+ accounts: {
498
+ mint: mint,
499
+ curveAccount: accounts.curveAccount,
500
+ poolTokenAccount: accounts.poolTokenAccount,
501
+ poolSolAccount: accounts.poolSolAccount,
502
+ payer: payer,
503
+ upOrderbook: upOrderbook,
504
+ downOrderbook: downOrderbook
505
+ },
506
+ orderData: {
507
+ closeInsertIndices: closeInsertIndices
508
+ }
509
+ };
510
+ }
511
+
512
+ /**
513
+ * 平仓做多 / Close Long Position
514
+ * @param {Object} params - 平仓参数 / Close position parameters
515
+ * @param {string|PublicKey} params.mintAccount - 代币铸造账户地址 / Token mint account address
516
+ * @param {anchor.BN} params.sellTokenAmount - 希望卖出的token数量 / Amount of tokens to sell
517
+ * @param {anchor.BN} params.minSolOutput - 卖出后最少得到的sol数量 / Minimum SOL output after selling
518
+ * @param {number|anchor.BN} params.closeOrderId - 订单的唯一编号 / Order unique ID
519
+ * @param {Array<number>} params.closeOrderIndices - 平仓时订单的位置索引数组 / Close order position indices array
520
+ * @param {PublicKey} params.payer - 支付者公钥 / Payer public key
521
+ * @param {PublicKey} params.userSolAccount - 开仓用户的SOL账户(接收资金)/ User SOL account to receive funds (must be order opener)
522
+ * @param {Object} options - 可选参数 / Optional parameters
523
+ * @param {number} options.computeUnits - 计算单元限制,默认1400000 / Compute units limit, default 1400000
524
+ * @returns {Promise<Object>} 包含交易对象、签名者和账户信息的对象 / Object containing transaction, signers and account info
525
+ *
526
+ * @example
527
+ * const result = await sdk.trading.closeLong({
528
+ * mintAccount: "HZBos3RNhExDcAtzmdKXhTd4sVcQFBiT3FDBgmBBMk7",
529
+ * sellTokenAmount: new anchor.BN("1000000000"),
530
+ * minSolOutput: new anchor.BN("100000000"),
531
+ * closeOrderId: 12345, // 订单唯一编号
532
+ * closeOrderIndices: [10, 11, 12], // 候选索引数组
533
+ * payer: wallet.publicKey,
534
+ * userSolAccount: orderOwnerPublicKey
535
+ * });
536
+ */
537
+ async closeLong({ mintAccount, sellTokenAmount, minSolOutput, closeOrderId, closeOrderIndices, payer, userSolAccount }, options = {}) {
538
+ const { computeUnits = 1400000 } = options;
539
+
540
+ // 1. 参数验证和转换 / Parameter validation and conversion
541
+ const mint = typeof mintAccount === 'string' ? new PublicKey(mintAccount) : mintAccount;
542
+
543
+ if (!anchor.BN.isBN(sellTokenAmount) || !anchor.BN.isBN(minSolOutput)) {
544
+ throw new Error('sellTokenAmount and minSolOutput must be anchor.BN type');
545
+ }
546
+
547
+ // 转换 closeOrderId 为 anchor.BN (u64)
548
+ const closeOrderIdBN = anchor.BN.isBN(closeOrderId) ? closeOrderId : new anchor.BN(closeOrderId);
549
+
550
+ // 验证 closeOrderIndices 参数
551
+ if (!Array.isArray(closeOrderIndices) || closeOrderIndices.length === 0) {
552
+ throw new Error('closeOrderIndices must be a non-empty array');
553
+ }
554
+
555
+ if (closeOrderIndices.length > MAX_CANDIDATE_INDICES) {
556
+ throw new Error(`closeOrderIndices array cannot exceed ${MAX_CANDIDATE_INDICES} elements`);
557
+ }
558
+
559
+ // 验证和转换 userSolAccount - 支持字符串或 PublicKey 对象
560
+ let userSolAccountPubkey;
561
+ if (!userSolAccount) {
562
+ throw new Error('userSolAccount is required');
563
+ }
564
+
565
+ if (typeof userSolAccount === 'string') {
566
+ try {
567
+ userSolAccountPubkey = new PublicKey(userSolAccount);
568
+ } catch (error) {
569
+ throw new Error('userSolAccount must be a valid PublicKey string');
570
+ }
571
+ } else if (userSolAccount instanceof PublicKey) {
572
+ userSolAccountPubkey = userSolAccount;
573
+ } else if (userSolAccount.constructor && userSolAccount.constructor.name === 'PublicKey') {
574
+ // 处理跨包 PublicKey 实例 - 提取字符串后重新创建
575
+ try {
576
+ userSolAccountPubkey = new PublicKey(userSolAccount.toString());
577
+ } catch (error) {
578
+ throw new Error('userSolAccount must be a valid PublicKey');
579
+ }
580
+ } else {
581
+ throw new Error('userSolAccount must be a valid PublicKey or PublicKey string');
582
+ }
583
+
584
+ // 2. 计算 PDA 账户 / Calculate PDA accounts
585
+ const accounts = this._calculatePDAAccounts(mint);
586
+
587
+ // 3. 计算 OrderBook PDA 地址 / Calculate OrderBook PDA addresses
588
+ const [upOrderbook] = PublicKey.findProgramAddressSync(
589
+ [Buffer.from('up_orderbook'), mint.toBuffer()],
590
+ this.sdk.programId
591
+ );
592
+
593
+ const [downOrderbook] = PublicKey.findProgramAddressSync(
594
+ [Buffer.from('down_orderbook'), mint.toBuffer()],
595
+ this.sdk.programId
596
+ );
597
+
598
+ // 4. 从 curve account 获取手续费接收账户 / Get fee recipient accounts from curve account (skipBalances: only need addresses)
599
+ const curveAccountInfo = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
600
+ const feeRecipientAccount = new PublicKey(curveAccountInfo.feeRecipient);
601
+ const baseFeeRecipientAccount = new PublicKey(curveAccountInfo.baseFeeRecipient);
602
+
603
+ // 5. 构建交易指令 / Build transaction instructions
604
+ const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
605
+ units: computeUnits
606
+ });
607
+
608
+ const closeLongIx = await this.sdk.program.methods
609
+ .closeLong(
610
+ sellTokenAmount, // sell_token_amount: 希望卖出的token数量 / Amount of tokens to sell
611
+ minSolOutput, // min_sol_output: 卖出后最少得到的sol数量 / Minimum SOL output
612
+ closeOrderIdBN, // close_order_id: 订单的唯一编号 / Order unique ID
613
+ closeOrderIndices // close_order_indices: 平仓时订单的位置索引数组 / Close order indices array
614
+ )
615
+ .accounts({
616
+ payer: payer,
617
+ mintAccount: mint,
618
+ curveAccount: accounts.curveAccount,
619
+ poolTokenAccount: accounts.poolTokenAccount,
620
+ poolSolAccount: accounts.poolSolAccount,
621
+ userSolAccount: userSolAccountPubkey, // 开仓用户的SOL账户 / User SOL account (must be order opener)
622
+ tokenProgram: TOKEN_PROGRAM_ID,
623
+ systemProgram: SystemProgram.programId,
624
+ rent: SYSVAR_RENT_PUBKEY,
625
+ feeRecipientAccount: feeRecipientAccount,
626
+ baseFeeRecipientAccount: baseFeeRecipientAccount,
627
+ upOrderbook: upOrderbook,
628
+ downOrderbook: downOrderbook
629
+ })
630
+ .instruction();
631
+
632
+ // 6. 创建交易并添加指令 / Create transaction and add instructions
633
+ const transaction = new Transaction();
634
+ transaction.add(modifyComputeUnits);
635
+ transaction.add(closeLongIx);
636
+
637
+ // 7. 返回交易对象和相关信息 / Return transaction object and related info
638
+ return {
639
+ transaction,
640
+ signers: [], // 平仓做多交易不需要额外的签名者,只需要 payer 签名 / Close long transaction doesn't need additional signers, only payer signature
641
+ accounts: {
642
+ mint: mint,
643
+ curveAccount: accounts.curveAccount,
644
+ poolTokenAccount: accounts.poolTokenAccount,
645
+ poolSolAccount: accounts.poolSolAccount,
646
+ payer: payer,
647
+ userSolAccount: userSolAccount,
648
+ upOrderbook: upOrderbook,
649
+ downOrderbook: downOrderbook,
650
+ feeRecipientAccount: feeRecipientAccount,
651
+ baseFeeRecipientAccount: baseFeeRecipientAccount
652
+ },
653
+ orderData: {
654
+ closeOrderId: closeOrderIdBN.toString(),
655
+ closeOrderIndices: closeOrderIndices
656
+ }
657
+ };
658
+ }
659
+
660
+
661
+ /**
662
+ * 平仓做空 / Close Short Position
663
+ * @param {Object} params - 平仓参数 / Close position parameters
664
+ * @param {string|PublicKey} params.mintAccount - 代币铸造账户地址 / Token mint account address
665
+ * @param {anchor.BN} params.buyTokenAmount - 希望买入的token数量 / Amount of tokens to buy
666
+ * @param {anchor.BN} params.maxSolAmount - 愿意给出的最大sol数量 / Maximum SOL amount to spend
667
+ * @param {number|anchor.BN} params.closeOrderId - 订单的唯一编号 / Order unique ID
668
+ * @param {Array<number>} params.closeOrderIndices - 平仓时订单的位置索引数组 / Close order position indices array
669
+ * @param {PublicKey} params.payer - 支付者公钥 / Payer public key
670
+ * @param {PublicKey} params.userSolAccount - 开仓用户的SOL账户(接收资金)/ User SOL account to receive funds (must be order opener)
671
+ * @param {Object} options - 可选参数 / Optional parameters
672
+ * @param {number} options.computeUnits - 计算单元限制,默认1400000 / Compute units limit, default 1400000
673
+ * @returns {Promise<Object>} 包含交易对象、签名者和账户信息的对象 / Object containing transaction, signers and account info
674
+ *
675
+ * @example
676
+ * const result = await sdk.trading.closeShort({
677
+ * mintAccount: "HZBos3RNhExDcAtzmdKXhTd4sVcQFBiT3FDBgmBBMk7",
678
+ * buyTokenAmount: new anchor.BN("1000000000"),
679
+ * maxSolAmount: new anchor.BN("100000000"),
680
+ * closeOrderId: 12345, // 订单唯一编号
681
+ * closeOrderIndices: [10, 11, 12], // 候选索引数组
682
+ * payer: wallet.publicKey,
683
+ * userSolAccount: orderOwnerPublicKey
684
+ * });
685
+ */
686
+ async closeShort({ mintAccount, buyTokenAmount, maxSolAmount, closeOrderId, closeOrderIndices, payer, userSolAccount }, options = {}) {
687
+ const { computeUnits = 1400000 } = options;
688
+
689
+ // 1. 参数验证和转换 / Parameter validation and conversion
690
+ const mint = typeof mintAccount === 'string' ? new PublicKey(mintAccount) : mintAccount;
691
+
692
+ if (!anchor.BN.isBN(buyTokenAmount) || !anchor.BN.isBN(maxSolAmount)) {
693
+ throw new Error('buyTokenAmount and maxSolAmount must be anchor.BN type');
694
+ }
695
+
696
+ // 转换 closeOrderId 为 anchor.BN (u64)
697
+ const closeOrderIdBN = anchor.BN.isBN(closeOrderId) ? closeOrderId : new anchor.BN(closeOrderId);
698
+
699
+ // 验证 closeOrderIndices 参数
700
+ if (!Array.isArray(closeOrderIndices) || closeOrderIndices.length === 0) {
701
+ throw new Error('closeOrderIndices must be a non-empty array');
702
+ }
703
+
704
+ if (closeOrderIndices.length > MAX_CANDIDATE_INDICES) {
705
+ throw new Error(`closeOrderIndices array cannot exceed ${MAX_CANDIDATE_INDICES} elements`);
706
+ }
707
+
708
+ // 验证和转换 userSolAccount - 支持字符串或 PublicKey 对象
709
+ let userSolAccountPubkey;
710
+ if (!userSolAccount) {
711
+ throw new Error('userSolAccount is required');
712
+ }
713
+
714
+ if (typeof userSolAccount === 'string') {
715
+ try {
716
+ userSolAccountPubkey = new PublicKey(userSolAccount);
717
+ } catch (error) {
718
+ throw new Error('userSolAccount must be a valid PublicKey string');
719
+ }
720
+ } else if (userSolAccount instanceof PublicKey) {
721
+ userSolAccountPubkey = userSolAccount;
722
+ } else if (userSolAccount.constructor && userSolAccount.constructor.name === 'PublicKey') {
723
+ // 处理跨包 PublicKey 实例 - 提取字符串后重新创建
724
+ try {
725
+ userSolAccountPubkey = new PublicKey(userSolAccount.toString());
726
+ } catch (error) {
727
+ throw new Error('userSolAccount must be a valid PublicKey');
728
+ }
729
+ } else {
730
+ throw new Error('userSolAccount must be a valid PublicKey or PublicKey string');
731
+ }
732
+
733
+ // 2. 计算 PDA 账户 / Calculate PDA accounts
734
+ const accounts = this._calculatePDAAccounts(mint);
735
+
736
+ // 3. 计算 OrderBook PDA 地址 / Calculate OrderBook PDA addresses
737
+ const [upOrderbook] = PublicKey.findProgramAddressSync(
738
+ [Buffer.from('up_orderbook'), mint.toBuffer()],
739
+ this.sdk.programId
740
+ );
741
+
742
+ const [downOrderbook] = PublicKey.findProgramAddressSync(
743
+ [Buffer.from('down_orderbook'), mint.toBuffer()],
744
+ this.sdk.programId
745
+ );
746
+
747
+ // 4. 从 curve account 获取手续费接收账户 / Get fee recipient accounts from curve account (skipBalances: only need addresses)
748
+ const curveAccountInfo = await this.sdk.chain.getCurveAccount(mint, { skipBalances: true });
749
+ const feeRecipientAccount = new PublicKey(curveAccountInfo.feeRecipient);
750
+ const baseFeeRecipientAccount = new PublicKey(curveAccountInfo.baseFeeRecipient);
751
+
752
+ // 5. 构建交易指令 / Build transaction instructions
753
+ const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({
754
+ units: computeUnits
755
+ });
756
+
757
+ const closeShortIx = await this.sdk.program.methods
758
+ .closeShort(
759
+ buyTokenAmount, // buy_token_amount: 希望买入的token数量 / Amount of tokens to buy
760
+ maxSolAmount, // max_sol_amount: 愿意给出的最大sol数量 / Maximum SOL amount to spend
761
+ closeOrderIdBN, // close_order_id: 订单的唯一编号 / Order unique ID
762
+ closeOrderIndices // close_order_indices: 平仓时订单的位置索引数组 / Close order indices array
763
+ )
764
+ .accounts({
765
+ payer: payer,
766
+ mintAccount: mint,
767
+ curveAccount: accounts.curveAccount,
768
+ poolTokenAccount: accounts.poolTokenAccount,
769
+ poolSolAccount: accounts.poolSolAccount,
770
+ userSolAccount: userSolAccountPubkey, // 开仓用户的SOL账户 / User SOL account (must be order opener)
771
+ tokenProgram: TOKEN_PROGRAM_ID,
772
+ systemProgram: SystemProgram.programId,
773
+ rent: SYSVAR_RENT_PUBKEY,
774
+ feeRecipientAccount: feeRecipientAccount,
775
+ baseFeeRecipientAccount: baseFeeRecipientAccount,
776
+ upOrderbook: upOrderbook,
777
+ downOrderbook: downOrderbook
778
+ })
779
+ .instruction();
780
+
781
+ // 6. 创建交易并添加指令 / Create transaction and add instructions
782
+ const transaction = new Transaction();
783
+ transaction.add(modifyComputeUnits);
784
+ transaction.add(closeShortIx);
785
+
786
+ // 7. 返回交易对象和相关信息 / Return transaction object and related info
787
+ return {
788
+ transaction,
789
+ signers: [], // 平仓做空交易不需要额外的签名者,只需要 payer 签名 / Close short transaction doesn't need additional signers, only payer signature
790
+ accounts: {
791
+ mint: mint,
792
+ curveAccount: accounts.curveAccount,
793
+ poolTokenAccount: accounts.poolTokenAccount,
794
+ poolSolAccount: accounts.poolSolAccount,
795
+ payer: payer,
796
+ userSolAccount: userSolAccount,
797
+ upOrderbook: upOrderbook,
798
+ downOrderbook: downOrderbook,
799
+ feeRecipientAccount: feeRecipientAccount,
800
+ baseFeeRecipientAccount: baseFeeRecipientAccount
801
+ },
802
+ orderData: {
803
+ closeOrderId: closeOrderIdBN.toString(),
804
+ closeOrderIndices: closeOrderIndices
805
+ }
806
+ };
807
+ }
808
+
809
+ // ========== PDA Calculation Methods ==========
810
+
811
+ /**
812
+ * 计算 PDA 账户
813
+ * @private
814
+ * @param {PublicKey} mintAccount - 代币铸造账户
815
+ * @returns {Object} PDA 账户对象
816
+ */
817
+ _calculatePDAAccounts(mintAccount) {
818
+ // 计算曲线账户 PDA
819
+ const [curveAccount] = PublicKey.findProgramAddressSync(
820
+ [Buffer.from('borrowing_curve'), mintAccount.toBuffer()],
821
+ this.sdk.programId
822
+ );
823
+
824
+ // 计算池子代币账户 PDA
825
+ const [poolTokenAccount] = PublicKey.findProgramAddressSync(
826
+ [Buffer.from('pool_token'), mintAccount.toBuffer()],
827
+ this.sdk.programId
828
+ );
829
+
830
+ // 计算池子 SOL 账户 PDA
831
+ const [poolSolAccount] = PublicKey.findProgramAddressSync(
832
+ [Buffer.from('pool_sol'), mintAccount.toBuffer()],
833
+ this.sdk.programId
834
+ );
835
+
836
+ return {
837
+ curveAccount,
838
+ poolTokenAccount,
839
+ poolSolAccount
840
+ };
841
+ }
842
+
843
+
844
+
845
+
846
+ }
847
+
848
+ module.exports = TradingModule;