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.
- package/README.md +364 -0
- package/dist/100x-sdk.cjs.js +57601 -0
- package/dist/100x-sdk.cjs.js.map +1 -0
- package/dist/100x-sdk.esm.js +46234 -0
- package/dist/100x-sdk.esm.js.map +1 -0
- package/dist/100x-sdk.js +46245 -0
- package/dist/100x-sdk.js.map +1 -0
- package/dist/index.d.ts +386 -0
- package/package.json +69 -0
- package/src/idl/fun100x_localnet.json +3735 -0
- package/src/idl/fun100x_main.json +3735 -0
- package/src/index.js +50 -0
- package/src/modules/chain.js +1310 -0
- package/src/modules/fast.js +734 -0
- package/src/modules/param.js +171 -0
- package/src/modules/simulator/buy_sell_token.js +326 -0
- package/src/modules/simulator/calcLiq.js +778 -0
- package/src/modules/simulator/calc_sol_liq.js +268 -0
- package/src/modules/simulator/close_indices.js +217 -0
- package/src/modules/simulator/long_shrot_stop.js +1028 -0
- package/src/modules/simulator/stop_loss_utils.js +378 -0
- package/src/modules/simulator/utils.js +63 -0
- package/src/modules/simulator.js +386 -0
- package/src/modules/token.js +455 -0
- package/src/modules/tools.js +370 -0
- package/src/modules/trading.js +848 -0
- package/src/sdk.js +206 -0
- package/src/types/index.d.ts +386 -0
- package/src/utils/constants.js +57 -0
- package/src/utils/curve_amm.js +1215 -0
- package/src/utils/orderUtils.js +17 -0
|
@@ -0,0 +1,1310 @@
|
|
|
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
|
+
// getCurveAccount 缓存:key = mint string, value = { data, timestamp }
|
|
18
|
+
this._curveAccountCache = new Map();
|
|
19
|
+
this._CACHE_TTL = 10000; // 10 秒 TTL
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 清除指定 mint 的 curveAccount 缓存(交易后调用)
|
|
24
|
+
* @param {string} [mint] - 指定 mint 地址,不传则清除全部
|
|
25
|
+
*/
|
|
26
|
+
invalidateCurveCache(mint) {
|
|
27
|
+
if (mint) {
|
|
28
|
+
this._curveAccountCache.delete(typeof mint === 'string' ? mint : mint.toString());
|
|
29
|
+
} else {
|
|
30
|
+
this._curveAccountCache.clear();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Get complete curve_account (BorrowingBondingCurve) data
|
|
36
|
+
*
|
|
37
|
+
* Read the borrowing liquidity pool account data for a specified token from the blockchain,
|
|
38
|
+
* including addresses and balance information for all related accounts.
|
|
39
|
+
* This function automatically calculates related PDA addresses and concurrently queries all balances,
|
|
40
|
+
* providing complete liquidity pool status.
|
|
41
|
+
*
|
|
42
|
+
* @param {string|PublicKey} mint - Token mint account address
|
|
43
|
+
*
|
|
44
|
+
* @returns {Promise<Object>} Complete BorrowingBondingCurve account data object
|
|
45
|
+
*
|
|
46
|
+
* @returns {Promise<Object>} Return object contains following complete fields:
|
|
47
|
+
*
|
|
48
|
+
* **Core Reserve Data:**
|
|
49
|
+
* @returns {bigint} returns.lpTokenReserve - LP Token reserves, total reserves of liquidity provider tokens
|
|
50
|
+
* @returns {bigint} returns.lpSolReserve - LP SOL reserves, SOL reserves in the liquidity pool
|
|
51
|
+
* @returns {bigint} returns.price - Current token price, calculated based on AMM algorithm
|
|
52
|
+
* @returns {bigint} returns.borrowTokenReserve - Borrowable Token reserves, borrowable token reserves
|
|
53
|
+
* @returns {bigint} returns.borrowSolReserve - Borrowable SOL reserves, borrowable SOL reserves
|
|
54
|
+
*
|
|
55
|
+
* **Fee and Parameter Configuration:**
|
|
56
|
+
* @returns {number} returns.swapFee - Swap fee rate, expressed in basis points (e.g. 100 = 1%)
|
|
57
|
+
* @returns {number} returns.borrowFee - Borrow fee rate, expressed in basis points
|
|
58
|
+
* @returns {number} returns.feeDiscountFlag - Fee discount flag (0: normal, 1: 50% off, 2: 25% off, 3: 12.5% off)
|
|
59
|
+
* @returns {number} returns.feeSplit - Fee split ratio, determines how fees are distributed among different recipients
|
|
60
|
+
* @returns {number} returns.borrowDuration - Borrow duration, in seconds
|
|
61
|
+
* @returns {number} returns.bump - curve_account PDA bump seed
|
|
62
|
+
*
|
|
63
|
+
* **Account Addresses:**
|
|
64
|
+
* @returns {string} returns.baseFeeRecipient - Base fee recipient address, receives base transaction fees
|
|
65
|
+
* @returns {string} returns.feeRecipient - Fee recipient address, receives additional fee income
|
|
66
|
+
* @returns {string} returns.mint - Token mint account address
|
|
67
|
+
* @returns {string} returns.upOrderbook - Up orderbook (short orders) PDA address
|
|
68
|
+
* @returns {string} returns.downOrderbook - Down orderbook (long orders) PDA address
|
|
69
|
+
* @returns {string} returns.creator - Token creator address, the wallet that created this token
|
|
70
|
+
* @returns {string} returns.poolTokenAccount - Pool token account address, stores tokens in the liquidity pool
|
|
71
|
+
* @returns {string} returns.poolSolAccount - Pool SOL account address, stores native SOL in the liquidity pool
|
|
72
|
+
*
|
|
73
|
+
* **Token Supply Information:**
|
|
74
|
+
* @returns {bigint} returns.totalSupply - Total token supply (smallest unit)
|
|
75
|
+
* @returns {number} returns.decimals - Token decimal places
|
|
76
|
+
* @returns {bigint} returns.initialBorrowPool - Initial borrow pool size = totalSupply - initialVirtualToken
|
|
77
|
+
* @returns {bigint} returns.circulatingSupply - Circulating supply = totalSupply - borrowTokenReserve
|
|
78
|
+
*
|
|
79
|
+
* **Balance Information:**
|
|
80
|
+
* @returns {number} returns.baseFeeRecipientBalance - SOL balance of base fee recipient address (lamports)
|
|
81
|
+
* @returns {number} returns.feeRecipientBalance - SOL balance of fee recipient address (lamports)
|
|
82
|
+
* @returns {bigint} returns.poolTokenBalance - Token balance of pool token account
|
|
83
|
+
* @returns {number} returns.poolSolBalance - SOL balance of pool SOL account (lamports)
|
|
84
|
+
*
|
|
85
|
+
* **Metadata:**
|
|
86
|
+
* @returns {Object} returns._metadata - Additional metadata information
|
|
87
|
+
* @returns {string} returns._metadata.accountAddress - Complete address of curve_account
|
|
88
|
+
* @returns {string} returns._metadata.mintAddress - Input token mint address
|
|
89
|
+
*
|
|
90
|
+
* @throws {Error} Throws error when curve_account does not exist
|
|
91
|
+
* @throws {Error} Throws error when unable to decode account data
|
|
92
|
+
* @throws {Error} Throws error when network connection fails
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* // Basic usage example
|
|
96
|
+
* try {
|
|
97
|
+
* const curveData = await sdk.chain.getCurveAccount('3YggGtxXEGBbjK1WLj2Z79doZC2gkCWXag1ag8BD4cYY');
|
|
98
|
+
*
|
|
99
|
+
* // Display core reserve information
|
|
100
|
+
* console.log('=== Core Reserve Data ===');
|
|
101
|
+
* console.log('LP Token reserves:', curveData.lpTokenReserve.toString());
|
|
102
|
+
* console.log('LP SOL reserves:', curveData.lpSolReserve.toString());
|
|
103
|
+
* console.log('Current price:', curveData.price.toString());
|
|
104
|
+
* console.log('Borrow Token reserves:', curveData.borrowTokenReserve.toString());
|
|
105
|
+
* console.log('Borrow SOL reserves:', curveData.borrowSolReserve.toString());
|
|
106
|
+
*
|
|
107
|
+
* // Display fee configuration
|
|
108
|
+
* console.log('=== Fee Configuration ===');
|
|
109
|
+
* console.log('Swap fee rate:', curveData.swapFee / 100, '%');
|
|
110
|
+
* console.log('Borrow fee rate:', curveData.borrowFee / 100, '%');
|
|
111
|
+
* console.log('Fee discount flag:', curveData.feeDiscountFlag);
|
|
112
|
+
* console.log('Borrow duration:', curveData.borrowDuration, 'seconds');
|
|
113
|
+
*
|
|
114
|
+
* // Display account addresses
|
|
115
|
+
* console.log('=== Account Addresses ===');
|
|
116
|
+
* console.log('Token creator:', curveData.creator);
|
|
117
|
+
* console.log('Base fee recipient address:', curveData.baseFeeRecipient);
|
|
118
|
+
* console.log('Fee recipient address:', curveData.feeRecipient);
|
|
119
|
+
* console.log('Pool token account:', curveData.poolTokenAccount);
|
|
120
|
+
* console.log('Pool SOL account:', curveData.poolSolAccount);
|
|
121
|
+
*
|
|
122
|
+
* // Display balance information
|
|
123
|
+
* console.log('=== Balance Information ===');
|
|
124
|
+
* console.log('Base fee recipient balance:', curveData.baseFeeRecipientBalance / 1e9, 'SOL');
|
|
125
|
+
* console.log('Fee recipient balance:', curveData.feeRecipientBalance / 1e9, 'SOL');
|
|
126
|
+
* console.log('Pool token balance:', curveData.poolTokenBalance.toString());
|
|
127
|
+
* console.log('Pool SOL balance:', curveData.poolSolBalance / 1e9, 'SOL');
|
|
128
|
+
*
|
|
129
|
+
* // Display orderbook addresses
|
|
130
|
+
* console.log('=== Order Books ===');
|
|
131
|
+
* console.log('Up orderbook (short):', curveData.upOrderbook);
|
|
132
|
+
* console.log('Down orderbook (long):', curveData.downOrderbook);
|
|
133
|
+
*
|
|
134
|
+
* } catch (error) {
|
|
135
|
+
* console.error('Failed to get curve account:', error.message);
|
|
136
|
+
* }
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* // Pool monitoring example
|
|
140
|
+
* async function monitorPool(mintAddress) {
|
|
141
|
+
* const data = await sdk.chain.getCurveAccount(mintAddress);
|
|
142
|
+
*
|
|
143
|
+
* // Calculate pool utilization
|
|
144
|
+
* const tokenUtilization = Number(data.lpTokenReserve - data.poolTokenBalance) / Number(data.lpTokenReserve);
|
|
145
|
+
* const solUtilization = Number(data.lpSolReserve - BigInt(data.poolSolBalance)) / Number(data.lpSolReserve);
|
|
146
|
+
*
|
|
147
|
+
* console.log('Token utilization:', (tokenUtilization * 100).toFixed(2), '%');
|
|
148
|
+
* console.log('SOL utilization:', (solUtilization * 100).toFixed(2), '%');
|
|
149
|
+
*
|
|
150
|
+
* // Check fee earnings
|
|
151
|
+
* const totalFeeBalance = data.baseFeeRecipientBalance + data.feeRecipientBalance;
|
|
152
|
+
* console.log('Total fee earnings:', totalFeeBalance / 1e9, 'SOL');
|
|
153
|
+
*
|
|
154
|
+
* return {
|
|
155
|
+
* tokenUtilization,
|
|
156
|
+
* solUtilization,
|
|
157
|
+
* totalFeeBalance,
|
|
158
|
+
* currentPrice: data.price
|
|
159
|
+
* };
|
|
160
|
+
* }
|
|
161
|
+
*
|
|
162
|
+
* @since 1.0.0
|
|
163
|
+
* @version 2.0.0 - Updated to use new OrderBook structure (up_orderbook/down_orderbook instead of upHead/downHead)
|
|
164
|
+
* @author 100x SDK Team
|
|
165
|
+
*/
|
|
166
|
+
async getCurveAccount(mint, options = {}) {
|
|
167
|
+
const { skipBalances = false } = options;
|
|
168
|
+
try {
|
|
169
|
+
// Parameter validation and conversion
|
|
170
|
+
const mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
|
|
171
|
+
const mintKey = mintPubkey.toString();
|
|
172
|
+
|
|
173
|
+
// 检查缓存
|
|
174
|
+
const cached = this._curveAccountCache.get(mintKey);
|
|
175
|
+
if (cached && (Date.now() - cached.timestamp < this._CACHE_TTL)) {
|
|
176
|
+
// 缓存命中:如果请求包含余额但缓存没有,需要重新获取余额部分
|
|
177
|
+
if (!skipBalances && cached.skipBalances) {
|
|
178
|
+
// 缓存是 skipBalances 的,但现在需要余额,需要补充查询
|
|
179
|
+
} else {
|
|
180
|
+
return cached.data;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Calculate curve_account PDA address
|
|
185
|
+
// Use the same seeds as in the contract: [b"borrowing_curve", mint_account.key().as_ref()]
|
|
186
|
+
const [curveAccountPDA] = PublicKey.findProgramAddressSync(
|
|
187
|
+
[
|
|
188
|
+
Buffer.from("borrowing_curve"),
|
|
189
|
+
mintPubkey.toBuffer()
|
|
190
|
+
],
|
|
191
|
+
this.sdk.programId
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// Use Anchor program to fetch account data directly
|
|
195
|
+
// Method 1: Use program's fetch method
|
|
196
|
+
let decodedData;
|
|
197
|
+
try {
|
|
198
|
+
decodedData = await this.sdk.program.account.borrowingBondingCurve.fetch(curveAccountPDA);
|
|
199
|
+
} catch (fetchError) {
|
|
200
|
+
// Method 2: If fetch fails, use raw method
|
|
201
|
+
const accountInfo = await this.sdk.connection.getAccountInfo(curveAccountPDA);
|
|
202
|
+
if (!accountInfo) {
|
|
203
|
+
throw new Error(`curve_account does not exist`);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Manually decode with BorshAccountsCoder
|
|
207
|
+
const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
|
|
208
|
+
|
|
209
|
+
// Try different account names
|
|
210
|
+
try {
|
|
211
|
+
decodedData = accountsCoder.decode('BorrowingBondingCurve', accountInfo.data);
|
|
212
|
+
} catch (decodeError1) {
|
|
213
|
+
try {
|
|
214
|
+
// Try lowercase name
|
|
215
|
+
decodedData = accountsCoder.decode('borrowingBondingCurve', accountInfo.data);
|
|
216
|
+
} catch (decodeError2) {
|
|
217
|
+
// Both failed, throw original error
|
|
218
|
+
throw new Error(`Cannot decode account data: ${decodeError1.message}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Calculate pool account PDA addresses
|
|
224
|
+
const [poolTokenAccountPDA] = PublicKey.findProgramAddressSync(
|
|
225
|
+
[
|
|
226
|
+
Buffer.from("pool_token"),
|
|
227
|
+
mintPubkey.toBuffer()
|
|
228
|
+
],
|
|
229
|
+
this.sdk.programId
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const [poolSolAccountPDA] = PublicKey.findProgramAddressSync(
|
|
233
|
+
[
|
|
234
|
+
Buffer.from("pool_sol"),
|
|
235
|
+
mintPubkey.toBuffer()
|
|
236
|
+
],
|
|
237
|
+
this.sdk.programId
|
|
238
|
+
);
|
|
239
|
+
|
|
240
|
+
// Query balances (skip if not needed)
|
|
241
|
+
let baseFeeRecipientBalance = 0;
|
|
242
|
+
let feeRecipientBalance = 0;
|
|
243
|
+
let poolTokenBalance = { value: { amount: '0' } };
|
|
244
|
+
let poolSolBalance = 0;
|
|
245
|
+
let tokenSupply = { value: { amount: '0', decimals: 0 } };
|
|
246
|
+
|
|
247
|
+
if (!skipBalances) {
|
|
248
|
+
[
|
|
249
|
+
baseFeeRecipientBalance,
|
|
250
|
+
feeRecipientBalance,
|
|
251
|
+
poolTokenBalance,
|
|
252
|
+
poolSolBalance,
|
|
253
|
+
tokenSupply
|
|
254
|
+
] = await Promise.all([
|
|
255
|
+
this.sdk.connection.getBalance(decodedData.baseFeeRecipient),
|
|
256
|
+
this.sdk.connection.getBalance(decodedData.feeRecipient),
|
|
257
|
+
this.sdk.connection.getTokenAccountBalance(poolTokenAccountPDA).catch(() => ({ value: { amount: '0' } })),
|
|
258
|
+
this.sdk.connection.getBalance(poolSolAccountPDA),
|
|
259
|
+
this.sdk.connection.getTokenSupply(mintPubkey)
|
|
260
|
+
]);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// Convert data format
|
|
264
|
+
const convertedData = {
|
|
265
|
+
// BN types convert to bigint
|
|
266
|
+
lpTokenReserve: BigInt(decodedData.lpTokenReserve.toString()),
|
|
267
|
+
lpSolReserve: BigInt(decodedData.lpSolReserve.toString()),
|
|
268
|
+
price: BigInt(decodedData.price.toString()),
|
|
269
|
+
borrowTokenReserve: BigInt(decodedData.borrowTokenReserve.toString()),
|
|
270
|
+
borrowSolReserve: BigInt(decodedData.borrowSolReserve.toString()),
|
|
271
|
+
|
|
272
|
+
// Numeric types remain unchanged
|
|
273
|
+
swapFee: decodedData.swapFee,
|
|
274
|
+
borrowFee: decodedData.borrowFee,
|
|
275
|
+
feeDiscountFlag: decodedData.feeDiscountFlag,
|
|
276
|
+
feeSplit: decodedData.feeSplit,
|
|
277
|
+
borrowDuration: decodedData.borrowDuration,
|
|
278
|
+
bump: decodedData.bump,
|
|
279
|
+
|
|
280
|
+
// PublicKey types convert to string
|
|
281
|
+
baseFeeRecipient: decodedData.baseFeeRecipient.toString(),
|
|
282
|
+
feeRecipient: decodedData.feeRecipient.toString(),
|
|
283
|
+
mint: decodedData.mint.toString(),
|
|
284
|
+
|
|
285
|
+
// New OrderBook structure - always has value (not optional)
|
|
286
|
+
upOrderbook: decodedData.upOrderbook.toString(),
|
|
287
|
+
downOrderbook: decodedData.downOrderbook.toString(),
|
|
288
|
+
|
|
289
|
+
// Creator address
|
|
290
|
+
creator: decodedData.creator.toString(),
|
|
291
|
+
|
|
292
|
+
// === 第一阶段新增字段:动态流动池参数 ===
|
|
293
|
+
initialVirtualSol: BigInt(decodedData.initialVirtualSol.toString()), // u64, lamports
|
|
294
|
+
initialVirtualToken: BigInt(decodedData.initialVirtualToken.toString()), // u64, 最小单位
|
|
295
|
+
|
|
296
|
+
// === 第二阶段新增字段:高级版池子参数 ===
|
|
297
|
+
poolType: decodedData.poolType, // u8, 0=普通版, 1=高级版
|
|
298
|
+
borrowPoolRatio: decodedData.borrowPoolRatio, // u8, 5-30表示5%-30%
|
|
299
|
+
|
|
300
|
+
// Token Supply 信息
|
|
301
|
+
totalSupply: BigInt(tokenSupply.value.amount),
|
|
302
|
+
decimals: tokenSupply.value.decimals,
|
|
303
|
+
initialBorrowPool: BigInt(tokenSupply.value.amount) - BigInt(decodedData.initialVirtualToken.toString()),
|
|
304
|
+
circulatingSupply: BigInt(decodedData.initialVirtualToken.toString())
|
|
305
|
+
+ (BigInt(tokenSupply.value.amount) - BigInt(decodedData.initialVirtualToken.toString()) - BigInt(decodedData.borrowTokenReserve.toString())),
|
|
306
|
+
|
|
307
|
+
// SOL balance information
|
|
308
|
+
baseFeeRecipientBalance: baseFeeRecipientBalance, // Unit: lamports
|
|
309
|
+
feeRecipientBalance: feeRecipientBalance, // Unit: lamports
|
|
310
|
+
|
|
311
|
+
// Pool account information
|
|
312
|
+
poolTokenAccount: poolTokenAccountPDA.toString(), // Pool token account address
|
|
313
|
+
poolSolAccount: poolSolAccountPDA.toString(), // Pool SOL account address
|
|
314
|
+
poolTokenBalance: BigInt(poolTokenBalance.value.amount), // Pool token balance
|
|
315
|
+
poolSolBalance: poolSolBalance, // Pool SOL balance (lamports)
|
|
316
|
+
|
|
317
|
+
// Additional metadata
|
|
318
|
+
_metadata: {
|
|
319
|
+
accountAddress: curveAccountPDA.toString(),
|
|
320
|
+
mintAddress: mintPubkey.toString()
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
// 写入缓存
|
|
325
|
+
this._curveAccountCache.set(mintKey, {
|
|
326
|
+
data: convertedData,
|
|
327
|
+
timestamp: Date.now(),
|
|
328
|
+
skipBalances: skipBalances
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
// Return converted data
|
|
332
|
+
return convertedData;
|
|
333
|
+
|
|
334
|
+
} catch (error) {
|
|
335
|
+
// Provide concise error information
|
|
336
|
+
if (error.message.includes('Account does not exist')) {
|
|
337
|
+
throw new Error(`curve_account does not exist for mint: ${mint}`);
|
|
338
|
+
} else {
|
|
339
|
+
throw new Error(`Failed to get curve_account: ${error.message}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Calculate curve_account PDA address
|
|
348
|
+
*
|
|
349
|
+
* @param {string|PublicKey} mint - Token mint address
|
|
350
|
+
* @returns {PublicKey} curve_account PDA address
|
|
351
|
+
*
|
|
352
|
+
* @example
|
|
353
|
+
* const curveAddress = sdk.chain.getCurveAccountAddress('3YggGtxXEGBbjK1WLj2Z79doZC2gkCWXag1ag8BD4cYY');
|
|
354
|
+
* console.log('Curve Account Address:', curveAddress.toString());
|
|
355
|
+
*/
|
|
356
|
+
getCurveAccountAddress(mint) {
|
|
357
|
+
const mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
|
|
358
|
+
|
|
359
|
+
const [curveAccountPDA] = PublicKey.findProgramAddressSync(
|
|
360
|
+
[
|
|
361
|
+
Buffer.from("borrowing_curve"),
|
|
362
|
+
mintPubkey.toBuffer()
|
|
363
|
+
],
|
|
364
|
+
this.sdk.programId
|
|
365
|
+
);
|
|
366
|
+
|
|
367
|
+
return curveAccountPDA;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Get price data (read price from chain curveAccountPDA)
|
|
372
|
+
* @param {string} mint - Token address
|
|
373
|
+
* @returns {Promise<string>} Latest price string
|
|
374
|
+
*
|
|
375
|
+
* @example
|
|
376
|
+
* // Get latest token price
|
|
377
|
+
* const price = await sdk.chain.price('56hfrQYiyRSUZdRKDuUvsqRik8j2UDW9kCisy7BiRxmg');
|
|
378
|
+
* console.log('Latest price:', price); // "13514066072452801812769"
|
|
379
|
+
*/
|
|
380
|
+
async price(mint) {
|
|
381
|
+
// Validate input
|
|
382
|
+
if (!mint || typeof mint !== 'string') {
|
|
383
|
+
throw new Error('price: mint address must be a valid string');
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
try {
|
|
387
|
+
// 复用 getCurveAccount 缓存,避免重复 fetch 同一个账户
|
|
388
|
+
const curveData = await this.getCurveAccount(mint, { skipBalances: true });
|
|
389
|
+
const price = curveData.price;
|
|
390
|
+
|
|
391
|
+
if (price && price !== 0n) {
|
|
392
|
+
return price.toString();
|
|
393
|
+
} else {
|
|
394
|
+
const initialPrice = CurveAMM.getInitialPrice();
|
|
395
|
+
if (initialPrice === null) {
|
|
396
|
+
throw new Error('price: Unable to calculate initial price');
|
|
397
|
+
}
|
|
398
|
+
return initialPrice.toString();
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
} catch (error) {
|
|
402
|
+
// If getting fails, return initial price
|
|
403
|
+
console.warn(`price: Failed to get chain price, using initial price: ${error.message}`);
|
|
404
|
+
|
|
405
|
+
const initialPrice = CurveAMM.getInitialPrice();
|
|
406
|
+
if (initialPrice === null) {
|
|
407
|
+
throw new Error('price: Unable to calculate initial price');
|
|
408
|
+
}
|
|
409
|
+
return initialPrice.toString();
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Get Orders Data (Read from Chain using new OrderBook structure)
|
|
415
|
+
* Returns ALL orders regardless of pagination parameters (for compatibility)
|
|
416
|
+
* @param {string} mint - Token mint address
|
|
417
|
+
* @param {Object} options - Query parameters (page and limit are ignored but kept for compatibility)
|
|
418
|
+
* @param {string} options.type - Order type: "up_orders" (short) or "down_orders" (long)
|
|
419
|
+
* @param {number} options.page - Page number (ignored, always returns all data)
|
|
420
|
+
* @param {number} options.limit - Items per page (ignored, always returns all data)
|
|
421
|
+
* @returns {Promise<Object>} Order data with ALL orders
|
|
422
|
+
*
|
|
423
|
+
* @example
|
|
424
|
+
* // Get long orders (returns ALL orders)
|
|
425
|
+
* const ordersData = await sdk.chain.orders('6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee', { type: 'down_orders' });
|
|
426
|
+
*
|
|
427
|
+
* // Return value example:
|
|
428
|
+
* // {
|
|
429
|
+
* // "success": true,
|
|
430
|
+
* // "data": {
|
|
431
|
+
* // "orders": [
|
|
432
|
+
* // {
|
|
433
|
+
* // "order_type": "down_orders", // Order type string (converted)
|
|
434
|
+
* // "mint": "6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee", // Token address
|
|
435
|
+
* // "user": "JD1eNPaJpbtejKfgimbLYLkvpsTHyYzKCCozVLGLS6zu", // User address
|
|
436
|
+
* // "lock_lp_start_price": "46618228118401293964111", // LP start price (string)
|
|
437
|
+
* // "lock_lp_end_price": "45827474968448818396222", // LP end price (string)
|
|
438
|
+
* // "open_price": "46222851543425056180166", // Open price (string) - NEW
|
|
439
|
+
* // "order_id": "12345", // Order ID (u64 as string) - NEW
|
|
440
|
+
* // "lock_lp_sol_amount": 3299491609, // LP locked SOL amount (lamports)
|
|
441
|
+
* // "lock_lp_token_amount": 713848715669, // LP locked token amount (min unit)
|
|
442
|
+
* // "next_lp_sol_amount": 3299491609, // Next LP SOL amount (lamports) - NEW
|
|
443
|
+
* // "next_lp_token_amount": 713848715669, // Next LP token amount (min unit) - NEW
|
|
444
|
+
* // "start_time": 1756352482, // Start time (Unix timestamp, i64 as number)
|
|
445
|
+
* // "end_time": 1756525282, // End time (Unix timestamp, i64 as number)
|
|
446
|
+
* // "margin_init_sol_amount": 571062973, // Initial margin SOL amount (lamports) - NEW
|
|
447
|
+
* // "margin_sol_amount": 571062973, // Margin SOL amount (lamports)
|
|
448
|
+
* // "borrow_amount": 3860656108, // Borrow amount (lamports)
|
|
449
|
+
* // "position_asset_amount": 713848715669, // Position asset amount (min unit)
|
|
450
|
+
* // "realized_sol_amount": 0, // Realized SOL amount (lamports) - NEW
|
|
451
|
+
* // "borrow_fee": 300, // Borrow fee (basis points, 300 = 3%)
|
|
452
|
+
* // "index": 0, // Order index in OrderBook (currentIndex)
|
|
453
|
+
* // "next_order": 1, // Next order index in linked list (u16, 65535=none) - NEW
|
|
454
|
+
* // "prev_order": 65535 // Previous order index in linked list (u16, 65535=none) - NEW
|
|
455
|
+
* // }
|
|
456
|
+
* // ],
|
|
457
|
+
* // "total": 12, // Total order count
|
|
458
|
+
* // "order_type": "down_orders", // Order type (string)
|
|
459
|
+
* // "mint_account": "6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee", // Queried token address
|
|
460
|
+
* // "page": 1, // Always 1 (for compatibility)
|
|
461
|
+
* // "limit": <total>, // Always equals total (for compatibility)
|
|
462
|
+
* // "has_next": false, // Always false (all data returned)
|
|
463
|
+
* // "has_prev": false // Always false (all data returned)
|
|
464
|
+
* // },
|
|
465
|
+
* // "message": "Operation successful" // Operation result message
|
|
466
|
+
* // }
|
|
467
|
+
*
|
|
468
|
+
* // Use utility methods to process data:
|
|
469
|
+
*/
|
|
470
|
+
async orders(mint, options = {}) {
|
|
471
|
+
try {
|
|
472
|
+
// Parameter validation
|
|
473
|
+
if (!mint || typeof mint !== 'string') {
|
|
474
|
+
throw new Error('orders: mint address must be a valid string');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Set default parameters (kept for compatibility but not used for pagination)
|
|
478
|
+
const orderType = options.type || 'down_orders';
|
|
479
|
+
const page = 1; // Always return page 1
|
|
480
|
+
|
|
481
|
+
// Validate order type
|
|
482
|
+
if (!['up_orders', 'down_orders'].includes(orderType)) {
|
|
483
|
+
throw new Error('orders: order type must be "up_orders" or "down_orders"');
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Convert API type to orderbook direction
|
|
487
|
+
// "up_orders" = short orders = upOrderbook (orderType=2)
|
|
488
|
+
// "down_orders" = long orders = downOrderbook (orderType=1)
|
|
489
|
+
const seed = orderType === 'up_orders' ? 'up_orderbook' : 'down_orderbook';
|
|
490
|
+
|
|
491
|
+
// 本地计算 orderbook PDA,避免调用 getCurveAccount(省 5 个 RPC)
|
|
492
|
+
const mintPubkey = new PublicKey(mint);
|
|
493
|
+
const [orderbookPubkey] = PublicKey.findProgramAddressSync(
|
|
494
|
+
[Buffer.from(seed), mintPubkey.toBuffer()],
|
|
495
|
+
this.sdk.programId
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
// Get OrderBook account data
|
|
499
|
+
const accountInfo = await this.sdk.connection.getAccountInfo(orderbookPubkey);
|
|
500
|
+
|
|
501
|
+
if (!accountInfo) {
|
|
502
|
+
// OrderBook account doesn't exist, return empty result
|
|
503
|
+
return {
|
|
504
|
+
success: true,
|
|
505
|
+
data: {
|
|
506
|
+
orders: [],
|
|
507
|
+
total: 0,
|
|
508
|
+
order_type: orderType,
|
|
509
|
+
mint_account: mint,
|
|
510
|
+
page: page,
|
|
511
|
+
limit: 0,
|
|
512
|
+
has_next: false,
|
|
513
|
+
has_prev: false
|
|
514
|
+
},
|
|
515
|
+
message: "Operation successful"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
const data = accountInfo.data;
|
|
520
|
+
|
|
521
|
+
// Parse OrderBook Header
|
|
522
|
+
const header = this._parseOrderBookHeader(data);
|
|
523
|
+
|
|
524
|
+
// If no orders in orderbook, return empty result
|
|
525
|
+
if (header.total === 0 || header.head === 65535) {
|
|
526
|
+
return {
|
|
527
|
+
success: true,
|
|
528
|
+
data: {
|
|
529
|
+
orders: [],
|
|
530
|
+
total: 0,
|
|
531
|
+
order_type: orderType,
|
|
532
|
+
mint_account: mint,
|
|
533
|
+
page: page,
|
|
534
|
+
limit: 0,
|
|
535
|
+
has_next: false,
|
|
536
|
+
has_prev: false
|
|
537
|
+
},
|
|
538
|
+
message: "Operation successful"
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Traverse linked list to read ALL orders
|
|
543
|
+
const orders = [];
|
|
544
|
+
let currentIndex = header.head;
|
|
545
|
+
|
|
546
|
+
// Read all orders without limit
|
|
547
|
+
while (currentIndex !== 65535) {
|
|
548
|
+
// Parse order at current index
|
|
549
|
+
const order = this._parseMarginOrder(data, currentIndex, header.headerSize);
|
|
550
|
+
|
|
551
|
+
// Data transformation - convert to API format
|
|
552
|
+
const convertedOrder = {
|
|
553
|
+
// Convert chain number to API string format
|
|
554
|
+
order_type: order.orderType === 1 ? 'down_orders' : 'up_orders', // 1=long=down_orders, 2=short=up_orders
|
|
555
|
+
mint: mint, // Use mint from function parameter (not stored in MarginOrder)
|
|
556
|
+
user: order.user.toString(),
|
|
557
|
+
|
|
558
|
+
// Price fields (u128 -> string)
|
|
559
|
+
lock_lp_start_price: order.lockLpStartPrice.toString(),
|
|
560
|
+
lock_lp_end_price: order.lockLpEndPrice.toString(),
|
|
561
|
+
open_price: order.openPrice.toString(),
|
|
562
|
+
|
|
563
|
+
// Order ID field (u64 -> string)
|
|
564
|
+
order_id: order.orderId.toString(),
|
|
565
|
+
|
|
566
|
+
// Amount fields (u64 -> string) - Fix precision issue
|
|
567
|
+
lock_lp_sol_amount: order.lockLpSolAmount.toString(),
|
|
568
|
+
lock_lp_token_amount: order.lockLpTokenAmount.toString(),
|
|
569
|
+
next_lp_sol_amount: order.nextLpSolAmount.toString(),
|
|
570
|
+
next_lp_token_amount: order.nextLpTokenAmount.toString(),
|
|
571
|
+
|
|
572
|
+
// Time fields (u32 -> number)
|
|
573
|
+
start_time: order.startTime,
|
|
574
|
+
end_time: order.endTime,
|
|
575
|
+
|
|
576
|
+
// Margin and position fields (u64 -> string) - Fix precision issue
|
|
577
|
+
margin_init_sol_amount: order.marginInitSolAmount.toString(),
|
|
578
|
+
margin_sol_amount: order.marginSolAmount.toString(),
|
|
579
|
+
borrow_amount: order.borrowAmount.toString(),
|
|
580
|
+
position_asset_amount: order.positionAssetAmount.toString(),
|
|
581
|
+
realized_sol_amount: order.realizedSolAmount.toString(),
|
|
582
|
+
|
|
583
|
+
// Fee field (u16 -> number)
|
|
584
|
+
borrow_fee: order.borrowFee,
|
|
585
|
+
|
|
586
|
+
// Order index in OrderBook (uses currentIndex from linked list)
|
|
587
|
+
index: currentIndex,
|
|
588
|
+
|
|
589
|
+
// Linked list navigation fields (u16 -> number)
|
|
590
|
+
next_order: order.nextOrder,
|
|
591
|
+
prev_order: order.prevOrder
|
|
592
|
+
};
|
|
593
|
+
|
|
594
|
+
orders.push(convertedOrder);
|
|
595
|
+
|
|
596
|
+
// Move to next order
|
|
597
|
+
currentIndex = order.nextOrder;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Return all orders with pagination-like format for compatibility
|
|
601
|
+
const totalOrders = orders.length;
|
|
602
|
+
|
|
603
|
+
return {
|
|
604
|
+
success: true,
|
|
605
|
+
data: {
|
|
606
|
+
orders: orders,
|
|
607
|
+
total: totalOrders,
|
|
608
|
+
order_type: orderType,
|
|
609
|
+
mint_account: mint,
|
|
610
|
+
page: page,
|
|
611
|
+
limit: totalOrders, // limit equals total for compatibility
|
|
612
|
+
has_next: false, // Always false since all data is returned
|
|
613
|
+
has_prev: false // Always false since all data is returned
|
|
614
|
+
},
|
|
615
|
+
message: "Operation successful"
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
} catch (error) {
|
|
619
|
+
// Error handling
|
|
620
|
+
console.error('chain.orders: Failed to get orders', error.message);
|
|
621
|
+
throw new Error(`Failed to get orders: ${error.message}`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/**
|
|
626
|
+
* Parse OrderBook Header from account data
|
|
627
|
+
* @private
|
|
628
|
+
* @param {Buffer} data - OrderBook account data
|
|
629
|
+
* @returns {Object} Parsed header object
|
|
630
|
+
*/
|
|
631
|
+
_parseOrderBookHeader(data) {
|
|
632
|
+
// Header structure (112 bytes total):
|
|
633
|
+
// discriminator(8) + version(1) + order_type(1) + bump(1) + padding1(5) +
|
|
634
|
+
// authority(32) + order_id_counter(8) + created_at(8) + last_modified(8) +
|
|
635
|
+
// total_capacity(4) + head(2) + tail(2) + total(2) + padding2(2) + reserved(32)
|
|
636
|
+
|
|
637
|
+
let offset = 8; // Skip discriminator
|
|
638
|
+
|
|
639
|
+
const version = data.readUInt8(offset);
|
|
640
|
+
offset += 1;
|
|
641
|
+
|
|
642
|
+
const orderType = data.readUInt8(offset);
|
|
643
|
+
offset += 1;
|
|
644
|
+
|
|
645
|
+
const bump = data.readUInt8(offset);
|
|
646
|
+
offset += 1;
|
|
647
|
+
|
|
648
|
+
offset += 5; // Skip padding1
|
|
649
|
+
|
|
650
|
+
const authority = new PublicKey(data.slice(offset, offset + 32));
|
|
651
|
+
offset += 32;
|
|
652
|
+
|
|
653
|
+
const orderIdCounter = data.readBigUInt64LE(offset);
|
|
654
|
+
offset += 8;
|
|
655
|
+
|
|
656
|
+
// created_at: i64 (8 bytes) - Unix timestamp in seconds
|
|
657
|
+
const createdAt = Number(data.readBigInt64LE(offset));
|
|
658
|
+
offset += 8;
|
|
659
|
+
|
|
660
|
+
// last_modified: i64 (8 bytes) - Unix timestamp in seconds
|
|
661
|
+
const lastModified = Number(data.readBigInt64LE(offset));
|
|
662
|
+
offset += 8;
|
|
663
|
+
|
|
664
|
+
const totalCapacity = data.readUInt32LE(offset);
|
|
665
|
+
offset += 4;
|
|
666
|
+
|
|
667
|
+
const head = data.readUInt16LE(offset);
|
|
668
|
+
offset += 2;
|
|
669
|
+
|
|
670
|
+
const tail = data.readUInt16LE(offset);
|
|
671
|
+
offset += 2;
|
|
672
|
+
|
|
673
|
+
const total = data.readUInt16LE(offset);
|
|
674
|
+
offset += 2;
|
|
675
|
+
|
|
676
|
+
offset += 2; // Skip padding2
|
|
677
|
+
offset += 32; // Skip reserved
|
|
678
|
+
|
|
679
|
+
return {
|
|
680
|
+
version,
|
|
681
|
+
orderType,
|
|
682
|
+
bump,
|
|
683
|
+
authority,
|
|
684
|
+
orderIdCounter,
|
|
685
|
+
createdAt,
|
|
686
|
+
lastModified,
|
|
687
|
+
totalCapacity,
|
|
688
|
+
head,
|
|
689
|
+
tail,
|
|
690
|
+
total,
|
|
691
|
+
headerSize: 112 // Fixed header size (updated for i64 timestamps)
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Parse MarginOrder from OrderBook account data
|
|
697
|
+
* @private
|
|
698
|
+
* @param {Buffer} data - OrderBook account data
|
|
699
|
+
* @param {number} index - Order slot index
|
|
700
|
+
* @param {number} headerSize - Header size (112 bytes)
|
|
701
|
+
* @returns {Object} Parsed order object
|
|
702
|
+
*/
|
|
703
|
+
_parseMarginOrder(data, index, headerSize) {
|
|
704
|
+
// MarginOrder structure (192 bytes per order):
|
|
705
|
+
// user(32) + lock_lp_start_price(16) + lock_lp_end_price(16) + open_price(16) +
|
|
706
|
+
// order_id(8) + lock_lp_sol_amount(8) + lock_lp_token_amount(8) +
|
|
707
|
+
// next_lp_sol_amount(8) + next_lp_token_amount(8) +
|
|
708
|
+
// margin_init_sol_amount(8) + margin_sol_amount(8) + borrow_amount(8) +
|
|
709
|
+
// position_asset_amount(8) + realized_sol_amount(8) +
|
|
710
|
+
// start_time(8) + end_time(8) + version(4) +
|
|
711
|
+
// next_order(2) + prev_order(2) + borrow_fee(2) +
|
|
712
|
+
// order_type(1) + padding(5)
|
|
713
|
+
|
|
714
|
+
const MARGIN_ORDER_SIZE = 192;
|
|
715
|
+
let offset = 8 + headerSize + index * MARGIN_ORDER_SIZE;
|
|
716
|
+
|
|
717
|
+
// Boundary check
|
|
718
|
+
if (offset + MARGIN_ORDER_SIZE > data.length) {
|
|
719
|
+
throw new Error(`Order index ${index} exceeds data boundary`);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// user (32 bytes)
|
|
723
|
+
const user = new PublicKey(data.slice(offset, offset + 32));
|
|
724
|
+
offset += 32;
|
|
725
|
+
|
|
726
|
+
// lock_lp_start_price (u128, 16 bytes)
|
|
727
|
+
const lockLpStartPrice = this._readU128LE(data, offset);
|
|
728
|
+
offset += 16;
|
|
729
|
+
|
|
730
|
+
// lock_lp_end_price (u128, 16 bytes)
|
|
731
|
+
const lockLpEndPrice = this._readU128LE(data, offset);
|
|
732
|
+
offset += 16;
|
|
733
|
+
|
|
734
|
+
// open_price (u128, 16 bytes)
|
|
735
|
+
const openPrice = this._readU128LE(data, offset);
|
|
736
|
+
offset += 16;
|
|
737
|
+
|
|
738
|
+
// order_id (u64, 8 bytes)
|
|
739
|
+
const orderId = data.readBigUInt64LE(offset);
|
|
740
|
+
offset += 8;
|
|
741
|
+
|
|
742
|
+
// lock_lp_sol_amount (u64, 8 bytes)
|
|
743
|
+
const lockLpSolAmount = data.readBigUInt64LE(offset);
|
|
744
|
+
offset += 8;
|
|
745
|
+
|
|
746
|
+
// lock_lp_token_amount (u64, 8 bytes)
|
|
747
|
+
const lockLpTokenAmount = data.readBigUInt64LE(offset);
|
|
748
|
+
offset += 8;
|
|
749
|
+
|
|
750
|
+
// next_lp_sol_amount (u64, 8 bytes)
|
|
751
|
+
const nextLpSolAmount = data.readBigUInt64LE(offset);
|
|
752
|
+
offset += 8;
|
|
753
|
+
|
|
754
|
+
// next_lp_token_amount (u64, 8 bytes)
|
|
755
|
+
const nextLpTokenAmount = data.readBigUInt64LE(offset);
|
|
756
|
+
offset += 8;
|
|
757
|
+
|
|
758
|
+
// margin_init_sol_amount (u64, 8 bytes)
|
|
759
|
+
const marginInitSolAmount = data.readBigUInt64LE(offset);
|
|
760
|
+
offset += 8;
|
|
761
|
+
|
|
762
|
+
// margin_sol_amount (u64, 8 bytes)
|
|
763
|
+
const marginSolAmount = data.readBigUInt64LE(offset);
|
|
764
|
+
offset += 8;
|
|
765
|
+
|
|
766
|
+
// borrow_amount (u64, 8 bytes)
|
|
767
|
+
const borrowAmount = data.readBigUInt64LE(offset);
|
|
768
|
+
offset += 8;
|
|
769
|
+
|
|
770
|
+
// position_asset_amount (u64, 8 bytes)
|
|
771
|
+
const positionAssetAmount = data.readBigUInt64LE(offset);
|
|
772
|
+
offset += 8;
|
|
773
|
+
|
|
774
|
+
// realized_sol_amount (u64, 8 bytes)
|
|
775
|
+
const realizedSolAmount = data.readBigUInt64LE(offset);
|
|
776
|
+
offset += 8;
|
|
777
|
+
|
|
778
|
+
// start_time (i64, 8 bytes) - Unix timestamp in seconds
|
|
779
|
+
const startTime = Number(data.readBigInt64LE(offset));
|
|
780
|
+
offset += 8;
|
|
781
|
+
|
|
782
|
+
// end_time (i64, 8 bytes) - Unix timestamp in seconds
|
|
783
|
+
const endTime = Number(data.readBigInt64LE(offset));
|
|
784
|
+
offset += 8;
|
|
785
|
+
|
|
786
|
+
// version (u32, 4 bytes)
|
|
787
|
+
const version = data.readUInt32LE(offset);
|
|
788
|
+
offset += 4;
|
|
789
|
+
|
|
790
|
+
// next_order (u16, 2 bytes)
|
|
791
|
+
const nextOrder = data.readUInt16LE(offset);
|
|
792
|
+
offset += 2;
|
|
793
|
+
|
|
794
|
+
// prev_order (u16, 2 bytes)
|
|
795
|
+
const prevOrder = data.readUInt16LE(offset);
|
|
796
|
+
offset += 2;
|
|
797
|
+
|
|
798
|
+
// borrow_fee (u16, 2 bytes)
|
|
799
|
+
const borrowFee = data.readUInt16LE(offset);
|
|
800
|
+
offset += 2;
|
|
801
|
+
|
|
802
|
+
// order_type (u8, 1 byte)
|
|
803
|
+
const orderType = data.readUInt8(offset);
|
|
804
|
+
offset += 1;
|
|
805
|
+
|
|
806
|
+
// Skip padding (5 bytes)
|
|
807
|
+
offset += 5;
|
|
808
|
+
|
|
809
|
+
// Note: mint is not stored in MarginOrder structure
|
|
810
|
+
// It should be obtained from context (the mint parameter passed to orders() function)
|
|
811
|
+
|
|
812
|
+
return {
|
|
813
|
+
user,
|
|
814
|
+
lockLpStartPrice,
|
|
815
|
+
lockLpEndPrice,
|
|
816
|
+
openPrice,
|
|
817
|
+
orderId,
|
|
818
|
+
lockLpSolAmount,
|
|
819
|
+
lockLpTokenAmount,
|
|
820
|
+
nextLpSolAmount,
|
|
821
|
+
nextLpTokenAmount,
|
|
822
|
+
marginInitSolAmount,
|
|
823
|
+
marginSolAmount,
|
|
824
|
+
borrowAmount,
|
|
825
|
+
positionAssetAmount,
|
|
826
|
+
realizedSolAmount,
|
|
827
|
+
version,
|
|
828
|
+
startTime,
|
|
829
|
+
endTime,
|
|
830
|
+
nextOrder,
|
|
831
|
+
prevOrder,
|
|
832
|
+
borrowFee,
|
|
833
|
+
orderType
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Read u128 value (little-endian) from buffer
|
|
839
|
+
* @private
|
|
840
|
+
* @param {Buffer} buffer - Data buffer
|
|
841
|
+
* @param {number} offset - Read offset
|
|
842
|
+
* @returns {bigint} u128 value as BigInt
|
|
843
|
+
*/
|
|
844
|
+
_readU128LE(buffer, offset) {
|
|
845
|
+
// Read low 64 bits
|
|
846
|
+
const low = buffer.readBigUInt64LE(offset);
|
|
847
|
+
// Read high 64 bits
|
|
848
|
+
const high = buffer.readBigUInt64LE(offset + 8);
|
|
849
|
+
// Combine into u128
|
|
850
|
+
return (high << 64n) | low;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* Debug Orders Data (Read ALL order slots from Chain, ignore linked list structure)
|
|
856
|
+
*
|
|
857
|
+
* This function is designed for debugging corrupted linked list data.
|
|
858
|
+
* It directly reads ALL order slots based on totalCapacity, without following next_order/prev_order.
|
|
859
|
+
* Use this when linked list navigation is broken (corrupted next_order/prev_order values).
|
|
860
|
+
*
|
|
861
|
+
* @param {string} mint - Token mint address
|
|
862
|
+
* @param {Object} options - Query parameters
|
|
863
|
+
* @param {string} options.type - Order type: "up_orders" (short) or "down_orders" (long)
|
|
864
|
+
* @returns {Promise<Object>} Debug order data with ALL order slots (including empty ones)
|
|
865
|
+
*
|
|
866
|
+
* @example
|
|
867
|
+
* // Get all long order slots for debugging (ignores linked list)
|
|
868
|
+
* const debugData = await sdk.chain.debug_orders('6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee', { type: 'down_orders' });
|
|
869
|
+
*
|
|
870
|
+
* // Return format:
|
|
871
|
+
* // {
|
|
872
|
+
* // "success": true,
|
|
873
|
+
* // "data": {
|
|
874
|
+
* // "header": {
|
|
875
|
+
* // "version": 1,
|
|
876
|
+
* // "orderType": 1,
|
|
877
|
+
* // "bump": 253,
|
|
878
|
+
* // "authority": "7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5",
|
|
879
|
+
* // "orderIdCounter": "123",
|
|
880
|
+
* // "createdAt": 1755964862,
|
|
881
|
+
* // "lastModified": 1756137662,
|
|
882
|
+
* // "totalCapacity": 100, // Max order slots
|
|
883
|
+
* // "head": 0, // Head index (may be corrupted)
|
|
884
|
+
* // "tail": 5, // Tail index (may be corrupted)
|
|
885
|
+
* // "total": 6, // Total active orders (may be incorrect)
|
|
886
|
+
* // "headerSize": 104
|
|
887
|
+
* // },
|
|
888
|
+
* // "orders": [
|
|
889
|
+
* // {
|
|
890
|
+
* // "slot_index": 0, // Physical slot index (0 to totalCapacity-1)
|
|
891
|
+
* // "is_empty": false, // Whether this slot is empty (all zeros)
|
|
892
|
+
* // "order_type": "down_orders",
|
|
893
|
+
* // "mint": "6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee",
|
|
894
|
+
* // "user": "JD1eNPaJpbtejKfgimbLYLkvpsTHyYzKCCozVLGLS6zu",
|
|
895
|
+
* // "lock_lp_start_price": "46618228118401293964111",
|
|
896
|
+
* // "lock_lp_end_price": "45827474968448818396222",
|
|
897
|
+
* // "open_price": "46222851543425056180166",
|
|
898
|
+
* // "order_id": "12345",
|
|
899
|
+
* // "lock_lp_sol_amount": 3299491609,
|
|
900
|
+
* // "lock_lp_token_amount": 713848715669,
|
|
901
|
+
* // "next_lp_sol_amount": 3299491609,
|
|
902
|
+
* // "next_lp_token_amount": 713848715669,
|
|
903
|
+
* // "start_time": 1756352482, // Unix timestamp (i64 as number)
|
|
904
|
+
* // "end_time": 1756525282, // Unix timestamp (i64 as number)
|
|
905
|
+
* // "margin_init_sol_amount": 571062973,
|
|
906
|
+
* // "margin_sol_amount": 571062973,
|
|
907
|
+
* // "borrow_amount": 3860656108,
|
|
908
|
+
* // "position_asset_amount": 713848715669,
|
|
909
|
+
* // "realized_sol_amount": 0,
|
|
910
|
+
* // "borrow_fee": 300,
|
|
911
|
+
* // "next_order": 2, // Next index (may be corrupted)
|
|
912
|
+
* // "prev_order": 65535 // Prev index (may be corrupted)
|
|
913
|
+
* // },
|
|
914
|
+
* // {
|
|
915
|
+
* // "slot_index": 1,
|
|
916
|
+
* // "is_empty": true, // Empty slot (all zeros)
|
|
917
|
+
* // "order_type": 0,
|
|
918
|
+
* // "user": "11111111111111111111111111111111",
|
|
919
|
+
* // ... // All fields will be zeros/default values
|
|
920
|
+
* // },
|
|
921
|
+
* // ... // All slots up to totalCapacity
|
|
922
|
+
* // ],
|
|
923
|
+
* // "total_slots": 100, // Total capacity (all slots)
|
|
924
|
+
* // "non_empty_slots": 6, // Count of non-empty slots
|
|
925
|
+
* // "order_type": "down_orders",
|
|
926
|
+
* // "mint_account": "6ZDJtGFTzrF3FaN5uaqa1h8EexW7BtQd4FwA9Dt7m3ee"
|
|
927
|
+
* // },
|
|
928
|
+
* // "message": "Debug data retrieved (ignores linked list)"
|
|
929
|
+
* // }
|
|
930
|
+
*
|
|
931
|
+
* @note This function is for debugging only. It may return empty slots and ignores linked list navigation.
|
|
932
|
+
* @note Use regular orders() function for production code.
|
|
933
|
+
*/
|
|
934
|
+
async debug_orders(mint, options = {}) {
|
|
935
|
+
try {
|
|
936
|
+
// Parameter validation
|
|
937
|
+
if (!mint || typeof mint !== 'string') {
|
|
938
|
+
throw new Error('debug_orders: mint address must be a valid string');
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Set default parameters
|
|
942
|
+
const orderType = options.type || 'down_orders';
|
|
943
|
+
|
|
944
|
+
// Validate order type
|
|
945
|
+
if (!['up_orders', 'down_orders'].includes(orderType)) {
|
|
946
|
+
throw new Error('debug_orders: order type must be "up_orders" or "down_orders"');
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// 本地计算 orderbook PDA,避免调用 getCurveAccount(省 5 个 RPC)
|
|
950
|
+
const seed = orderType === 'up_orders' ? 'up_orderbook' : 'down_orderbook';
|
|
951
|
+
const mintPubkey = new PublicKey(mint);
|
|
952
|
+
const [orderbookPubkey] = PublicKey.findProgramAddressSync(
|
|
953
|
+
[Buffer.from(seed), mintPubkey.toBuffer()],
|
|
954
|
+
this.sdk.programId
|
|
955
|
+
);
|
|
956
|
+
|
|
957
|
+
// Get OrderBook account data
|
|
958
|
+
const accountInfo = await this.sdk.connection.getAccountInfo(orderbookPubkey);
|
|
959
|
+
|
|
960
|
+
if (!accountInfo) {
|
|
961
|
+
// OrderBook account doesn't exist, return empty result
|
|
962
|
+
return {
|
|
963
|
+
success: true,
|
|
964
|
+
data: {
|
|
965
|
+
header: null,
|
|
966
|
+
orders: [],
|
|
967
|
+
total_slots: 0,
|
|
968
|
+
non_empty_slots: 0,
|
|
969
|
+
order_type: orderType,
|
|
970
|
+
mint_account: mint
|
|
971
|
+
},
|
|
972
|
+
message: "OrderBook account does not exist"
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
const data = accountInfo.data;
|
|
977
|
+
|
|
978
|
+
// Parse OrderBook Header
|
|
979
|
+
const header = this._parseOrderBookHeader(data);
|
|
980
|
+
|
|
981
|
+
// Read ALL order slots based on totalCapacity (ignore linked list)
|
|
982
|
+
const orders = [];
|
|
983
|
+
let nonEmptyCount = 0;
|
|
984
|
+
|
|
985
|
+
// Iterate through ALL slots from 0 to totalCapacity-1
|
|
986
|
+
for (let slotIndex = 0; slotIndex < header.totalCapacity; slotIndex++) {
|
|
987
|
+
try {
|
|
988
|
+
// Parse order at this slot
|
|
989
|
+
const order = this._parseMarginOrder(data, slotIndex, header.headerSize);
|
|
990
|
+
|
|
991
|
+
// Check if slot is empty (user address is all zeros)
|
|
992
|
+
const isEmpty = order.user.toString() === '11111111111111111111111111111111';
|
|
993
|
+
|
|
994
|
+
if (!isEmpty) {
|
|
995
|
+
nonEmptyCount++;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Convert to API format
|
|
999
|
+
const convertedOrder = {
|
|
1000
|
+
slot_index: slotIndex, // Physical slot position
|
|
1001
|
+
is_empty: isEmpty, // Empty slot indicator
|
|
1002
|
+
|
|
1003
|
+
// Order data fields
|
|
1004
|
+
order_type: order.orderType === 1 ? 'down_orders' : 'up_orders',
|
|
1005
|
+
mint: mint,
|
|
1006
|
+
user: order.user.toString(),
|
|
1007
|
+
|
|
1008
|
+
// Price fields (u128 -> string)
|
|
1009
|
+
lock_lp_start_price: order.lockLpStartPrice.toString(),
|
|
1010
|
+
lock_lp_end_price: order.lockLpEndPrice.toString(),
|
|
1011
|
+
open_price: order.openPrice.toString(),
|
|
1012
|
+
|
|
1013
|
+
// Order ID field (u64 -> string)
|
|
1014
|
+
order_id: order.orderId.toString(),
|
|
1015
|
+
|
|
1016
|
+
// Amount fields (u64 -> string) - Fix precision issue
|
|
1017
|
+
lock_lp_sol_amount: order.lockLpSolAmount.toString(),
|
|
1018
|
+
lock_lp_token_amount: order.lockLpTokenAmount.toString(),
|
|
1019
|
+
next_lp_sol_amount: order.nextLpSolAmount.toString(),
|
|
1020
|
+
next_lp_token_amount: order.nextLpTokenAmount.toString(),
|
|
1021
|
+
|
|
1022
|
+
// Time fields (u32 -> number)
|
|
1023
|
+
start_time: order.startTime,
|
|
1024
|
+
end_time: order.endTime,
|
|
1025
|
+
|
|
1026
|
+
// Margin and position fields (u64 -> string) - Fix precision issue
|
|
1027
|
+
margin_init_sol_amount: order.marginInitSolAmount.toString(),
|
|
1028
|
+
margin_sol_amount: order.marginSolAmount.toString(),
|
|
1029
|
+
borrow_amount: order.borrowAmount.toString(),
|
|
1030
|
+
position_asset_amount: order.positionAssetAmount.toString(),
|
|
1031
|
+
realized_sol_amount: order.realizedSolAmount.toString(),
|
|
1032
|
+
|
|
1033
|
+
// Fee field (u16 -> number)
|
|
1034
|
+
borrow_fee: order.borrowFee,
|
|
1035
|
+
|
|
1036
|
+
// Linked list navigation (may be corrupted, for debugging)
|
|
1037
|
+
next_order: order.nextOrder,
|
|
1038
|
+
prev_order: order.prevOrder
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
orders.push(convertedOrder);
|
|
1042
|
+
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
// If parsing fails (beyond data boundary), stop iteration
|
|
1045
|
+
console.warn(`debug_orders: Failed to parse slot ${slotIndex}: ${error.message}`);
|
|
1046
|
+
break;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Return debug data
|
|
1051
|
+
return {
|
|
1052
|
+
success: true,
|
|
1053
|
+
data: {
|
|
1054
|
+
header: {
|
|
1055
|
+
version: header.version,
|
|
1056
|
+
orderType: header.orderType,
|
|
1057
|
+
bump: header.bump,
|
|
1058
|
+
authority: header.authority.toString(),
|
|
1059
|
+
orderIdCounter: header.orderIdCounter.toString(),
|
|
1060
|
+
createdAt: header.createdAt,
|
|
1061
|
+
lastModified: header.lastModified,
|
|
1062
|
+
totalCapacity: header.totalCapacity,
|
|
1063
|
+
head: header.head,
|
|
1064
|
+
tail: header.tail,
|
|
1065
|
+
total: header.total,
|
|
1066
|
+
headerSize: header.headerSize
|
|
1067
|
+
},
|
|
1068
|
+
orders: orders,
|
|
1069
|
+
total_slots: orders.length, // Total slots read
|
|
1070
|
+
non_empty_slots: nonEmptyCount, // Non-empty slots count
|
|
1071
|
+
order_type: orderType,
|
|
1072
|
+
mint_account: mint
|
|
1073
|
+
},
|
|
1074
|
+
message: "Debug data retrieved (ignores linked list)"
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
// Error handling
|
|
1079
|
+
console.error('chain.debug_orders: Failed to get debug orders', error.message);
|
|
1080
|
+
throw new Error(`Failed to get debug orders: ${error.message}`);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/**
|
|
1085
|
+
* Get User Orders (Read from Chain using new OrderBook structure)
|
|
1086
|
+
* Returns ALL user orders regardless of pagination parameters (for compatibility)
|
|
1087
|
+
* @param {string} user - User wallet address
|
|
1088
|
+
* @param {string} mint - Token mint address
|
|
1089
|
+
* @param {Object} options - Query parameters (page and limit are ignored but kept for compatibility)
|
|
1090
|
+
* @param {number} options.page - Page number (ignored, always returns all data)
|
|
1091
|
+
* @param {number} options.limit - Items per page (ignored, always returns all data)
|
|
1092
|
+
* @param {string} options.order_by - Sort order, default 'start_time_desc'
|
|
1093
|
+
* @returns {Promise<Object>} User orders data with ALL orders
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* const userOrders = await sdk.chain.user_orders(
|
|
1097
|
+
* '8iGFeUkRpyRx8w5uoUMbfZepUr6BfTdPuJmqGoNBntdb',
|
|
1098
|
+
* '4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X',
|
|
1099
|
+
* { order_by: 'start_time_desc' }
|
|
1100
|
+
* );
|
|
1101
|
+
* // Return format:
|
|
1102
|
+
* // {
|
|
1103
|
+
* // "success": true,
|
|
1104
|
+
* // "data": {
|
|
1105
|
+
* // "orders": [
|
|
1106
|
+
* // {
|
|
1107
|
+
* // "order_type": 2, // Order type: 1=long, 2=short
|
|
1108
|
+
* // "mint": "4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X", // Token address
|
|
1109
|
+
* // "user": "8iGFeUkRpyRx8w5uoUMbfZepUr6BfTdPuJmqGoNBntdb", // User address
|
|
1110
|
+
* // "lock_lp_start_price": "753522984132656210522", // LP start price
|
|
1111
|
+
* // "lock_lp_end_price": "833102733432007194898", // LP end price
|
|
1112
|
+
* // "open_price": "793312858782331702710", // Open price - NEW
|
|
1113
|
+
* // "order_id": "12345", // Order ID (u64 as string) - NEW
|
|
1114
|
+
* // "lock_lp_sol_amount": 2535405978, // LP locked SOL
|
|
1115
|
+
* // "lock_lp_token_amount": 32000000000000, // LP locked token
|
|
1116
|
+
* // "next_lp_sol_amount": 2535405978, // Next LP SOL - NEW
|
|
1117
|
+
* // "next_lp_token_amount": 32000000000000, // Next LP token - NEW
|
|
1118
|
+
* // "start_time": 1755964862, // Start timestamp (i64 as number)
|
|
1119
|
+
* // "end_time": 1756137662, // End timestamp (i64 as number)
|
|
1120
|
+
* // "margin_init_sol_amount": 1909140052, // Initial margin - NEW
|
|
1121
|
+
* // "margin_sol_amount": 1909140052, // Current margin
|
|
1122
|
+
* // "borrow_amount": 32000000000000, // Borrow amount
|
|
1123
|
+
* // "position_asset_amount": 656690798, // Position asset
|
|
1124
|
+
* // "realized_sol_amount": 0, // Realized SOL - NEW
|
|
1125
|
+
* // "borrow_fee": 1200, // Borrow fee (bps)
|
|
1126
|
+
* // "index": 0, // Order index in OrderBook (currentIndex)
|
|
1127
|
+
* // "next_order": 2, // Next order index in linked list (u16, 65535=none) - NEW
|
|
1128
|
+
* // "prev_order": 65535 // Previous order index in linked list (u16, 65535=none) - NEW
|
|
1129
|
+
* // }
|
|
1130
|
+
* // ],
|
|
1131
|
+
* // "total": 11, // Total order count
|
|
1132
|
+
* // "user": "8iGFeUkRpyRx8w5uoUMbfZepUr6BfTdPuJmqGoNBntdb", // User address
|
|
1133
|
+
* // "mint_account": "4Kq51Kt48FCwdo5CeKjRVPodH1ticHa7mZ5n5gqMEy1X", // Token address
|
|
1134
|
+
* // "page": 1, // Always 1 (for compatibility)
|
|
1135
|
+
* // "limit": <total>, // Always equals total (for compatibility)
|
|
1136
|
+
* // "has_next": false, // Always false (all data returned)
|
|
1137
|
+
* // "has_prev": false // Always false (all data returned)
|
|
1138
|
+
* // },
|
|
1139
|
+
* // "message": "Operation successful"
|
|
1140
|
+
* // }
|
|
1141
|
+
*
|
|
1142
|
+
* // Use order data:
|
|
1143
|
+
* const orders = userOrders.data.orders; // Order array
|
|
1144
|
+
* const totalCount = userOrders.data.total; // Total count
|
|
1145
|
+
*/
|
|
1146
|
+
async user_orders(user, mint, options = {}) {
|
|
1147
|
+
try {
|
|
1148
|
+
// Parameter validation
|
|
1149
|
+
if (!user || typeof user !== 'string') {
|
|
1150
|
+
throw new Error('user_orders: user address must be a valid string');
|
|
1151
|
+
}
|
|
1152
|
+
if (!mint || typeof mint !== 'string') {
|
|
1153
|
+
throw new Error('user_orders: mint address must be a valid string');
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
// Set default parameters (kept for compatibility)
|
|
1157
|
+
const page = 1; // Always return page 1
|
|
1158
|
+
const orderBy = options.order_by || 'start_time_desc';
|
|
1159
|
+
|
|
1160
|
+
// 本地计算 orderbook PDA,避免调用 getCurveAccount(省 5 个 RPC)
|
|
1161
|
+
const mintPubkey = new PublicKey(mint);
|
|
1162
|
+
const [upOrderbookPubkey] = PublicKey.findProgramAddressSync(
|
|
1163
|
+
[Buffer.from('up_orderbook'), mintPubkey.toBuffer()],
|
|
1164
|
+
this.sdk.programId
|
|
1165
|
+
);
|
|
1166
|
+
const [downOrderbookPubkey] = PublicKey.findProgramAddressSync(
|
|
1167
|
+
[Buffer.from('down_orderbook'), mintPubkey.toBuffer()],
|
|
1168
|
+
this.sdk.programId
|
|
1169
|
+
);
|
|
1170
|
+
const upOrderbookAddress = upOrderbookPubkey.toString(); // Short orders (orderType=2)
|
|
1171
|
+
const downOrderbookAddress = downOrderbookPubkey.toString(); // Long orders (orderType=1)
|
|
1172
|
+
|
|
1173
|
+
// Collect all user orders from both OrderBooks
|
|
1174
|
+
const allUserOrders = [];
|
|
1175
|
+
|
|
1176
|
+
// Helper function to traverse an OrderBook and collect user orders
|
|
1177
|
+
const traverseOrderBook = async (orderbookAddress) => {
|
|
1178
|
+
if (!orderbookAddress) return [];
|
|
1179
|
+
|
|
1180
|
+
const orders = [];
|
|
1181
|
+
|
|
1182
|
+
// Get OrderBook account data
|
|
1183
|
+
const orderbookPubkey = new PublicKey(orderbookAddress);
|
|
1184
|
+
const accountInfo = await this.sdk.connection.getAccountInfo(orderbookPubkey);
|
|
1185
|
+
|
|
1186
|
+
if (!accountInfo) {
|
|
1187
|
+
return []; // OrderBook doesn't exist
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
const data = accountInfo.data;
|
|
1191
|
+
|
|
1192
|
+
// Parse OrderBook Header
|
|
1193
|
+
const header = this._parseOrderBookHeader(data);
|
|
1194
|
+
|
|
1195
|
+
// If no orders in orderbook, return empty
|
|
1196
|
+
if (header.total === 0 || header.head === 65535) {
|
|
1197
|
+
return [];
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
// Traverse linked list to find ALL user orders
|
|
1201
|
+
let currentIndex = header.head;
|
|
1202
|
+
|
|
1203
|
+
while (currentIndex !== 65535) {
|
|
1204
|
+
try {
|
|
1205
|
+
// Parse order at current index
|
|
1206
|
+
const order = this._parseMarginOrder(data, currentIndex, header.headerSize);
|
|
1207
|
+
|
|
1208
|
+
// Check if this order belongs to the target user
|
|
1209
|
+
if (order.user.toString() === user) {
|
|
1210
|
+
// Data transformation - convert to API format
|
|
1211
|
+
const convertedOrder = {
|
|
1212
|
+
// Keep as number for compatibility (1=long, 2=short)
|
|
1213
|
+
order_type: order.orderType,
|
|
1214
|
+
mint: mint, // Use mint from function parameter
|
|
1215
|
+
user: order.user.toString(),
|
|
1216
|
+
|
|
1217
|
+
// Price fields (u128 -> string)
|
|
1218
|
+
lock_lp_start_price: order.lockLpStartPrice.toString(),
|
|
1219
|
+
lock_lp_end_price: order.lockLpEndPrice.toString(),
|
|
1220
|
+
open_price: order.openPrice.toString(),
|
|
1221
|
+
|
|
1222
|
+
// Order ID field (u64 -> string)
|
|
1223
|
+
order_id: order.orderId.toString(),
|
|
1224
|
+
|
|
1225
|
+
// Amount fields (u64 -> string) - Fix precision issue
|
|
1226
|
+
lock_lp_sol_amount: order.lockLpSolAmount.toString(),
|
|
1227
|
+
lock_lp_token_amount: order.lockLpTokenAmount.toString(),
|
|
1228
|
+
next_lp_sol_amount: order.nextLpSolAmount.toString(),
|
|
1229
|
+
next_lp_token_amount: order.nextLpTokenAmount.toString(),
|
|
1230
|
+
|
|
1231
|
+
// Time fields (u32 -> number)
|
|
1232
|
+
start_time: order.startTime,
|
|
1233
|
+
end_time: order.endTime,
|
|
1234
|
+
|
|
1235
|
+
// Margin and position fields (u64 -> string) - Fix precision issue
|
|
1236
|
+
margin_init_sol_amount: order.marginInitSolAmount.toString(),
|
|
1237
|
+
margin_sol_amount: order.marginSolAmount.toString(),
|
|
1238
|
+
borrow_amount: order.borrowAmount.toString(),
|
|
1239
|
+
position_asset_amount: order.positionAssetAmount.toString(),
|
|
1240
|
+
realized_sol_amount: order.realizedSolAmount.toString(),
|
|
1241
|
+
|
|
1242
|
+
// Fee field (u16 -> number)
|
|
1243
|
+
borrow_fee: order.borrowFee,
|
|
1244
|
+
|
|
1245
|
+
// Order index in OrderBook (uses currentIndex from linked list)
|
|
1246
|
+
index: currentIndex,
|
|
1247
|
+
|
|
1248
|
+
// Linked list navigation fields (u16 -> number)
|
|
1249
|
+
next_order: order.nextOrder,
|
|
1250
|
+
prev_order: order.prevOrder
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
orders.push(convertedOrder);
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// Move to next order
|
|
1257
|
+
currentIndex = order.nextOrder;
|
|
1258
|
+
|
|
1259
|
+
} catch (error) {
|
|
1260
|
+
console.warn(`user_orders: Error parsing order at index ${currentIndex}: ${error.message}`);
|
|
1261
|
+
break;
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
return orders;
|
|
1266
|
+
};
|
|
1267
|
+
|
|
1268
|
+
// Traverse both OrderBooks in parallel to find user orders
|
|
1269
|
+
const [upOrders, downOrders] = await Promise.all([
|
|
1270
|
+
traverseOrderBook(upOrderbookAddress),
|
|
1271
|
+
traverseOrderBook(downOrderbookAddress)
|
|
1272
|
+
]);
|
|
1273
|
+
|
|
1274
|
+
// Combine all orders
|
|
1275
|
+
allUserOrders.push(...upOrders, ...downOrders);
|
|
1276
|
+
|
|
1277
|
+
// Sort orders by start_time
|
|
1278
|
+
if (orderBy === 'start_time_desc') {
|
|
1279
|
+
allUserOrders.sort((a, b) => b.start_time - a.start_time);
|
|
1280
|
+
} else if (orderBy === 'start_time_asc') {
|
|
1281
|
+
allUserOrders.sort((a, b) => a.start_time - b.start_time);
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// Return all orders with pagination-like format for compatibility
|
|
1285
|
+
const totalOrders = allUserOrders.length;
|
|
1286
|
+
|
|
1287
|
+
return {
|
|
1288
|
+
success: true,
|
|
1289
|
+
data: {
|
|
1290
|
+
orders: allUserOrders,
|
|
1291
|
+
total: totalOrders,
|
|
1292
|
+
user: user,
|
|
1293
|
+
mint_account: mint,
|
|
1294
|
+
page: page,
|
|
1295
|
+
limit: totalOrders, // limit equals total for compatibility
|
|
1296
|
+
has_next: false, // Always false since all data is returned
|
|
1297
|
+
has_prev: false // Always false since all data is returned
|
|
1298
|
+
},
|
|
1299
|
+
message: "Operation successful"
|
|
1300
|
+
};
|
|
1301
|
+
|
|
1302
|
+
} catch (error) {
|
|
1303
|
+
// Error handling
|
|
1304
|
+
console.error('chain.user_orders: Failed to get user orders', error.message);
|
|
1305
|
+
throw new Error(`Failed to get user orders: ${error.message}`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
module.exports = ChainModule;
|