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,734 @@
|
|
|
1
|
+
const axios = require('axios');
|
|
2
|
+
const CurveAMM = require('../utils/curve_amm');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Fast API Module
|
|
6
|
+
* Access data through API interfaces from centralized servers, quickly obtain transaction parameters
|
|
7
|
+
* Enables smooth trading operations
|
|
8
|
+
*/
|
|
9
|
+
class FastModule {
|
|
10
|
+
constructor(sdk) {
|
|
11
|
+
this.sdk = sdk;
|
|
12
|
+
this.baseUrl = sdk.fastApiUrl;
|
|
13
|
+
|
|
14
|
+
// Only create axios instance if baseUrl is provided
|
|
15
|
+
// This allows SDK to be initialized with chain-only mode
|
|
16
|
+
if (this.baseUrl) {
|
|
17
|
+
// Create axios instance
|
|
18
|
+
this.httpClient = axios.create({
|
|
19
|
+
baseURL: this.baseUrl,
|
|
20
|
+
timeout: 10000,
|
|
21
|
+
headers: {
|
|
22
|
+
'Accept': 'application/json',
|
|
23
|
+
'Content-Type': 'application/json'
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
this.httpClient = null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if FastModule is properly configured
|
|
33
|
+
* @private
|
|
34
|
+
*/
|
|
35
|
+
_ensureConfigured() {
|
|
36
|
+
if (!this.baseUrl || !this.httpClient) {
|
|
37
|
+
throw new Error('FastModule requires fastApiUrl configuration. Either provide fastApiUrl in options or use defaultDataSource: "chain"');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get token list (获取代币列表)
|
|
43
|
+
* @param {Object} options - Query parameters
|
|
44
|
+
* @param {number} options.limit - Items per page, default 10 (每页数量,默认 10)
|
|
45
|
+
* @param {number} options.before_timestamp - Cursor for pagination (分页游标,Unix 时间戳)
|
|
46
|
+
* @returns {Promise<Object>} Token list data
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* // Get latest 5 tokens
|
|
50
|
+
* const result = await sdk.fast.mints({ limit: 5 });
|
|
51
|
+
* // Return format:
|
|
52
|
+
* // {
|
|
53
|
+
* // "code": 200,
|
|
54
|
+
* // "msg": "success",
|
|
55
|
+
* // "data": {
|
|
56
|
+
* // "tokens": [
|
|
57
|
+
* // {
|
|
58
|
+
* // "payer": "8iGFeUkRpyRx8w5uoUMbfZepUr6BfTdPuJmqGoNBntdb",
|
|
59
|
+
* // "mint_account": "7K7WLJ5rKgnjG3xtknAoRmFqWoRS6cNR3TsDEN7MnLJu",
|
|
60
|
+
* // "curve_account": "58XWvk4q9NeD2fbSjy5jRyrG7ub6V8kQFvkf8EuDFVPx",
|
|
61
|
+
* // "pool_token_account": "69cooFpg3Ykg8T6iaUeCT4HhKUvZGwDbjUHUAsQa3oD",
|
|
62
|
+
* // "pool_sol_account": "5vWvG42FrKPPwdZDs7TLVncTLf6VqyQnrhgjuGtWiseH",
|
|
63
|
+
* // "fee_recipient": "GesAj2dTn2wdNcxj4x8qsqS9aNRVPBPkE76aaqg7skxu",
|
|
64
|
+
* // "base_fee_recipient": "5YHi1HsxobLiTD6NQfHJQpoPoRjMuNyXp4RroTvR6dKi",
|
|
65
|
+
* // "params_account": "4yTBVZZQLiYi2p2xgwQXPb329z9jsQnhoPJyGmF3R4hb",
|
|
66
|
+
* // "swap_fee": 1000,
|
|
67
|
+
* // "borrow_fee": 1200,
|
|
68
|
+
* // "fee_discount_flag": 0,
|
|
69
|
+
* // "name": "AutoCreatedToken",
|
|
70
|
+
* // "symbol": "ACT",
|
|
71
|
+
* // "uri": "https://example.com/token-metadata.json",
|
|
72
|
+
* // "up_orderbook": "E63BemzshShTMk3Cbm7wYzm5muS6inokKM1hZWM8aDwp",
|
|
73
|
+
* // "down_orderbook": "CSva3cHwTYsJiK21H9Li4HL9pVzvA1hhy98noYBsbvxb",
|
|
74
|
+
* // "latest_price": "2795899347623485554",
|
|
75
|
+
* // "created_at": 1763804750,
|
|
76
|
+
* // "created_slot": 3156632,
|
|
77
|
+
* // "updated_at": 1763804750,
|
|
78
|
+
* // "extras": {}
|
|
79
|
+
* // }
|
|
80
|
+
* // ],
|
|
81
|
+
* // "total": 5,
|
|
82
|
+
* // "next_cursor": "1763774111"
|
|
83
|
+
* // }
|
|
84
|
+
* // }
|
|
85
|
+
*
|
|
86
|
+
* // Use pagination with cursor:
|
|
87
|
+
* const page1 = await sdk.fast.mints({ limit: 5 });
|
|
88
|
+
* const page2 = await sdk.fast.mints({ limit: 5, before_timestamp: page1.data.next_cursor });
|
|
89
|
+
*
|
|
90
|
+
* // Access token data:
|
|
91
|
+
* const tokens = result.data.tokens; // 完整代币对象数组
|
|
92
|
+
* const mintAddresses = tokens.map(t => t.mint_account); // 提取代币地址
|
|
93
|
+
*/
|
|
94
|
+
async mints(options = {}) {
|
|
95
|
+
const params = {
|
|
96
|
+
limit: options.limit || 10
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Add cursor if provided
|
|
100
|
+
if (options.before_timestamp) {
|
|
101
|
+
params.before_timestamp = options.before_timestamp;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return this._directApiCall({
|
|
105
|
+
endpoint: '/api/tokens/latest',
|
|
106
|
+
method: 'GET'
|
|
107
|
+
}, params);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get token details (获取代币详情信息)
|
|
112
|
+
* @param {string} mint - Token mint address (代币地址)
|
|
113
|
+
* @returns {Promise<Object>} Token details data
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* // Get single token details
|
|
117
|
+
* const info = await sdk.fast.mint_info('Dj5FhAmbQ2qNDUXQV3ST2kJXTH3C3B6mrwfYXPhGb6ea');
|
|
118
|
+
* // Return format:
|
|
119
|
+
* // {
|
|
120
|
+
* // "code": 200,
|
|
121
|
+
* // "msg": "success",
|
|
122
|
+
* // "data": {
|
|
123
|
+
* // "payer": "8iGFeUkRpyRx8w5uoUMbfZepUr6BfTdPuJmqGoNBntdb",
|
|
124
|
+
* // "mint_account": "Dj5FhAmbQ2qNDUXQV3ST2kJXTH3C3B6mrwfYXPhGb6ea",
|
|
125
|
+
* // "curve_account": "3hhFqoPi4C4urNfxCW5iwdSZPg6bQbNc4mwWtuagDsEn",
|
|
126
|
+
* // "pool_token_account": "DTLk1p97Z5tFaZGRSV6qSkogf3gBfj57Jt1xMCAwwBGJ",
|
|
127
|
+
* // "pool_sol_account": "8oxE8wS1Zu4nktRyZqCiTXi6wEburBtRLszLHuZoyJ4b",
|
|
128
|
+
* // "fee_recipient": "GesAj2dTn2wdNcxj4x8qsqS9aNRVPBPkE76aaqg7skxu",
|
|
129
|
+
* // "base_fee_recipient": "5YHi1HsxobLiTD6NQfHJQpoPoRjMuNyXp4RroTvR6dKi",
|
|
130
|
+
* // "params_account": "4yTBVZZQLiYi2p2xgwQXPb329z9jsQnhoPJyGmF3R4hb",
|
|
131
|
+
* // "swap_fee": 1000,
|
|
132
|
+
* // "borrow_fee": 1200,
|
|
133
|
+
* // "fee_discount_flag": 0,
|
|
134
|
+
* // "name": "AutoCreatedToken",
|
|
135
|
+
* // "symbol": "ACT",
|
|
136
|
+
* // "uri": "https://example.com/token-metadata.json",
|
|
137
|
+
* // "up_orderbook": "8gYvmYKFNk6PN78yET2BB6xtyCKaEMgn65b6TA8VFMkM",
|
|
138
|
+
* // "down_orderbook": "6kk8XC3z1WUFJD8GHqqgi79GmoKd9houJ8GUEXk8ki6N",
|
|
139
|
+
* // "latest_price": "2795899347623485554",
|
|
140
|
+
* // "created_at": 1763777802,
|
|
141
|
+
* // "created_slot": 3090283,
|
|
142
|
+
* // "updated_at": 1763777802,
|
|
143
|
+
* // "extras": {}
|
|
144
|
+
* // }
|
|
145
|
+
* // }
|
|
146
|
+
*
|
|
147
|
+
* // Access token data:
|
|
148
|
+
* const tokenData = info.data;
|
|
149
|
+
* const mintAccount = tokenData.mint_account;
|
|
150
|
+
* const latestPrice = tokenData.latest_price;
|
|
151
|
+
*/
|
|
152
|
+
async mint_info(mint) {
|
|
153
|
+
// Validate input
|
|
154
|
+
if (!mint || typeof mint !== 'string') {
|
|
155
|
+
throw new Error('mint_info: 代币地址必须是有效的字符串');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return this._directApiCall({
|
|
159
|
+
endpoint: '/api/tokens/mint',
|
|
160
|
+
method: 'GET',
|
|
161
|
+
usePathParam: true
|
|
162
|
+
}, { mint });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 直接API调用方法
|
|
167
|
+
* @private
|
|
168
|
+
* @param {Object} config - 数据配置
|
|
169
|
+
* @param {Object} params - 请求参数
|
|
170
|
+
* @returns {Promise<Object>} API响应数据
|
|
171
|
+
*/
|
|
172
|
+
async _directApiCall(config, params = {}) {
|
|
173
|
+
// Ensure FastModule is properly configured before making API calls
|
|
174
|
+
this._ensureConfigured();
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
let url = config.endpoint;
|
|
178
|
+
let queryParams = { ...params };
|
|
179
|
+
|
|
180
|
+
// Handle path parameters (e.g., /api/tokens/mint/:mint)
|
|
181
|
+
if (config.usePathParam && params.mint) {
|
|
182
|
+
url = `${config.endpoint}/${params.mint}`;
|
|
183
|
+
delete queryParams.mint; // Remove from query params
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const requestConfig = {
|
|
187
|
+
method: config.method || 'GET',
|
|
188
|
+
url: url
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// 根据请求方法设置参数
|
|
192
|
+
if (config.method === 'POST') {
|
|
193
|
+
requestConfig.data = queryParams; // POST 请求使用 data
|
|
194
|
+
} else {
|
|
195
|
+
requestConfig.params = queryParams; // GET 请求使用 params
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const response = await this.httpClient.request(requestConfig);
|
|
199
|
+
|
|
200
|
+
// 检查新旧API响应格式
|
|
201
|
+
// 新格式: { code: 200, msg: "success", data: {...} }
|
|
202
|
+
// 旧格式: { success: true, data: {...}, message: "..." }
|
|
203
|
+
const isNewFormat = response.data && typeof response.data.code === 'number';
|
|
204
|
+
const isOldFormat = response.data && typeof response.data.success === 'boolean';
|
|
205
|
+
|
|
206
|
+
if (isNewFormat) {
|
|
207
|
+
// 新 API 格式
|
|
208
|
+
if (response.data.code !== 200) {
|
|
209
|
+
throw new Error(`API请求失败: ${response.data.msg || '未知错误'}`);
|
|
210
|
+
}
|
|
211
|
+
} else if (isOldFormat) {
|
|
212
|
+
// 旧 API 格式(向后兼容)
|
|
213
|
+
if (!response.data.success) {
|
|
214
|
+
throw new Error(`API请求失败: ${response.data.message || '未知错误'}`);
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
// 未知格式
|
|
218
|
+
throw new Error('API返回格式无法识别');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return response.data;
|
|
222
|
+
|
|
223
|
+
} catch (error) {
|
|
224
|
+
if (error.response) {
|
|
225
|
+
// API返回错误
|
|
226
|
+
const errorMsg = error.response.data?.msg || error.response.data?.message || error.message;
|
|
227
|
+
throw new Error(`API请求失败 [${error.response.status}]: ${errorMsg}`);
|
|
228
|
+
} else if (error.request) {
|
|
229
|
+
// 网络错误
|
|
230
|
+
throw new Error(`网络请求失败: 无法连接到 ${this.baseUrl}`);
|
|
231
|
+
} else {
|
|
232
|
+
// 其他错误
|
|
233
|
+
throw new Error(`请求处理失败: ${error.message}`);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* 获取订单数据 Get Orders Data (查询活跃订单 Active Orders)
|
|
242
|
+
* @param {string} mint - 代币地址 Token mint address
|
|
243
|
+
* @param {Object} options - 查询参数 Query parameters
|
|
244
|
+
* @param {string} options.type - 订单类型 Order type: "up_orders" (做空/short) 或 "down_orders" (做多/long)
|
|
245
|
+
* @param {number} options.page - 页码,默认1 Page number, default 1
|
|
246
|
+
* @param {number} options.limit - 每页数量,默认500 Items per page, default 500
|
|
247
|
+
* @returns {Promise<Object>} 订单数据,包含订单列表及分页信息 Order data with order list and pagination info
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* // 获取做多订单 Get long orders
|
|
251
|
+
* const ordersData = await sdk.fast.orders('T3NFPvYvpULCTgrhHb4b4Sj5J1qtSNvyKZfE8hCvuKM', { type: 'down_orders' });
|
|
252
|
+
*
|
|
253
|
+
* // 返回值示例 Return value example (compatible with chain.js format):
|
|
254
|
+
* // {
|
|
255
|
+
* // "success": true,
|
|
256
|
+
* // "data": {
|
|
257
|
+
* // "orders": [
|
|
258
|
+
* // {
|
|
259
|
+
* // "index": 0, // 订单在 OrderBook 中的索引位置
|
|
260
|
+
* // "user": "7621yjkZJ1jxBHw3oCPoazFfMx82NWBSARk2AGV7EBig", // 用户地址
|
|
261
|
+
* // "lock_lp_start_price": "2656104380242311276", // LP 开始价格(字符串)
|
|
262
|
+
* // "lock_lp_end_price": "2325402748045870207", // LP 结束价格(字符串)
|
|
263
|
+
* // "open_price": "2795899347623485554", // 开仓价格(字符串)
|
|
264
|
+
* // "order_id": "0", // 订单 ID(字符串)
|
|
265
|
+
* // "lock_lp_sol_amount": 1880793407, // LP 锁定 SOL 数量(lamports)
|
|
266
|
+
* // "lock_lp_token_amount": 75677963031921, // LP 锁定代币数量(最小单位)
|
|
267
|
+
* // "next_lp_sol_amount": 0, // Next LP SOL 数量
|
|
268
|
+
* // "next_lp_token_amount": 0, // Next LP Token 数量
|
|
269
|
+
* // "margin_init_sol_amount": 418211213, // 初始保证金
|
|
270
|
+
* // "margin_sol_amount": 418211213, // 保证金 SOL 数量(lamports)
|
|
271
|
+
* // "borrow_amount": 2276435100, // 借款数量(lamports)
|
|
272
|
+
* // "position_asset_amount": 75677963031921, // 持仓资产数量(最小单位)
|
|
273
|
+
* // "realized_sol_amount": 0, // 已实现收益
|
|
274
|
+
* // "version": 2, // 版本号
|
|
275
|
+
* // "start_time": 1764047379, // 开始时间(Unix 时间戳)
|
|
276
|
+
* // "end_time": 1764652179, // 结束时间(Unix 时间戳)
|
|
277
|
+
* // "next_order": 1, // 链表中下一个订单索引 (65535=none)
|
|
278
|
+
* // "prev_order": 65535, // 链表中上一个订单索引 (65535=none)
|
|
279
|
+
* // "borrow_fee": 1200, // 借款费用(基点,1200 = 12%)
|
|
280
|
+
* // "order_type": "down_orders" // 订单类型(字符串)
|
|
281
|
+
* // }
|
|
282
|
+
* // ],
|
|
283
|
+
* // "total": 10, // 总订单数量 (total_count)
|
|
284
|
+
* // "order_type": "down_orders", // 订单类型(字符串)
|
|
285
|
+
* // "mint_account": "T3NFPvYvpULCTgrhHb4b4Sj5J1qtSNvyKZfE8hCvuKM", // 查询的代币地址
|
|
286
|
+
* // "page": 1, // 当前页码
|
|
287
|
+
* // "limit": 3, // 每页限制
|
|
288
|
+
* // "has_next": true, // 是否有下一页
|
|
289
|
+
* // "has_prev": false // 是否有上一页
|
|
290
|
+
* // },
|
|
291
|
+
* // "message": "Operation successful" // 操作结果消息
|
|
292
|
+
* // }
|
|
293
|
+
*
|
|
294
|
+
* // 访问订单数据 Access order data:
|
|
295
|
+
* const orders = ordersData.data.orders; // 订单数组
|
|
296
|
+
* const totalOrders = ordersData.data.total; // 总订单数量
|
|
297
|
+
* const firstOrder = orders[0]; // 第一个订单
|
|
298
|
+
* const userAddress = firstOrder.user; // 用户地址
|
|
299
|
+
* const marginAmount = firstOrder.margin_sol_amount; // 保证金数量(lamports)
|
|
300
|
+
* const borrowFee = firstOrder.borrow_fee; // 借款费用(基点)
|
|
301
|
+
*
|
|
302
|
+
* // 获取做空订单 Get short orders:
|
|
303
|
+
* const shortOrders = await sdk.fast.orders(mint, { type: 'up_orders' });
|
|
304
|
+
*
|
|
305
|
+
* // 分页获取订单 Get paginated orders:
|
|
306
|
+
* const pageTwo = await sdk.fast.orders(mint, { type: 'down_orders', page: 2, limit: 100 });
|
|
307
|
+
*/
|
|
308
|
+
async orders(mint, options = {}) {
|
|
309
|
+
// Ensure FastModule is properly configured
|
|
310
|
+
this._ensureConfigured();
|
|
311
|
+
|
|
312
|
+
try {
|
|
313
|
+
// Parameter validation
|
|
314
|
+
if (!mint || typeof mint !== 'string') {
|
|
315
|
+
throw new Error('orders: mint address must be a valid string');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Extract parameters with defaults
|
|
319
|
+
const type = options.type || 'down_orders';
|
|
320
|
+
const page = options.page || 1;
|
|
321
|
+
const pageSize = options.limit || 55000;
|
|
322
|
+
|
|
323
|
+
// Validate order type
|
|
324
|
+
if (!['up_orders', 'down_orders'].includes(type)) {
|
|
325
|
+
throw new Error('orders: order type must be "up_orders" or "down_orders"');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Convert type to direction for new API
|
|
329
|
+
// "up_orders" = short orders = direction 'up'
|
|
330
|
+
// "down_orders" = long orders = direction 'dn'
|
|
331
|
+
const direction = type === 'up_orders' ? 'up' : 'dn';
|
|
332
|
+
|
|
333
|
+
// Build API endpoint path: /api/orderbook/{mint}/{direction}
|
|
334
|
+
const url = `/api/orderbook/${mint}/${direction}`;
|
|
335
|
+
|
|
336
|
+
// Build query parameters
|
|
337
|
+
const queryParams = {
|
|
338
|
+
page: page,
|
|
339
|
+
page_size: pageSize
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Make API request
|
|
343
|
+
const requestConfig = {
|
|
344
|
+
method: 'GET',
|
|
345
|
+
url: url,
|
|
346
|
+
params: queryParams
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const response = await this.httpClient.request(requestConfig);
|
|
350
|
+
|
|
351
|
+
// Check new API response format
|
|
352
|
+
const isNewFormat = response.data && typeof response.data.code === 'number';
|
|
353
|
+
|
|
354
|
+
if (!isNewFormat) {
|
|
355
|
+
throw new Error('API返回格式无法识别');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (response.data.code !== 200) {
|
|
359
|
+
throw new Error(`API请求失败: ${response.data.msg || '未知错误'}`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Transform new API format to match chain.js format for compatibility
|
|
363
|
+
const apiData = response.data.data;
|
|
364
|
+
|
|
365
|
+
// Process orders: convert order_type from number to string, ensure all fields are properly formatted
|
|
366
|
+
const processedOrders = apiData.orders.map(order => ({
|
|
367
|
+
// Keep index from API (physical slot index in OrderBook)
|
|
368
|
+
index: order.index,
|
|
369
|
+
|
|
370
|
+
// User address
|
|
371
|
+
user: order.user,
|
|
372
|
+
|
|
373
|
+
// Price fields (ensure strings)
|
|
374
|
+
lock_lp_start_price: String(order.lock_lp_start_price),
|
|
375
|
+
lock_lp_end_price: String(order.lock_lp_end_price),
|
|
376
|
+
open_price: String(order.open_price),
|
|
377
|
+
|
|
378
|
+
// Order ID (ensure string)
|
|
379
|
+
order_id: order.order_id != null ? String(order.order_id) : undefined,
|
|
380
|
+
|
|
381
|
+
// Amount fields (ensure strings) - Fix precision issue
|
|
382
|
+
lock_lp_sol_amount: String(order.lock_lp_sol_amount),
|
|
383
|
+
lock_lp_token_amount: String(order.lock_lp_token_amount),
|
|
384
|
+
next_lp_sol_amount: String(order.next_lp_sol_amount),
|
|
385
|
+
next_lp_token_amount: String(order.next_lp_token_amount),
|
|
386
|
+
|
|
387
|
+
// Margin fields (ensure strings) - Fix precision issue
|
|
388
|
+
margin_init_sol_amount: String(order.margin_init_sol_amount),
|
|
389
|
+
margin_sol_amount: String(order.margin_sol_amount),
|
|
390
|
+
|
|
391
|
+
// Position fields (ensure strings) - Fix precision issue
|
|
392
|
+
borrow_amount: String(order.borrow_amount),
|
|
393
|
+
position_asset_amount: String(order.position_asset_amount),
|
|
394
|
+
realized_sol_amount: String(order.realized_sol_amount),
|
|
395
|
+
|
|
396
|
+
// Version
|
|
397
|
+
version: order.version,
|
|
398
|
+
|
|
399
|
+
// Time fields
|
|
400
|
+
start_time: order.start_time,
|
|
401
|
+
end_time: order.end_time,
|
|
402
|
+
|
|
403
|
+
// Linked list navigation
|
|
404
|
+
next_order: order.next_order,
|
|
405
|
+
prev_order: order.prev_order,
|
|
406
|
+
|
|
407
|
+
// Fee
|
|
408
|
+
borrow_fee: order.borrow_fee,
|
|
409
|
+
|
|
410
|
+
// Convert order_type number to string to match chain.js format
|
|
411
|
+
// order_type 1 = down_orders (long), order_type 2 = up_orders (short)
|
|
412
|
+
order_type: order.order_type === 1 ? 'down_orders' : 'up_orders',
|
|
413
|
+
|
|
414
|
+
// Add mint field for compatibility with chain.js (not in API response, use function parameter)
|
|
415
|
+
mint: mint
|
|
416
|
+
}));
|
|
417
|
+
|
|
418
|
+
// Sort orders by lock_lp_start_price
|
|
419
|
+
// down_orders (做多): descending order (从大到小)
|
|
420
|
+
// up_orders (做空): ascending order (从小到大)
|
|
421
|
+
processedOrders.sort((a, b) => {
|
|
422
|
+
const priceA = BigInt(a.lock_lp_start_price);
|
|
423
|
+
const priceB = BigInt(b.lock_lp_start_price);
|
|
424
|
+
|
|
425
|
+
if (type === 'down_orders') {
|
|
426
|
+
// 做多订单:从大到小排列
|
|
427
|
+
return priceB > priceA ? 1 : (priceB < priceA ? -1 : 0);
|
|
428
|
+
} else {
|
|
429
|
+
// 做空订单:从小到大排列
|
|
430
|
+
return priceA > priceB ? 1 : (priceA < priceB ? -1 : 0);
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
// Return in chain.js compatible format
|
|
435
|
+
return {
|
|
436
|
+
success: true,
|
|
437
|
+
data: {
|
|
438
|
+
orders: processedOrders,
|
|
439
|
+
total: apiData.total_count, // Use total_count from API
|
|
440
|
+
order_type: type, // Keep original type parameter for compatibility
|
|
441
|
+
mint_account: mint, // Add mint_account for compatibility
|
|
442
|
+
page: apiData.page,
|
|
443
|
+
limit: apiData.page_size, // Map page_size to limit
|
|
444
|
+
has_next: apiData.page < apiData.total_pages, // Calculate has_next
|
|
445
|
+
has_prev: apiData.page > 1 // Calculate has_prev
|
|
446
|
+
},
|
|
447
|
+
message: "Operation successful"
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
} catch (error) {
|
|
451
|
+
if (error.response) {
|
|
452
|
+
// API returned error
|
|
453
|
+
const errorMsg = error.response.data?.msg || error.response.data?.message || error.message;
|
|
454
|
+
throw new Error(`API请求失败 [${error.response.status}]: ${errorMsg}`);
|
|
455
|
+
} else if (error.request) {
|
|
456
|
+
// Network error
|
|
457
|
+
throw new Error(`网络请求失败: 无法连接到 ${this.baseUrl}`);
|
|
458
|
+
} else {
|
|
459
|
+
// Other errors
|
|
460
|
+
throw new Error(`请求处理失败: ${error.message}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Get token price (获取代币价格)
|
|
467
|
+
* @param {string} mint - Token mint address (代币地址)
|
|
468
|
+
* @returns {Promise<string>} Latest price string (最新价格字符串)
|
|
469
|
+
*
|
|
470
|
+
* @example
|
|
471
|
+
* // Get token latest price
|
|
472
|
+
* const price = await sdk.fast.price('Dj5FhAmbQ2qNDUXQV3ST2kJXTH3C3B6mrwfYXPhGb6ea');
|
|
473
|
+
* console.log('Latest price:', price); // "2795899347623485554"
|
|
474
|
+
*/
|
|
475
|
+
async price(mint) {
|
|
476
|
+
// Validate input
|
|
477
|
+
if (!mint || typeof mint !== 'string') {
|
|
478
|
+
throw new Error('price: 代币地址必须是有效的字符串 mint address must be a valid string');
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Call mint_info API
|
|
482
|
+
const result = await this.mint_info(mint);
|
|
483
|
+
|
|
484
|
+
// Check return data (新格式: { code: 200, msg: "success", data: {...} })
|
|
485
|
+
if (!result || !result.data) {
|
|
486
|
+
throw new Error('price: 无法获取代币信息 Unable to fetch token information');
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Extract latest price (新格式直接返回对象,不是数组)
|
|
490
|
+
let latestPrice = result.data.latest_price;
|
|
491
|
+
|
|
492
|
+
if (!latestPrice) {
|
|
493
|
+
// If no price data, calculate initial price
|
|
494
|
+
const initialPrice = CurveAMM.getInitialPrice();
|
|
495
|
+
if (initialPrice === null) {
|
|
496
|
+
throw new Error('price: 无法计算初始价格 Unable to calculate initial price');
|
|
497
|
+
}
|
|
498
|
+
latestPrice = initialPrice.toString();
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return latestPrice;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Get User Active Orders (获取用户活跃订单)
|
|
506
|
+
* Compatible with chain.js user_orders() method
|
|
507
|
+
*
|
|
508
|
+
* @param {string} user - User wallet address (用户钱包地址)
|
|
509
|
+
* @param {string} mint - Token mint address (optional, null/undefined for all tokens) (代币地址,可选)
|
|
510
|
+
* @param {Object} options - Query parameters (查询参数)
|
|
511
|
+
* @param {string} options.direction - Order direction (optional: 'up'=short, 'dn'=long, omit=all) (订单方向)
|
|
512
|
+
* @param {number} options.page - Page number, default 1 (页码,默认1)
|
|
513
|
+
* @param {number} options.limit - Items per page, default 20 (每页数量,默认20)
|
|
514
|
+
* @param {string} options.order_by - Sort order: 'start_time_desc' or 'start_time_asc', default 'start_time_desc' (排序方式)
|
|
515
|
+
* @returns {Promise<Object>} User orders data compatible with chain.js format
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* // Get user's all active orders for a specific token
|
|
519
|
+
* const userOrders = await sdk.fast.user_orders(
|
|
520
|
+
* '7621yjkZJ1jxBHw3oCPoazFfMx82NWBSARk2AGV7EBig',
|
|
521
|
+
* 'T3NFPvYvpULCTgrhHb4b4Sj5J1qtSNvyKZfE8hCvuKM',
|
|
522
|
+
* { page: 1, limit: 20 }
|
|
523
|
+
* );
|
|
524
|
+
*
|
|
525
|
+
* // Get user's long orders for a specific token
|
|
526
|
+
* const longOrders = await sdk.fast.user_orders(
|
|
527
|
+
* '7621yjkZJ1jxBHw3oCPoazFfMx82NWBSARk2AGV7EBig',
|
|
528
|
+
* 'T3NFPvYvpULCTgrhHb4b4Sj5J1qtSNvyKZfE8hCvuKM',
|
|
529
|
+
* { direction: 'dn' }
|
|
530
|
+
* );
|
|
531
|
+
*
|
|
532
|
+
* // Get user's all active orders across all tokens (mint = null)
|
|
533
|
+
* const allOrders = await sdk.fast.user_orders(
|
|
534
|
+
* '7621yjkZJ1jxBHw3oCPoazFfMx82NWBSARk2AGV7EBig',
|
|
535
|
+
* null,
|
|
536
|
+
* { page: 1, limit: 20 }
|
|
537
|
+
* );
|
|
538
|
+
*
|
|
539
|
+
* // Return format (compatible with chain.js):
|
|
540
|
+
* // {
|
|
541
|
+
* // "success": true,
|
|
542
|
+
* // "data": {
|
|
543
|
+
* // "orders": [
|
|
544
|
+
* // {
|
|
545
|
+
* // "order_type": 1, // Order type: 1=long, 2=short
|
|
546
|
+
* // "mint": "T3NFPvYvpULCTgrhHb4b4Sj5J1qtSNvyKZfE8hCvuKM", // Token address
|
|
547
|
+
* // "user": "7621yjkZJ1jxBHw3oCPoazFfMx82NWBSARk2AGV7EBig", // User address
|
|
548
|
+
* // "lock_lp_start_price": "2656104380242311276", // LP start price (string)
|
|
549
|
+
* // "lock_lp_end_price": "2325402748045870207", // LP end price (string)
|
|
550
|
+
* // "open_price": "2795899347623485554", // Open price (string)
|
|
551
|
+
* // "order_id": "0", // Order ID (u64 as string)
|
|
552
|
+
* // "lock_lp_sol_amount": 1880793407, // LP locked SOL (lamports)
|
|
553
|
+
* // "lock_lp_token_amount": 75677963031921, // LP locked token (min unit)
|
|
554
|
+
* // "next_lp_sol_amount": 0, // Next LP SOL amount
|
|
555
|
+
* // "next_lp_token_amount": 0, // Next LP token amount
|
|
556
|
+
* // "margin_init_sol_amount": 418211213, // Initial margin
|
|
557
|
+
* // "margin_sol_amount": 418211213, // Current margin
|
|
558
|
+
* // "borrow_amount": 2276435100, // Borrow amount
|
|
559
|
+
* // "position_asset_amount": 75677963031921, // Position asset amount
|
|
560
|
+
* // "realized_sol_amount": 0, // Realized SOL amount
|
|
561
|
+
* // "start_time": 1764047379, // Start timestamp
|
|
562
|
+
* // "end_time": 1764652179, // End timestamp
|
|
563
|
+
* // "next_order": 1, // Next order index (65535=none)
|
|
564
|
+
* // "prev_order": 65535, // Previous order index (65535=none)
|
|
565
|
+
* // "borrow_fee": 1200, // Borrow fee (basis points)
|
|
566
|
+
* // "index": 0 // Order index in OrderBook
|
|
567
|
+
* // }
|
|
568
|
+
* // ],
|
|
569
|
+
* // "total": 37, // Total order count
|
|
570
|
+
* // "user": "7621yjkZJ1jxBHw3oCPoazFfMx82NWBSARk2AGV7EBig", // User address
|
|
571
|
+
* // "mint_account": "T3NFPvYvpULCTgrhHb4b4Sj5J1qtSNvyKZfE8hCvuKM", // Token address (null if not specified)
|
|
572
|
+
* // "page": 1, // Current page
|
|
573
|
+
* // "limit": 20, // Items per page
|
|
574
|
+
* // "has_next": true, // Has next page
|
|
575
|
+
* // "has_prev": false // Has previous page
|
|
576
|
+
* // },
|
|
577
|
+
* // "message": "Operation successful"
|
|
578
|
+
* // }
|
|
579
|
+
*
|
|
580
|
+
* // Use order data:
|
|
581
|
+
* const orders = userOrders.data.orders; // Order array
|
|
582
|
+
* const totalCount = userOrders.data.total; // Total count
|
|
583
|
+
*/
|
|
584
|
+
async user_orders(user, mint, options = {}) {
|
|
585
|
+
this._ensureConfigured();
|
|
586
|
+
|
|
587
|
+
try {
|
|
588
|
+
// Parameter validation
|
|
589
|
+
if (!user || typeof user !== 'string') {
|
|
590
|
+
throw new Error('user_orders: user address must be a valid string');
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Extract parameters
|
|
594
|
+
const page = options.page || 1;
|
|
595
|
+
const pageSize = options.limit || 20;
|
|
596
|
+
const direction = options.direction; // Optional: 'up', 'dn', or undefined
|
|
597
|
+
const orderBy = options.order_by || 'start_time_desc';
|
|
598
|
+
|
|
599
|
+
// Build API path: /api/orderbook/user/{user}/active
|
|
600
|
+
const url = `/api/orderbook/user/${user}/active`;
|
|
601
|
+
|
|
602
|
+
// Build query parameters
|
|
603
|
+
const queryParams = {
|
|
604
|
+
page: page,
|
|
605
|
+
page_size: pageSize
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
// Optional parameter: mint (if provided and valid)
|
|
609
|
+
if (mint && typeof mint === 'string') {
|
|
610
|
+
queryParams.mint = mint;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// Optional parameter: direction
|
|
614
|
+
if (direction) {
|
|
615
|
+
if (!['up', 'dn'].includes(direction)) {
|
|
616
|
+
throw new Error('user_orders: direction must be "up" or "dn"');
|
|
617
|
+
}
|
|
618
|
+
queryParams.direction = direction;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// Make API request
|
|
622
|
+
const requestConfig = {
|
|
623
|
+
method: 'GET',
|
|
624
|
+
url: url,
|
|
625
|
+
params: queryParams
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const response = await this.httpClient.request(requestConfig);
|
|
629
|
+
|
|
630
|
+
// Check new API response format
|
|
631
|
+
const isNewFormat = response.data && typeof response.data.code === 'number';
|
|
632
|
+
|
|
633
|
+
if (!isNewFormat) {
|
|
634
|
+
throw new Error('API返回格式无法识别');
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
if (response.data.code !== 200) {
|
|
638
|
+
throw new Error(`API请求失败: ${response.data.msg || '未知错误'}`);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Transform to chain.js compatible format
|
|
642
|
+
const apiData = response.data.data;
|
|
643
|
+
|
|
644
|
+
// Process orders: add missing fields for chain.js compatibility
|
|
645
|
+
let processedOrders = apiData.orders.map(order => ({
|
|
646
|
+
// Keep order_type as number (1=long, 2=short) - compatible with chain.js
|
|
647
|
+
order_type: order.order_type,
|
|
648
|
+
|
|
649
|
+
// Token and user addresses
|
|
650
|
+
mint: order.mint,
|
|
651
|
+
user: order.user,
|
|
652
|
+
|
|
653
|
+
// Price fields (ensure strings)
|
|
654
|
+
lock_lp_start_price: String(order.lock_lp_start_price),
|
|
655
|
+
lock_lp_end_price: String(order.lock_lp_end_price),
|
|
656
|
+
open_price: String(order.open_price),
|
|
657
|
+
|
|
658
|
+
// Order ID (ensure string)
|
|
659
|
+
order_id: order.order_id != null ? String(order.order_id) : undefined,
|
|
660
|
+
|
|
661
|
+
// Amount fields (ensure strings) - Fix precision issue
|
|
662
|
+
lock_lp_sol_amount: String(order.lock_lp_sol_amount),
|
|
663
|
+
lock_lp_token_amount: String(order.lock_lp_token_amount),
|
|
664
|
+
next_lp_sol_amount: String(order.next_lp_sol_amount),
|
|
665
|
+
next_lp_token_amount: String(order.next_lp_token_amount),
|
|
666
|
+
|
|
667
|
+
// Margin fields (ensure strings) - Fix precision issue
|
|
668
|
+
margin_init_sol_amount: String(order.margin_init_sol_amount),
|
|
669
|
+
margin_sol_amount: String(order.margin_sol_amount),
|
|
670
|
+
|
|
671
|
+
// Position fields (ensure strings) - Fix precision issue
|
|
672
|
+
borrow_amount: String(order.borrow_amount),
|
|
673
|
+
position_asset_amount: String(order.position_asset_amount),
|
|
674
|
+
realized_sol_amount: String(order.realized_sol_amount),
|
|
675
|
+
|
|
676
|
+
// Time fields
|
|
677
|
+
start_time: order.start_time,
|
|
678
|
+
end_time: order.end_time,
|
|
679
|
+
|
|
680
|
+
// Linked list navigation fields (add from API response)
|
|
681
|
+
next_order: order.next_order,
|
|
682
|
+
prev_order: order.prev_order,
|
|
683
|
+
|
|
684
|
+
// Fee field
|
|
685
|
+
borrow_fee: order.borrow_fee,
|
|
686
|
+
|
|
687
|
+
// Index field (add from API response)
|
|
688
|
+
index: order.index
|
|
689
|
+
}));
|
|
690
|
+
|
|
691
|
+
// Client-side sorting (API doesn't support sorting)
|
|
692
|
+
if (orderBy === 'start_time_desc') {
|
|
693
|
+
processedOrders.sort((a, b) => b.start_time - a.start_time);
|
|
694
|
+
} else if (orderBy === 'start_time_asc') {
|
|
695
|
+
processedOrders.sort((a, b) => a.start_time - b.start_time);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// Calculate pagination info
|
|
699
|
+
const totalPages = Math.ceil(apiData.total / apiData.page_size);
|
|
700
|
+
|
|
701
|
+
// Return in chain.js compatible format
|
|
702
|
+
return {
|
|
703
|
+
success: true,
|
|
704
|
+
data: {
|
|
705
|
+
orders: processedOrders,
|
|
706
|
+
total: apiData.total,
|
|
707
|
+
user: user,
|
|
708
|
+
mint_account: mint || null, // If mint not specified, return null
|
|
709
|
+
page: apiData.page,
|
|
710
|
+
limit: apiData.page_size,
|
|
711
|
+
has_next: apiData.page < totalPages,
|
|
712
|
+
has_prev: apiData.page > 1
|
|
713
|
+
},
|
|
714
|
+
message: "Operation successful"
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
} catch (error) {
|
|
718
|
+
if (error.response) {
|
|
719
|
+
const errorMsg = error.response.data?.msg || error.response.data?.message || error.message;
|
|
720
|
+
throw new Error(`API请求失败 [${error.response.status}]: ${errorMsg}`);
|
|
721
|
+
} else if (error.request) {
|
|
722
|
+
throw new Error(`网络请求失败: 无法连接到 ${this.baseUrl}`);
|
|
723
|
+
} else {
|
|
724
|
+
throw new Error(`请求处理失败: ${error.message}`);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
module.exports = FastModule;
|