100x-sdk 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,370 @@
1
+ const anchor = require('@coral-xyz/anchor');
2
+ const { PublicKey, SystemProgram, SYSVAR_RENT_PUBKEY, Transaction } = require('@solana/web3.js');
3
+ const { getAssociatedTokenAddress, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID } = require('@solana/spl-token');
4
+ // 统一使用 buffer 包,所有平台一致
5
+ const { Buffer } = require('buffer');
6
+
7
+ /**
8
+ * Tools Module
9
+ * Provides trading utility functions: approve trade and close cooldown PDA
10
+ */
11
+ class ToolsModule {
12
+ /**
13
+ * Constructor
14
+ * @param {Fun100xSdk} sdk - SDK instance
15
+ */
16
+ constructor(sdk) {
17
+ this.sdk = sdk;
18
+ }
19
+
20
+ /**
21
+ * Approve current token balance for trading
22
+ *
23
+ * Use Cases:
24
+ * 1. After receiving tokens from another address and wanting to trade immediately
25
+ * 2. Reactivating cooldown PDA
26
+ *
27
+ * @param {Object} params - Parameters
28
+ * @param {PublicKey|string} params.mint - Token mint address
29
+ * @param {Keypair} params.wallet - User wallet for signing
30
+ * @returns {Promise<Object>} Transaction object and related account info
31
+ *
32
+ * @example
33
+ * const result = await sdk.tools.approveTrade({
34
+ * mint: 'xxxxx',
35
+ * wallet: userKeypair
36
+ * });
37
+ *
38
+ * const signature = await this.sdk.connection.sendTransaction(
39
+ * result.transaction,
40
+ * [wallet]
41
+ * );
42
+ */
43
+ async approveTrade(params) {
44
+ const { mint, wallet } = params;
45
+
46
+ // Validate parameters
47
+ if (!mint) {
48
+ throw new Error('mint parameter is required');
49
+ }
50
+ if (!wallet) {
51
+ throw new Error('wallet parameter is required');
52
+ }
53
+
54
+ // Convert mint to PublicKey
55
+ const mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
56
+ const walletPubkey = wallet.publicKey;
57
+
58
+ // Calculate user token account
59
+ const userTokenAccount = await getAssociatedTokenAddress(
60
+ mintPubkey,
61
+ walletPubkey
62
+ );
63
+
64
+ // Calculate trade cooldown PDA
65
+ const [cooldown] = PublicKey.findProgramAddressSync(
66
+ [
67
+ Buffer.from('trade_cooldown'),
68
+ mintPubkey.toBuffer(),
69
+ walletPubkey.toBuffer()
70
+ ],
71
+ this.sdk.programId
72
+ );
73
+
74
+ // Build instruction
75
+ const approveTradeIx = await this.sdk.program.methods
76
+ .approveTrade()
77
+ .accounts({
78
+ payer: walletPubkey,
79
+ mintAccount: mintPubkey,
80
+ userTokenAccount: userTokenAccount,
81
+ cooldown: cooldown,
82
+ tokenProgram: TOKEN_PROGRAM_ID,
83
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
84
+ systemProgram: SystemProgram.programId,
85
+ rent: SYSVAR_RENT_PUBKEY,
86
+ })
87
+ .instruction();
88
+
89
+ // Build transaction
90
+ const transaction = new Transaction().add(approveTradeIx);
91
+
92
+ // Return result
93
+ return {
94
+ transaction,
95
+ signers: [],
96
+ accounts: {
97
+ payer: walletPubkey,
98
+ mintAccount: mintPubkey,
99
+ userTokenAccount: userTokenAccount,
100
+ cooldown: cooldown,
101
+ }
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Manually close TradeCooldown PDA and reclaim rent
107
+ *
108
+ * Conditions:
109
+ * 1. Can only close your own PDA (verified through seeds)
110
+ *
111
+ * Use Cases:
112
+ * - User wants to reclaim rent
113
+ * - Clean up unused PDAs
114
+ * - Admin batch cleanup of expired PDAs
115
+ *
116
+ * Notes:
117
+ * - No need to verify token balance, can be recreated via approve_trade after closing
118
+ * - After PDA is closed, next buy or approve will automatically recreate it
119
+ *
120
+ * @param {Object} params - Parameters
121
+ * @param {PublicKey|string} params.mint - Token mint address
122
+ * @param {Keypair} params.wallet - User wallet for signing
123
+ * @returns {Promise<Object>} Transaction object and related account info
124
+ *
125
+ * @example
126
+ * const result = await sdk.tools.closeTradeCooldown({
127
+ * mint: 'xxxxx',
128
+ * wallet: userKeypair
129
+ * });
130
+ *
131
+ * const signature = await this.sdk.connection.sendTransaction(
132
+ * result.transaction,
133
+ * [wallet]
134
+ * );
135
+ */
136
+ async closeTradeCooldown(params) {
137
+ const { mint, wallet } = params;
138
+
139
+ // Validate parameters
140
+ if (!mint) {
141
+ throw new Error('mint parameter is required');
142
+ }
143
+ if (!wallet) {
144
+ throw new Error('wallet parameter is required');
145
+ }
146
+
147
+ // Convert mint to PublicKey
148
+ const mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
149
+ const walletPubkey = wallet.publicKey;
150
+
151
+ // Calculate user token account
152
+ const userTokenAccount = await getAssociatedTokenAddress(
153
+ mintPubkey,
154
+ walletPubkey
155
+ );
156
+
157
+ // Calculate trade cooldown PDA
158
+ const [cooldown] = PublicKey.findProgramAddressSync(
159
+ [
160
+ Buffer.from('trade_cooldown'),
161
+ mintPubkey.toBuffer(),
162
+ walletPubkey.toBuffer()
163
+ ],
164
+ this.sdk.programId
165
+ );
166
+
167
+ // Get cooldown account to fetch bump
168
+ // Use manual fetch method to avoid provider issues
169
+ const cooldownAccountInfo = await this.sdk.connection.getAccountInfo(cooldown);
170
+ if (!cooldownAccountInfo) {
171
+ throw new Error(`Cooldown PDA does not exist for mint: ${mint} and wallet: ${wallet.publicKey.toString()}`);
172
+ }
173
+
174
+ const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
175
+ let cooldownAccountData;
176
+ try {
177
+ cooldownAccountData = accountsCoder.decode('TradeCooldown', cooldownAccountInfo.data);
178
+ } catch (e1) {
179
+ try {
180
+ cooldownAccountData = accountsCoder.decode('tradeCooldown', cooldownAccountInfo.data);
181
+ } catch (e2) {
182
+ throw new Error(`Cannot decode cooldown account: ${e1.message}`);
183
+ }
184
+ }
185
+
186
+ // Build instruction
187
+ const closeTradeCooldownIx = await this.sdk.program.methods
188
+ .closeTradeCooldown()
189
+ .accounts({
190
+ payer: walletPubkey,
191
+ mintAccount: mintPubkey,
192
+ userTokenAccount: userTokenAccount,
193
+ cooldown: cooldown,
194
+ tokenProgram: TOKEN_PROGRAM_ID,
195
+ associatedTokenProgram: ASSOCIATED_TOKEN_PROGRAM_ID,
196
+ systemProgram: SystemProgram.programId,
197
+ })
198
+ .instruction();
199
+
200
+ // Build transaction
201
+ const transaction = new Transaction().add(closeTradeCooldownIx);
202
+
203
+ // Return result
204
+ return {
205
+ transaction,
206
+ signers: [],
207
+ accounts: {
208
+ payer: walletPubkey,
209
+ mintAccount: mintPubkey,
210
+ userTokenAccount: userTokenAccount,
211
+ cooldown: cooldown,
212
+ },
213
+ cooldownInfo: {
214
+ lastTradeTime: cooldownAccountData.lastTradeTime,
215
+ approvalTokenAmount: cooldownAccountData.approvalTokenAmount,
216
+ bump: cooldownAccountData.bump,
217
+ }
218
+ };
219
+ }
220
+
221
+ /**
222
+ * Validate cooldown PDA approval_token_amount matches user's current token balance
223
+ *
224
+ * Use Cases:
225
+ * - Before trading, verify if the cooldown PDA is in sync with user's token balance
226
+ * - Check if user needs to call approveTrade after receiving tokens
227
+ * - Validate cooldown state for security checks
228
+ *
229
+ * @param {Object} params - Parameters
230
+ * @param {PublicKey|string} params.mint - Token mint address
231
+ * @param {Keypair|Object} params.wallet - User wallet (can be Keypair or object with publicKey)
232
+ * @param {anchor.BN|number|string} [params.tokenBalance] - Optional: user's current token balance, if not provided will fetch from chain
233
+ * @returns {Promise<Object>} Validation result with detailed info
234
+ *
235
+ * @example
236
+ * // Auto-fetch token balance from chain
237
+ * const result = await sdk.tools.validateCooldown({
238
+ * mint: 'xxxxx',
239
+ * wallet: userKeypair
240
+ * });
241
+ *
242
+ * // Provide token balance manually
243
+ * const result = await sdk.tools.validateCooldown({
244
+ * mint: 'xxxxx',
245
+ * wallet: userKeypair,
246
+ * tokenBalance: new anchor.BN('1000000')
247
+ * });
248
+ *
249
+ * console.log(result.isValid); // true/false
250
+ * console.log(result.cooldownInfo.approvalTokenAmount);
251
+ * console.log(result.tokenBalance);
252
+ */
253
+ async validateCooldown(params) {
254
+ const { mint, wallet, tokenBalance } = params;
255
+
256
+ // Validate parameters
257
+ if (!mint) {
258
+ throw new Error('mint parameter is required');
259
+ }
260
+ if (!wallet) {
261
+ throw new Error('wallet parameter is required');
262
+ }
263
+
264
+ // Convert mint to PublicKey
265
+ const mintPubkey = typeof mint === 'string' ? new PublicKey(mint) : mint;
266
+ const walletPubkey = wallet.publicKey || wallet;
267
+
268
+ // Calculate user token account
269
+ const userTokenAccount = await getAssociatedTokenAddress(
270
+ mintPubkey,
271
+ walletPubkey
272
+ );
273
+
274
+ // Calculate trade cooldown PDA
275
+ const [cooldown] = PublicKey.findProgramAddressSync(
276
+ [
277
+ Buffer.from('trade_cooldown'),
278
+ mintPubkey.toBuffer(),
279
+ walletPubkey.toBuffer()
280
+ ],
281
+ this.sdk.programId
282
+ );
283
+
284
+ // Get cooldown account data
285
+ const cooldownAccountInfo = await this.sdk.connection.getAccountInfo(cooldown);
286
+ if (!cooldownAccountInfo) {
287
+ // Cooldown PDA does not exist, return status without throwing error
288
+ return {
289
+ isValid: false,
290
+ exists: false,
291
+ reason: 'COOLDOWN_NOT_EXISTS',
292
+ message: 'Cooldown PDA does not exist. User has never traded this token or needs to call approveTrade first.',
293
+ cooldownInfo: null,
294
+ tokenBalance: null,
295
+ accounts: {
296
+ mintAccount: mintPubkey,
297
+ userTokenAccount: userTokenAccount,
298
+ cooldown: cooldown,
299
+ wallet: walletPubkey,
300
+ }
301
+ };
302
+ }
303
+
304
+ const accountsCoder = new anchor.BorshAccountsCoder(this.sdk.program.idl);
305
+ let cooldownAccountData;
306
+ try {
307
+ cooldownAccountData = accountsCoder.decode('TradeCooldown', cooldownAccountInfo.data);
308
+ } catch (e1) {
309
+ try {
310
+ cooldownAccountData = accountsCoder.decode('tradeCooldown', cooldownAccountInfo.data);
311
+ } catch (e2) {
312
+ throw new Error(`Cannot decode cooldown account: ${e1.message}`);
313
+ }
314
+ }
315
+
316
+ // Get user's current token balance
317
+ let currentTokenBalance;
318
+ if (tokenBalance !== undefined && tokenBalance !== null) {
319
+ // Use provided token balance
320
+ if (anchor.BN.isBN(tokenBalance)) {
321
+ currentTokenBalance = tokenBalance;
322
+ } else {
323
+ currentTokenBalance = new anchor.BN(tokenBalance.toString());
324
+ }
325
+ } else {
326
+ // Fetch token balance from chain
327
+ const userTokenAccountInfo = await this.sdk.connection.getAccountInfo(userTokenAccount);
328
+ if (!userTokenAccountInfo) {
329
+ throw new Error(`User token account does not exist for mint: ${mint} and wallet: ${walletPubkey.toString()}`);
330
+ }
331
+
332
+ try {
333
+ // Fetch SPL token account balance
334
+ const tokenAccountInfo = await this.sdk.connection.getTokenAccountBalance(userTokenAccount);
335
+ currentTokenBalance = new anchor.BN(tokenAccountInfo.value.amount);
336
+ } catch (e) {
337
+ throw new Error(`Cannot fetch token balance: ${e.message}`);
338
+ }
339
+ }
340
+
341
+ // Compare approval_token_amount with current token balance
342
+ // Valid if approval_token_amount >= current token balance
343
+ const approvalTokenAmount = new anchor.BN(cooldownAccountData.approvalTokenAmount.toString());
344
+ const isValid = approvalTokenAmount.gte(currentTokenBalance);
345
+
346
+ // Return result
347
+ return {
348
+ isValid,
349
+ exists: true,
350
+ reason: isValid ? 'VALID' : 'AMOUNT_MISMATCH',
351
+ message: isValid
352
+ ? 'Cooldown validation passed. approval_token_amount >= token_balance'
353
+ : 'Cooldown validation failed. approval_token_amount < token_balance. User needs to call approveTrade.',
354
+ cooldownInfo: {
355
+ approvalTokenAmount: cooldownAccountData.approvalTokenAmount,
356
+ lastTradeTime: cooldownAccountData.lastTradeTime,
357
+ bump: cooldownAccountData.bump,
358
+ },
359
+ tokenBalance: currentTokenBalance,
360
+ accounts: {
361
+ mintAccount: mintPubkey,
362
+ userTokenAccount: userTokenAccount,
363
+ cooldown: cooldown,
364
+ wallet: walletPubkey,
365
+ }
366
+ };
367
+ }
368
+ }
369
+
370
+ module.exports = ToolsModule;