torchsdk 3.5.0 → 3.5.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,994 @@
1
+ "use strict";
2
+ /**
3
+ * Transaction builders
4
+ *
5
+ * Build unsigned transactions for buy, sell, create, star, vault, and lending.
6
+ * Agents sign these locally and submit to the network.
7
+ */
8
+ var __importDefault = (this && this.__importDefault) || function (mod) {
9
+ return (mod && mod.__esModule) ? mod : { "default": mod };
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.buildVaultSwapTransaction = exports.buildWithdrawTokensTransaction = exports.buildClaimProtocolRewardsTransaction = exports.buildLiquidateTransaction = exports.buildRepayTransaction = exports.buildBorrowTransaction = exports.buildTransferAuthorityTransaction = exports.buildUnlinkWalletTransaction = exports.buildLinkWalletTransaction = exports.buildWithdrawVaultTransaction = exports.buildDepositVaultTransaction = exports.buildCreateVaultTransaction = exports.buildStarTransaction = exports.buildCreateTokenTransaction = exports.buildSellTransaction = exports.buildDirectBuyTransaction = exports.buildBuyTransaction = void 0;
13
+ const web3_js_1 = require("@solana/web3.js");
14
+ const spl_token_1 = require("@solana/spl-token");
15
+ const anchor_1 = require("@coral-xyz/anchor");
16
+ const program_1 = require("./program");
17
+ const constants_1 = require("./constants");
18
+ const tokens_1 = require("./tokens");
19
+ const torch_market_json_1 = __importDefault(require("./torch_market.json"));
20
+ // ============================================================================
21
+ // Helpers
22
+ // ============================================================================
23
+ const makeDummyProvider = (connection, payer) => {
24
+ const dummyWallet = {
25
+ publicKey: payer,
26
+ signTransaction: async (t) => t,
27
+ signAllTransactions: async (t) => t,
28
+ };
29
+ return new anchor_1.AnchorProvider(connection, dummyWallet, {});
30
+ };
31
+ const finalizeTransaction = async (connection, tx, feePayer) => {
32
+ const { blockhash } = await connection.getLatestBlockhash();
33
+ tx.recentBlockhash = blockhash;
34
+ tx.feePayer = feePayer;
35
+ };
36
+ // ============================================================================
37
+ // Buy
38
+ // ============================================================================
39
+ // Internal buy builder shared by both vault and direct variants
40
+ const buildBuyTransactionInternal = async (connection, mintStr, buyerStr, amount_sol, slippage_bps, vote, message, vaultCreatorStr) => {
41
+ const mint = new web3_js_1.PublicKey(mintStr);
42
+ const buyer = new web3_js_1.PublicKey(buyerStr);
43
+ const tokenData = await (0, tokens_1.fetchTokenRaw)(connection, mint);
44
+ if (!tokenData)
45
+ throw new Error(`Token not found: ${mintStr}`);
46
+ const { bondingCurve } = tokenData;
47
+ if (bondingCurve.bonding_complete)
48
+ throw new Error('Bonding curve complete, trade on DEX');
49
+ // Calculate expected output
50
+ const virtualSol = BigInt(bondingCurve.virtual_sol_reserves.toString());
51
+ const virtualTokens = BigInt(bondingCurve.virtual_token_reserves.toString());
52
+ const realSol = BigInt(bondingCurve.real_sol_reserves.toString());
53
+ const bondingTarget = BigInt(bondingCurve.bonding_target.toString());
54
+ const solAmount = BigInt(amount_sol);
55
+ const result = (0, program_1.calculateTokensOut)(solAmount, virtualSol, virtualTokens, realSol, 100, 100, bondingTarget);
56
+ // Apply slippage
57
+ if (slippage_bps < 10 || slippage_bps > 1000) {
58
+ throw new Error(`slippage_bps must be between 10 (0.1%) and 1000 (10%), got ${slippage_bps}`);
59
+ }
60
+ const slippage = slippage_bps;
61
+ const minTokens = (result.tokensToUser * BigInt(10000 - slippage)) / BigInt(10000);
62
+ // Derive PDAs
63
+ const [bondingCurvePda] = (0, program_1.getBondingCurvePda)(mint);
64
+ const [treasuryPda] = (0, program_1.getTokenTreasuryPda)(mint);
65
+ const [userPositionPda] = (0, program_1.getUserPositionPda)(bondingCurvePda, buyer);
66
+ const [userStatsPda] = (0, program_1.getUserStatsPda)(buyer);
67
+ const [globalConfigPda] = (0, program_1.getGlobalConfigPda)();
68
+ const [protocolTreasuryPda] = (0, program_1.getProtocolTreasuryPda)();
69
+ const bondingCurveTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurvePda, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
70
+ const treasuryTokenAccount = (0, program_1.getTreasuryTokenAccount)(mint, treasuryPda);
71
+ const buyerTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, buyer, false, spl_token_1.TOKEN_2022_PROGRAM_ID);
72
+ const tx = new web3_js_1.Transaction();
73
+ // Create buyer ATA if needed
74
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(buyer, buyerTokenAccount, buyer, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
75
+ const provider = makeDummyProvider(connection, buyer);
76
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
77
+ // Fetch global config for dev wallet
78
+ const globalConfigAccount = (await program.account.globalConfig.fetch(globalConfigPda));
79
+ // Vault accounts (optional — pass null when not using vault)
80
+ let torchVaultAccount = null;
81
+ let vaultWalletLinkAccount = null;
82
+ let vaultTokenAccount = null;
83
+ if (vaultCreatorStr) {
84
+ const vaultCreator = new web3_js_1.PublicKey(vaultCreatorStr);
85
+ [torchVaultAccount] = (0, program_1.getTorchVaultPda)(vaultCreator);
86
+ [vaultWalletLinkAccount] = (0, program_1.getVaultWalletLinkPda)(buyer);
87
+ // [V18] Tokens go to vault ATA instead of buyer's wallet
88
+ vaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, torchVaultAccount, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
89
+ // Create vault ATA if needed (vault PDA owns it)
90
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(buyer, vaultTokenAccount, torchVaultAccount, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
91
+ }
92
+ const buyIx = await program.methods
93
+ .buy({
94
+ solAmount: new anchor_1.BN(amount_sol.toString()),
95
+ minTokensOut: new anchor_1.BN(minTokens.toString()),
96
+ vote: vote === 'return' ? true : vote === 'burn' ? false : null,
97
+ })
98
+ .accounts({
99
+ buyer,
100
+ globalConfig: globalConfigPda,
101
+ devWallet: globalConfigAccount.devWallet || globalConfigAccount.dev_wallet,
102
+ protocolTreasury: protocolTreasuryPda,
103
+ mint,
104
+ bondingCurve: bondingCurvePda,
105
+ tokenVault: bondingCurveTokenAccount,
106
+ tokenTreasury: treasuryPda,
107
+ treasuryTokenAccount,
108
+ buyerTokenAccount,
109
+ userPosition: userPositionPda,
110
+ userStats: userStatsPda,
111
+ torchVault: torchVaultAccount,
112
+ vaultWalletLink: vaultWalletLinkAccount,
113
+ vaultTokenAccount,
114
+ tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID,
115
+ associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
116
+ systemProgram: web3_js_1.SystemProgram.programId,
117
+ })
118
+ .instruction();
119
+ tx.add(buyIx);
120
+ // Bundle optional message as SPL Memo instruction
121
+ if (message && message.trim().length > 0) {
122
+ if (message.length > MAX_MESSAGE_LENGTH) {
123
+ throw new Error(`Message must be ${MAX_MESSAGE_LENGTH} characters or less`);
124
+ }
125
+ const memoIx = new web3_js_1.TransactionInstruction({
126
+ programId: constants_1.MEMO_PROGRAM_ID,
127
+ keys: [{ pubkey: buyer, isSigner: true, isWritable: false }],
128
+ data: Buffer.from(message.trim(), 'utf-8'),
129
+ });
130
+ tx.add(memoIx);
131
+ }
132
+ await finalizeTransaction(connection, tx, buyer);
133
+ const vaultLabel = vaultCreatorStr ? ' (via vault)' : '';
134
+ return {
135
+ transaction: tx,
136
+ message: `Buy ${Number(result.tokensToUser) / 1e6} tokens for ${Number(solAmount) / 1e9} SOL${vaultLabel}`,
137
+ };
138
+ };
139
+ /**
140
+ * Build an unsigned vault-funded buy transaction.
141
+ *
142
+ * The vault pays for the buy. This is the recommended path for AI agents.
143
+ *
144
+ * @param connection - Solana RPC connection
145
+ * @param params - Buy parameters with required vault creator pubkey
146
+ * @returns Unsigned transaction and descriptive message
147
+ */
148
+ const buildBuyTransaction = async (connection, params) => {
149
+ const { mint, buyer, amount_sol, slippage_bps = 100, vote, message, vault } = params;
150
+ return buildBuyTransactionInternal(connection, mint, buyer, amount_sol, slippage_bps, vote, message, vault);
151
+ };
152
+ exports.buildBuyTransaction = buildBuyTransaction;
153
+ /**
154
+ * Build an unsigned direct buy transaction (no vault).
155
+ *
156
+ * The buyer pays from their own wallet. Use this for human-operated wallets only.
157
+ * For AI agents, use buildBuyTransaction with a vault instead.
158
+ *
159
+ * @param connection - Solana RPC connection
160
+ * @param params - Buy parameters (no vault)
161
+ * @returns Unsigned transaction and descriptive message
162
+ */
163
+ const buildDirectBuyTransaction = async (connection, params) => {
164
+ const { mint, buyer, amount_sol, slippage_bps = 100, vote, message } = params;
165
+ return buildBuyTransactionInternal(connection, mint, buyer, amount_sol, slippage_bps, vote, message, undefined);
166
+ };
167
+ exports.buildDirectBuyTransaction = buildDirectBuyTransaction;
168
+ // ============================================================================
169
+ // Sell
170
+ // ============================================================================
171
+ /**
172
+ * Build an unsigned sell transaction.
173
+ *
174
+ * @param connection - Solana RPC connection
175
+ * @param params - Sell parameters (mint, seller, amount_tokens in raw units, optional slippage_bps)
176
+ * @returns Unsigned transaction and descriptive message
177
+ */
178
+ const buildSellTransaction = async (connection, params) => {
179
+ const { mint: mintStr, seller: sellerStr, amount_tokens, slippage_bps = 100, message, vault: vaultCreatorStr } = params;
180
+ const mint = new web3_js_1.PublicKey(mintStr);
181
+ const seller = new web3_js_1.PublicKey(sellerStr);
182
+ const tokenData = await (0, tokens_1.fetchTokenRaw)(connection, mint);
183
+ if (!tokenData)
184
+ throw new Error(`Token not found: ${mintStr}`);
185
+ const { bondingCurve } = tokenData;
186
+ if (bondingCurve.bonding_complete)
187
+ throw new Error('Bonding curve complete, trade on DEX');
188
+ // Calculate expected output
189
+ const virtualSol = BigInt(bondingCurve.virtual_sol_reserves.toString());
190
+ const virtualTokens = BigInt(bondingCurve.virtual_token_reserves.toString());
191
+ const tokenAmount = BigInt(amount_tokens);
192
+ const result = (0, program_1.calculateSolOut)(tokenAmount, virtualSol, virtualTokens);
193
+ // Apply slippage
194
+ if (slippage_bps < 10 || slippage_bps > 1000) {
195
+ throw new Error(`slippage_bps must be between 10 (0.1%) and 1000 (10%), got ${slippage_bps}`);
196
+ }
197
+ const slippage = slippage_bps;
198
+ const minSol = (result.solToUser * BigInt(10000 - slippage)) / BigInt(10000);
199
+ // Derive PDAs
200
+ const [bondingCurvePda] = (0, program_1.getBondingCurvePda)(mint);
201
+ const [treasuryPda] = (0, program_1.getTokenTreasuryPda)(mint);
202
+ const [userPositionPda] = (0, program_1.getUserPositionPda)(bondingCurvePda, seller);
203
+ const [userStatsPda] = (0, program_1.getUserStatsPda)(seller);
204
+ const bondingCurveTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, bondingCurvePda, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
205
+ const sellerTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, seller, false, spl_token_1.TOKEN_2022_PROGRAM_ID);
206
+ // [V18] Vault accounts (optional — pass null when not using vault)
207
+ let torchVaultAccount = null;
208
+ let vaultWalletLinkAccount = null;
209
+ let vaultTokenAccount = null;
210
+ if (vaultCreatorStr) {
211
+ const vaultCreator = new web3_js_1.PublicKey(vaultCreatorStr);
212
+ [torchVaultAccount] = (0, program_1.getTorchVaultPda)(vaultCreator);
213
+ [vaultWalletLinkAccount] = (0, program_1.getVaultWalletLinkPda)(seller);
214
+ vaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, torchVaultAccount, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
215
+ }
216
+ const tx = new web3_js_1.Transaction();
217
+ // Create vault ATA if needed (idempotent — safe for first vault sell on a mint)
218
+ if (vaultTokenAccount && torchVaultAccount) {
219
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(seller, vaultTokenAccount, torchVaultAccount, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
220
+ }
221
+ const provider = makeDummyProvider(connection, seller);
222
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
223
+ const sellIx = await program.methods
224
+ .sell({
225
+ tokenAmount: new anchor_1.BN(amount_tokens.toString()),
226
+ minSolOut: new anchor_1.BN(minSol.toString()),
227
+ })
228
+ .accounts({
229
+ seller,
230
+ mint,
231
+ bondingCurve: bondingCurvePda,
232
+ tokenVault: bondingCurveTokenAccount,
233
+ sellerTokenAccount,
234
+ userPosition: userPositionPda,
235
+ tokenTreasury: treasuryPda,
236
+ userStats: userStatsPda,
237
+ torchVault: torchVaultAccount,
238
+ vaultWalletLink: vaultWalletLinkAccount,
239
+ vaultTokenAccount,
240
+ tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID,
241
+ systemProgram: web3_js_1.SystemProgram.programId,
242
+ })
243
+ .instruction();
244
+ tx.add(sellIx);
245
+ // Bundle optional message as SPL Memo instruction
246
+ if (message && message.trim().length > 0) {
247
+ if (message.length > MAX_MESSAGE_LENGTH) {
248
+ throw new Error(`Message must be ${MAX_MESSAGE_LENGTH} characters or less`);
249
+ }
250
+ const memoIx = new web3_js_1.TransactionInstruction({
251
+ programId: constants_1.MEMO_PROGRAM_ID,
252
+ keys: [{ pubkey: seller, isSigner: true, isWritable: false }],
253
+ data: Buffer.from(message.trim(), 'utf-8'),
254
+ });
255
+ tx.add(memoIx);
256
+ }
257
+ await finalizeTransaction(connection, tx, seller);
258
+ const vaultLabel = vaultCreatorStr ? ' (via vault)' : '';
259
+ return {
260
+ transaction: tx,
261
+ message: `Sell ${Number(tokenAmount) / 1e6} tokens for ${Number(result.solToUser) / 1e9} SOL${vaultLabel}`,
262
+ };
263
+ };
264
+ exports.buildSellTransaction = buildSellTransaction;
265
+ // ============================================================================
266
+ // Create Token
267
+ // ============================================================================
268
+ /**
269
+ * Build an unsigned create token transaction.
270
+ *
271
+ * Returns the transaction (partially signed by the mint keypair) and the mint keypair
272
+ * so the agent can extract the mint address.
273
+ *
274
+ * @param connection - Solana RPC connection
275
+ * @param params - Create parameters (creator, name, symbol, metadata_uri)
276
+ * @returns Partially-signed transaction, mint PublicKey, and mint Keypair
277
+ */
278
+ const buildCreateTokenTransaction = async (connection, params) => {
279
+ const { creator: creatorStr, name, symbol, metadata_uri, sol_target = 0 } = params;
280
+ const creator = new web3_js_1.PublicKey(creatorStr);
281
+ if (name.length > 32)
282
+ throw new Error('Name must be 32 characters or less');
283
+ if (symbol.length > 10)
284
+ throw new Error('Symbol must be 10 characters or less');
285
+ // Grind for vanity "tm" suffix
286
+ let mint;
287
+ const maxAttempts = 500000;
288
+ let attempts = 0;
289
+ while (true) {
290
+ mint = web3_js_1.Keypair.generate();
291
+ attempts++;
292
+ if (mint.publicKey.toBase58().endsWith('tm'))
293
+ break;
294
+ if (attempts >= maxAttempts)
295
+ break;
296
+ }
297
+ // Derive PDAs
298
+ const [globalConfig] = (0, program_1.getGlobalConfigPda)();
299
+ const [bondingCurve] = (0, program_1.getBondingCurvePda)(mint.publicKey);
300
+ const [treasury] = (0, program_1.getTokenTreasuryPda)(mint.publicKey);
301
+ const bondingCurveTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint.publicKey, bondingCurve, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
302
+ const treasuryTokenAccount = (0, program_1.getTreasuryTokenAccount)(mint.publicKey, treasury);
303
+ const tx = new web3_js_1.Transaction();
304
+ const provider = makeDummyProvider(connection, creator);
305
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
306
+ const createIx = await program.methods
307
+ .createToken({ name, symbol, uri: metadata_uri, solTarget: new anchor_1.BN(sol_target) })
308
+ .accounts({
309
+ creator,
310
+ globalConfig,
311
+ mint: mint.publicKey,
312
+ bondingCurve,
313
+ tokenVault: bondingCurveTokenAccount,
314
+ treasury,
315
+ treasuryTokenAccount,
316
+ token2022Program: spl_token_1.TOKEN_2022_PROGRAM_ID,
317
+ associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
318
+ systemProgram: web3_js_1.SystemProgram.programId,
319
+ rent: web3_js_1.SYSVAR_RENT_PUBKEY,
320
+ })
321
+ .instruction();
322
+ tx.add(createIx);
323
+ await finalizeTransaction(connection, tx, creator);
324
+ // Partially sign with mint keypair
325
+ tx.partialSign(mint);
326
+ return {
327
+ transaction: tx,
328
+ mint: mint.publicKey,
329
+ mintKeypair: mint,
330
+ message: `Create token "${name}" ($${symbol})`,
331
+ };
332
+ };
333
+ exports.buildCreateTokenTransaction = buildCreateTokenTransaction;
334
+ // ============================================================================
335
+ // Star
336
+ // ============================================================================
337
+ /**
338
+ * Build an unsigned star transaction (costs 0.05 SOL).
339
+ *
340
+ * @param connection - Solana RPC connection
341
+ * @param params - Star parameters (mint, user)
342
+ * @returns Unsigned transaction and descriptive message
343
+ */
344
+ const buildStarTransaction = async (connection, params) => {
345
+ const { mint: mintStr, user: userStr, vault: vaultCreatorStr } = params;
346
+ const mint = new web3_js_1.PublicKey(mintStr);
347
+ const user = new web3_js_1.PublicKey(userStr);
348
+ const tokenData = await (0, tokens_1.fetchTokenRaw)(connection, mint);
349
+ if (!tokenData)
350
+ throw new Error(`Token not found: ${mintStr}`);
351
+ const { bondingCurve } = tokenData;
352
+ if (user.equals(bondingCurve.creator)) {
353
+ throw new Error('Cannot star your own token');
354
+ }
355
+ // Check if already starred
356
+ const [starRecordPda] = (0, program_1.getStarRecordPda)(user, mint);
357
+ const starRecord = await connection.getAccountInfo(starRecordPda);
358
+ if (starRecord)
359
+ throw new Error('Already starred this token');
360
+ // Derive PDAs
361
+ const [bondingCurvePda] = (0, program_1.getBondingCurvePda)(mint);
362
+ const [treasuryPda] = (0, program_1.getTokenTreasuryPda)(mint);
363
+ // [V18] Vault accounts (optional — vault pays star cost)
364
+ let torchVaultAccount = null;
365
+ let vaultWalletLinkAccount = null;
366
+ if (vaultCreatorStr) {
367
+ const vaultCreator = new web3_js_1.PublicKey(vaultCreatorStr);
368
+ [torchVaultAccount] = (0, program_1.getTorchVaultPda)(vaultCreator);
369
+ [vaultWalletLinkAccount] = (0, program_1.getVaultWalletLinkPda)(user);
370
+ }
371
+ const tx = new web3_js_1.Transaction();
372
+ const provider = makeDummyProvider(connection, user);
373
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
374
+ const starIx = await program.methods
375
+ .starToken()
376
+ .accounts({
377
+ user,
378
+ mint,
379
+ bondingCurve: bondingCurvePda,
380
+ tokenTreasury: treasuryPda,
381
+ creator: bondingCurve.creator,
382
+ starRecord: starRecordPda,
383
+ torchVault: torchVaultAccount,
384
+ vaultWalletLink: vaultWalletLinkAccount,
385
+ systemProgram: web3_js_1.SystemProgram.programId,
386
+ })
387
+ .instruction();
388
+ tx.add(starIx);
389
+ await finalizeTransaction(connection, tx, user);
390
+ const vaultLabel = vaultCreatorStr ? ' (via vault)' : '';
391
+ return {
392
+ transaction: tx,
393
+ message: `Star token (costs 0.05 SOL)${vaultLabel}`,
394
+ };
395
+ };
396
+ exports.buildStarTransaction = buildStarTransaction;
397
+ // ============================================================================
398
+ // Message
399
+ // ============================================================================
400
+ const MAX_MESSAGE_LENGTH = 500;
401
+ // ============================================================================
402
+ // Vault (V2.0)
403
+ // ============================================================================
404
+ /**
405
+ * Build an unsigned create vault transaction.
406
+ *
407
+ * Creates a TorchVault PDA and auto-links the creator's wallet.
408
+ *
409
+ * @param connection - Solana RPC connection
410
+ * @param params - Creator public key
411
+ * @returns Unsigned transaction
412
+ */
413
+ const buildCreateVaultTransaction = async (connection, params) => {
414
+ const creator = new web3_js_1.PublicKey(params.creator);
415
+ const [vaultPda] = (0, program_1.getTorchVaultPda)(creator);
416
+ const [walletLinkPda] = (0, program_1.getVaultWalletLinkPda)(creator);
417
+ const provider = makeDummyProvider(connection, creator);
418
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
419
+ const ix = await program.methods
420
+ .createVault()
421
+ .accounts({
422
+ creator,
423
+ vault: vaultPda,
424
+ walletLink: walletLinkPda,
425
+ systemProgram: web3_js_1.SystemProgram.programId,
426
+ })
427
+ .instruction();
428
+ const tx = new web3_js_1.Transaction().add(ix);
429
+ await finalizeTransaction(connection, tx, creator);
430
+ return {
431
+ transaction: tx,
432
+ message: `Create vault for ${params.creator.slice(0, 8)}...`,
433
+ };
434
+ };
435
+ exports.buildCreateVaultTransaction = buildCreateVaultTransaction;
436
+ /**
437
+ * Build an unsigned deposit vault transaction.
438
+ *
439
+ * Anyone can deposit SOL into any vault.
440
+ *
441
+ * @param connection - Solana RPC connection
442
+ * @param params - Depositor, vault creator, amount in lamports
443
+ * @returns Unsigned transaction
444
+ */
445
+ const buildDepositVaultTransaction = async (connection, params) => {
446
+ const depositor = new web3_js_1.PublicKey(params.depositor);
447
+ const vaultCreator = new web3_js_1.PublicKey(params.vault_creator);
448
+ const [vaultPda] = (0, program_1.getTorchVaultPda)(vaultCreator);
449
+ const provider = makeDummyProvider(connection, depositor);
450
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
451
+ const ix = await program.methods
452
+ .depositVault(new anchor_1.BN(params.amount_sol.toString()))
453
+ .accounts({
454
+ depositor,
455
+ vault: vaultPda,
456
+ systemProgram: web3_js_1.SystemProgram.programId,
457
+ })
458
+ .instruction();
459
+ const tx = new web3_js_1.Transaction().add(ix);
460
+ await finalizeTransaction(connection, tx, depositor);
461
+ return {
462
+ transaction: tx,
463
+ message: `Deposit ${params.amount_sol / 1e9} SOL into vault`,
464
+ };
465
+ };
466
+ exports.buildDepositVaultTransaction = buildDepositVaultTransaction;
467
+ /**
468
+ * Build an unsigned withdraw vault transaction.
469
+ *
470
+ * Only the vault authority can withdraw.
471
+ *
472
+ * @param connection - Solana RPC connection
473
+ * @param params - Authority, vault creator, amount in lamports
474
+ * @returns Unsigned transaction
475
+ */
476
+ const buildWithdrawVaultTransaction = async (connection, params) => {
477
+ const authority = new web3_js_1.PublicKey(params.authority);
478
+ const vaultCreator = new web3_js_1.PublicKey(params.vault_creator);
479
+ const [vaultPda] = (0, program_1.getTorchVaultPda)(vaultCreator);
480
+ const provider = makeDummyProvider(connection, authority);
481
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
482
+ const ix = await program.methods
483
+ .withdrawVault(new anchor_1.BN(params.amount_sol.toString()))
484
+ .accounts({
485
+ authority,
486
+ vault: vaultPda,
487
+ systemProgram: web3_js_1.SystemProgram.programId,
488
+ })
489
+ .instruction();
490
+ const tx = new web3_js_1.Transaction().add(ix);
491
+ await finalizeTransaction(connection, tx, authority);
492
+ return {
493
+ transaction: tx,
494
+ message: `Withdraw ${params.amount_sol / 1e9} SOL from vault`,
495
+ };
496
+ };
497
+ exports.buildWithdrawVaultTransaction = buildWithdrawVaultTransaction;
498
+ /**
499
+ * Build an unsigned link wallet transaction.
500
+ *
501
+ * Only the vault authority can link wallets.
502
+ *
503
+ * @param connection - Solana RPC connection
504
+ * @param params - Authority, vault creator, wallet to link
505
+ * @returns Unsigned transaction
506
+ */
507
+ const buildLinkWalletTransaction = async (connection, params) => {
508
+ const authority = new web3_js_1.PublicKey(params.authority);
509
+ const vaultCreator = new web3_js_1.PublicKey(params.vault_creator);
510
+ const walletToLink = new web3_js_1.PublicKey(params.wallet_to_link);
511
+ const [vaultPda] = (0, program_1.getTorchVaultPda)(vaultCreator);
512
+ const [walletLinkPda] = (0, program_1.getVaultWalletLinkPda)(walletToLink);
513
+ const provider = makeDummyProvider(connection, authority);
514
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
515
+ const ix = await program.methods
516
+ .linkWallet()
517
+ .accounts({
518
+ authority,
519
+ vault: vaultPda,
520
+ walletToLink,
521
+ walletLink: walletLinkPda,
522
+ systemProgram: web3_js_1.SystemProgram.programId,
523
+ })
524
+ .instruction();
525
+ const tx = new web3_js_1.Transaction().add(ix);
526
+ await finalizeTransaction(connection, tx, authority);
527
+ return {
528
+ transaction: tx,
529
+ message: `Link wallet ${params.wallet_to_link.slice(0, 8)}... to vault`,
530
+ };
531
+ };
532
+ exports.buildLinkWalletTransaction = buildLinkWalletTransaction;
533
+ /**
534
+ * Build an unsigned unlink wallet transaction.
535
+ *
536
+ * Only the vault authority can unlink wallets. Rent returns to authority.
537
+ *
538
+ * @param connection - Solana RPC connection
539
+ * @param params - Authority, vault creator, wallet to unlink
540
+ * @returns Unsigned transaction
541
+ */
542
+ const buildUnlinkWalletTransaction = async (connection, params) => {
543
+ const authority = new web3_js_1.PublicKey(params.authority);
544
+ const vaultCreator = new web3_js_1.PublicKey(params.vault_creator);
545
+ const walletToUnlink = new web3_js_1.PublicKey(params.wallet_to_unlink);
546
+ const [vaultPda] = (0, program_1.getTorchVaultPda)(vaultCreator);
547
+ const [walletLinkPda] = (0, program_1.getVaultWalletLinkPda)(walletToUnlink);
548
+ const provider = makeDummyProvider(connection, authority);
549
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
550
+ const ix = await program.methods
551
+ .unlinkWallet()
552
+ .accounts({
553
+ authority,
554
+ vault: vaultPda,
555
+ walletToUnlink,
556
+ walletLink: walletLinkPda,
557
+ systemProgram: web3_js_1.SystemProgram.programId,
558
+ })
559
+ .instruction();
560
+ const tx = new web3_js_1.Transaction().add(ix);
561
+ await finalizeTransaction(connection, tx, authority);
562
+ return {
563
+ transaction: tx,
564
+ message: `Unlink wallet ${params.wallet_to_unlink.slice(0, 8)}... from vault`,
565
+ };
566
+ };
567
+ exports.buildUnlinkWalletTransaction = buildUnlinkWalletTransaction;
568
+ /**
569
+ * Build an unsigned transfer authority transaction.
570
+ *
571
+ * Transfers vault admin control to a new wallet.
572
+ *
573
+ * @param connection - Solana RPC connection
574
+ * @param params - Current authority, vault creator, new authority
575
+ * @returns Unsigned transaction
576
+ */
577
+ const buildTransferAuthorityTransaction = async (connection, params) => {
578
+ const authority = new web3_js_1.PublicKey(params.authority);
579
+ const vaultCreator = new web3_js_1.PublicKey(params.vault_creator);
580
+ const newAuthority = new web3_js_1.PublicKey(params.new_authority);
581
+ const [vaultPda] = (0, program_1.getTorchVaultPda)(vaultCreator);
582
+ const provider = makeDummyProvider(connection, authority);
583
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
584
+ const ix = await program.methods
585
+ .transferAuthority()
586
+ .accounts({
587
+ authority,
588
+ vault: vaultPda,
589
+ newAuthority,
590
+ })
591
+ .instruction();
592
+ const tx = new web3_js_1.Transaction().add(ix);
593
+ await finalizeTransaction(connection, tx, authority);
594
+ return {
595
+ transaction: tx,
596
+ message: `Transfer vault authority to ${params.new_authority.slice(0, 8)}...`,
597
+ };
598
+ };
599
+ exports.buildTransferAuthorityTransaction = buildTransferAuthorityTransaction;
600
+ // ============================================================================
601
+ // Borrow (V2.4)
602
+ // ============================================================================
603
+ /**
604
+ * Build an unsigned borrow transaction.
605
+ *
606
+ * Lock tokens as collateral in the collateral vault and receive SOL from treasury.
607
+ * Token must be migrated (has Raydium pool for price calculation).
608
+ *
609
+ * @param connection - Solana RPC connection
610
+ * @param params - Borrow parameters (mint, borrower, collateral_amount, sol_to_borrow)
611
+ * @returns Unsigned transaction and descriptive message
612
+ */
613
+ const buildBorrowTransaction = async (connection, params) => {
614
+ const { mint: mintStr, borrower: borrowerStr, collateral_amount, sol_to_borrow, vault: vaultCreatorStr } = params;
615
+ const mint = new web3_js_1.PublicKey(mintStr);
616
+ const borrower = new web3_js_1.PublicKey(borrowerStr);
617
+ // Derive PDAs
618
+ const [bondingCurvePda] = (0, program_1.getBondingCurvePda)(mint);
619
+ const [treasuryPda] = (0, program_1.getTokenTreasuryPda)(mint);
620
+ const [collateralVaultPda] = (0, program_1.getCollateralVaultPda)(mint);
621
+ const [loanPositionPda] = (0, program_1.getLoanPositionPda)(mint, borrower);
622
+ const borrowerTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, borrower, false, spl_token_1.TOKEN_2022_PROGRAM_ID);
623
+ // Get Raydium pool accounts for price calculation
624
+ const raydium = (0, program_1.getRaydiumMigrationAccounts)(mint);
625
+ // [V18] Vault accounts (optional — collateral from vault ATA, SOL to vault)
626
+ let torchVaultAccount = null;
627
+ let vaultWalletLinkAccount = null;
628
+ let vaultTokenAccount = null;
629
+ if (vaultCreatorStr) {
630
+ const vaultCreator = new web3_js_1.PublicKey(vaultCreatorStr);
631
+ [torchVaultAccount] = (0, program_1.getTorchVaultPda)(vaultCreator);
632
+ [vaultWalletLinkAccount] = (0, program_1.getVaultWalletLinkPda)(borrower);
633
+ vaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, torchVaultAccount, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
634
+ }
635
+ const tx = new web3_js_1.Transaction();
636
+ // Create vault ATA if needed
637
+ if (vaultTokenAccount && torchVaultAccount) {
638
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(borrower, vaultTokenAccount, torchVaultAccount, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
639
+ }
640
+ const provider = makeDummyProvider(connection, borrower);
641
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
642
+ const borrowIx = await program.methods
643
+ .borrow({
644
+ collateralAmount: new anchor_1.BN(collateral_amount.toString()),
645
+ solToBorrow: new anchor_1.BN(sol_to_borrow.toString()),
646
+ })
647
+ .accounts({
648
+ borrower,
649
+ mint,
650
+ bondingCurve: bondingCurvePda,
651
+ treasury: treasuryPda,
652
+ collateralVault: collateralVaultPda,
653
+ borrowerTokenAccount,
654
+ loanPosition: loanPositionPda,
655
+ poolState: raydium.poolState,
656
+ tokenVault0: raydium.token0Vault,
657
+ tokenVault1: raydium.token1Vault,
658
+ torchVault: torchVaultAccount,
659
+ vaultWalletLink: vaultWalletLinkAccount,
660
+ vaultTokenAccount,
661
+ tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID,
662
+ systemProgram: web3_js_1.SystemProgram.programId,
663
+ })
664
+ .instruction();
665
+ tx.add(borrowIx);
666
+ await finalizeTransaction(connection, tx, borrower);
667
+ const vaultLabel = vaultCreatorStr ? ' (via vault)' : '';
668
+ return {
669
+ transaction: tx,
670
+ message: `Borrow ${Number(sol_to_borrow) / 1e9} SOL with ${Number(collateral_amount) / 1e6} tokens as collateral${vaultLabel}`,
671
+ };
672
+ };
673
+ exports.buildBorrowTransaction = buildBorrowTransaction;
674
+ // ============================================================================
675
+ // Repay (V2.4)
676
+ // ============================================================================
677
+ /**
678
+ * Build an unsigned repay transaction.
679
+ *
680
+ * Repay SOL debt. Interest is paid first, then principal.
681
+ * Full repay returns all collateral and closes the position.
682
+ *
683
+ * @param connection - Solana RPC connection
684
+ * @param params - Repay parameters (mint, borrower, sol_amount)
685
+ * @returns Unsigned transaction and descriptive message
686
+ */
687
+ const buildRepayTransaction = async (connection, params) => {
688
+ const { mint: mintStr, borrower: borrowerStr, sol_amount, vault: vaultCreatorStr } = params;
689
+ const mint = new web3_js_1.PublicKey(mintStr);
690
+ const borrower = new web3_js_1.PublicKey(borrowerStr);
691
+ // Derive PDAs
692
+ const [treasuryPda] = (0, program_1.getTokenTreasuryPda)(mint);
693
+ const [collateralVaultPda] = (0, program_1.getCollateralVaultPda)(mint);
694
+ const [loanPositionPda] = (0, program_1.getLoanPositionPda)(mint, borrower);
695
+ const borrowerTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, borrower, false, spl_token_1.TOKEN_2022_PROGRAM_ID);
696
+ // [V18] Vault accounts (optional — SOL from vault, collateral returns to vault ATA)
697
+ let torchVaultAccount = null;
698
+ let vaultWalletLinkAccount = null;
699
+ let vaultTokenAccount = null;
700
+ if (vaultCreatorStr) {
701
+ const vaultCreator = new web3_js_1.PublicKey(vaultCreatorStr);
702
+ [torchVaultAccount] = (0, program_1.getTorchVaultPda)(vaultCreator);
703
+ [vaultWalletLinkAccount] = (0, program_1.getVaultWalletLinkPda)(borrower);
704
+ vaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, torchVaultAccount, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
705
+ }
706
+ const tx = new web3_js_1.Transaction();
707
+ // Create vault ATA if needed (collateral returns here)
708
+ if (vaultTokenAccount && torchVaultAccount) {
709
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(borrower, vaultTokenAccount, torchVaultAccount, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
710
+ }
711
+ const provider = makeDummyProvider(connection, borrower);
712
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
713
+ const repayIx = await program.methods
714
+ .repay(new anchor_1.BN(sol_amount.toString()))
715
+ .accounts({
716
+ borrower,
717
+ mint,
718
+ treasury: treasuryPda,
719
+ collateralVault: collateralVaultPda,
720
+ borrowerTokenAccount,
721
+ loanPosition: loanPositionPda,
722
+ torchVault: torchVaultAccount,
723
+ vaultWalletLink: vaultWalletLinkAccount,
724
+ vaultTokenAccount,
725
+ tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID,
726
+ systemProgram: web3_js_1.SystemProgram.programId,
727
+ })
728
+ .instruction();
729
+ tx.add(repayIx);
730
+ await finalizeTransaction(connection, tx, borrower);
731
+ const vaultLabel = vaultCreatorStr ? ' (via vault)' : '';
732
+ return {
733
+ transaction: tx,
734
+ message: `Repay ${Number(sol_amount) / 1e9} SOL${vaultLabel}`,
735
+ };
736
+ };
737
+ exports.buildRepayTransaction = buildRepayTransaction;
738
+ // ============================================================================
739
+ // Liquidate (V2.4)
740
+ // ============================================================================
741
+ /**
742
+ * Build an unsigned liquidate transaction.
743
+ *
744
+ * Permissionless — anyone can call when a borrower's LTV exceeds the
745
+ * liquidation threshold. Liquidator pays SOL and receives collateral + bonus.
746
+ *
747
+ * @param connection - Solana RPC connection
748
+ * @param params - Liquidate parameters (mint, liquidator, borrower)
749
+ * @returns Unsigned transaction and descriptive message
750
+ */
751
+ const buildLiquidateTransaction = async (connection, params) => {
752
+ const { mint: mintStr, liquidator: liquidatorStr, borrower: borrowerStr, vault: vaultCreatorStr } = params;
753
+ const mint = new web3_js_1.PublicKey(mintStr);
754
+ const liquidator = new web3_js_1.PublicKey(liquidatorStr);
755
+ const borrower = new web3_js_1.PublicKey(borrowerStr);
756
+ // Derive PDAs
757
+ const [bondingCurvePda] = (0, program_1.getBondingCurvePda)(mint);
758
+ const [treasuryPda] = (0, program_1.getTokenTreasuryPda)(mint);
759
+ const [collateralVaultPda] = (0, program_1.getCollateralVaultPda)(mint);
760
+ const [loanPositionPda] = (0, program_1.getLoanPositionPda)(mint, borrower);
761
+ const liquidatorTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, liquidator, false, spl_token_1.TOKEN_2022_PROGRAM_ID);
762
+ // Get Raydium pool accounts for price calculation
763
+ const raydium = (0, program_1.getRaydiumMigrationAccounts)(mint);
764
+ // [V20] Vault accounts (optional — SOL from vault, collateral to vault ATA)
765
+ let torchVaultAccount = null;
766
+ let vaultWalletLinkAccount = null;
767
+ let vaultTokenAccount = null;
768
+ if (vaultCreatorStr) {
769
+ const vaultCreator = new web3_js_1.PublicKey(vaultCreatorStr);
770
+ [torchVaultAccount] = (0, program_1.getTorchVaultPda)(vaultCreator);
771
+ [vaultWalletLinkAccount] = (0, program_1.getVaultWalletLinkPda)(liquidator);
772
+ vaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, torchVaultAccount, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
773
+ }
774
+ const tx = new web3_js_1.Transaction();
775
+ // Create liquidator ATA if needed
776
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(liquidator, liquidatorTokenAccount, liquidator, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
777
+ // Create vault ATA if needed (collateral goes here)
778
+ if (vaultTokenAccount && torchVaultAccount) {
779
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(liquidator, vaultTokenAccount, torchVaultAccount, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
780
+ }
781
+ const provider = makeDummyProvider(connection, liquidator);
782
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
783
+ const liquidateIx = await program.methods
784
+ .liquidate()
785
+ .accounts({
786
+ liquidator,
787
+ borrower,
788
+ mint,
789
+ bondingCurve: bondingCurvePda,
790
+ treasury: treasuryPda,
791
+ collateralVault: collateralVaultPda,
792
+ liquidatorTokenAccount,
793
+ loanPosition: loanPositionPda,
794
+ poolState: raydium.poolState,
795
+ tokenVault0: raydium.token0Vault,
796
+ tokenVault1: raydium.token1Vault,
797
+ torchVault: torchVaultAccount,
798
+ vaultWalletLink: vaultWalletLinkAccount,
799
+ vaultTokenAccount,
800
+ tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID,
801
+ associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
802
+ systemProgram: web3_js_1.SystemProgram.programId,
803
+ })
804
+ .instruction();
805
+ tx.add(liquidateIx);
806
+ await finalizeTransaction(connection, tx, liquidator);
807
+ const vaultLabel = vaultCreatorStr ? ' (via vault)' : '';
808
+ return {
809
+ transaction: tx,
810
+ message: `Liquidate loan position for ${borrowerStr.slice(0, 8)}...${vaultLabel}`,
811
+ };
812
+ };
813
+ exports.buildLiquidateTransaction = buildLiquidateTransaction;
814
+ // ============================================================================
815
+ // Claim Protocol Rewards
816
+ // ============================================================================
817
+ /**
818
+ * Build an unsigned claim protocol rewards transaction.
819
+ *
820
+ * Claims the user's proportional share of protocol treasury rewards
821
+ * based on trading volume in the previous epoch. Requires >= 10 SOL volume.
822
+ *
823
+ * @param connection - Solana RPC connection
824
+ * @param params - Claim parameters (user, optional vault)
825
+ * @returns Unsigned transaction and descriptive message
826
+ */
827
+ const buildClaimProtocolRewardsTransaction = async (connection, params) => {
828
+ const { user: userStr, vault: vaultCreatorStr } = params;
829
+ const user = new web3_js_1.PublicKey(userStr);
830
+ // Derive PDAs
831
+ const [userStatsPda] = (0, program_1.getUserStatsPda)(user);
832
+ const [protocolTreasuryPda] = (0, program_1.getProtocolTreasuryPda)();
833
+ // [V20] Vault accounts (optional — rewards go to vault instead of user)
834
+ let torchVaultAccount = null;
835
+ let vaultWalletLinkAccount = null;
836
+ if (vaultCreatorStr) {
837
+ const vaultCreator = new web3_js_1.PublicKey(vaultCreatorStr);
838
+ [torchVaultAccount] = (0, program_1.getTorchVaultPda)(vaultCreator);
839
+ [vaultWalletLinkAccount] = (0, program_1.getVaultWalletLinkPda)(user);
840
+ }
841
+ const tx = new web3_js_1.Transaction();
842
+ const provider = makeDummyProvider(connection, user);
843
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
844
+ const claimIx = await program.methods
845
+ .claimProtocolRewards()
846
+ .accounts({
847
+ user,
848
+ userStats: userStatsPda,
849
+ protocolTreasury: protocolTreasuryPda,
850
+ torchVault: torchVaultAccount,
851
+ vaultWalletLink: vaultWalletLinkAccount,
852
+ systemProgram: web3_js_1.SystemProgram.programId,
853
+ })
854
+ .instruction();
855
+ tx.add(claimIx);
856
+ await finalizeTransaction(connection, tx, user);
857
+ const vaultLabel = vaultCreatorStr ? ' (via vault)' : '';
858
+ return {
859
+ transaction: tx,
860
+ message: `Claim protocol rewards${vaultLabel}`,
861
+ };
862
+ };
863
+ exports.buildClaimProtocolRewardsTransaction = buildClaimProtocolRewardsTransaction;
864
+ // ============================================================================
865
+ // Withdraw Tokens (V18)
866
+ // ============================================================================
867
+ /**
868
+ * Build an unsigned withdraw tokens transaction.
869
+ *
870
+ * Withdraw tokens from a vault ATA to any destination token account.
871
+ * Authority only. Composability escape hatch for external DeFi.
872
+ *
873
+ * @param connection - Solana RPC connection
874
+ * @param params - Authority, vault creator, mint, destination, amount in raw units
875
+ * @returns Unsigned transaction
876
+ */
877
+ const buildWithdrawTokensTransaction = async (connection, params) => {
878
+ const authority = new web3_js_1.PublicKey(params.authority);
879
+ const vaultCreator = new web3_js_1.PublicKey(params.vault_creator);
880
+ const mint = new web3_js_1.PublicKey(params.mint);
881
+ const destination = new web3_js_1.PublicKey(params.destination);
882
+ const [vaultPda] = (0, program_1.getTorchVaultPda)(vaultCreator);
883
+ const vaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, vaultPda, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
884
+ const destinationTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, destination, false, spl_token_1.TOKEN_2022_PROGRAM_ID);
885
+ const tx = new web3_js_1.Transaction();
886
+ // Create destination ATA if needed
887
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(authority, destinationTokenAccount, destination, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
888
+ const provider = makeDummyProvider(connection, authority);
889
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
890
+ const ix = await program.methods
891
+ .withdrawTokens(new anchor_1.BN(params.amount.toString()))
892
+ .accounts({
893
+ authority,
894
+ vault: vaultPda,
895
+ mint,
896
+ vaultTokenAccount,
897
+ destinationTokenAccount,
898
+ tokenProgram: spl_token_1.TOKEN_2022_PROGRAM_ID,
899
+ })
900
+ .instruction();
901
+ tx.add(ix);
902
+ await finalizeTransaction(connection, tx, authority);
903
+ return {
904
+ transaction: tx,
905
+ message: `Withdraw ${params.amount} tokens from vault to ${params.destination.slice(0, 8)}...`,
906
+ };
907
+ };
908
+ exports.buildWithdrawTokensTransaction = buildWithdrawTokensTransaction;
909
+ // ============================================================================
910
+ // Vault Swap (V19)
911
+ // ============================================================================
912
+ /**
913
+ * Build an unsigned vault-routed DEX swap transaction.
914
+ *
915
+ * Executes a Raydium CPMM swap through the vault PDA for migrated Torch tokens.
916
+ * Full custody preserved — all value flows through the vault.
917
+ *
918
+ * @param connection - Solana RPC connection
919
+ * @param params - Swap parameters (mint, signer, vault_creator, amount_in, minimum_amount_out, is_buy)
920
+ * @returns Unsigned transaction and descriptive message
921
+ */
922
+ const buildVaultSwapTransaction = async (connection, params) => {
923
+ const { mint: mintStr, signer: signerStr, vault_creator: vaultCreatorStr, amount_in, minimum_amount_out, is_buy } = params;
924
+ const mint = new web3_js_1.PublicKey(mintStr);
925
+ const signer = new web3_js_1.PublicKey(signerStr);
926
+ const vaultCreator = new web3_js_1.PublicKey(vaultCreatorStr);
927
+ // Derive vault PDAs
928
+ const [torchVaultPda] = (0, program_1.getTorchVaultPda)(vaultCreator);
929
+ const [vaultWalletLinkPda] = (0, program_1.getVaultWalletLinkPda)(signer);
930
+ const [bondingCurvePda] = (0, program_1.getBondingCurvePda)(mint);
931
+ // Vault's token ATA (Token-2022)
932
+ const vaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(mint, torchVaultPda, true, spl_token_1.TOKEN_2022_PROGRAM_ID);
933
+ // Vault's WSOL ATA (SPL Token — persistent, reused across swaps)
934
+ const vaultWsolAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(constants_1.WSOL_MINT, torchVaultPda, true, spl_token_1.TOKEN_PROGRAM_ID);
935
+ // Raydium pool accounts
936
+ const raydium = (0, program_1.getRaydiumMigrationAccounts)(mint);
937
+ const tx = new web3_js_1.Transaction();
938
+ // Create vault token ATA if needed (for first buy of a migrated token)
939
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(signer, vaultTokenAccount, torchVaultPda, mint, spl_token_1.TOKEN_2022_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
940
+ // Create vault WSOL ATA if needed (persistent — reused across swaps)
941
+ tx.add((0, spl_token_1.createAssociatedTokenAccountIdempotentInstruction)(signer, vaultWsolAccount, torchVaultPda, constants_1.WSOL_MINT, spl_token_1.TOKEN_PROGRAM_ID, spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID));
942
+ const provider = makeDummyProvider(connection, signer);
943
+ const program = new anchor_1.Program(torch_market_json_1.default, provider);
944
+ // On buy: fund WSOL with lamports from vault in a separate instruction
945
+ // (isolates direct lamport manipulation from CPIs to avoid runtime balance errors)
946
+ if (is_buy) {
947
+ const fundIx = await program.methods
948
+ .fundVaultWsol(new anchor_1.BN(amount_in.toString()))
949
+ .accounts({
950
+ signer,
951
+ torchVault: torchVaultPda,
952
+ vaultWalletLink: vaultWalletLinkPda,
953
+ vaultWsolAccount,
954
+ })
955
+ .instruction();
956
+ tx.add(fundIx);
957
+ }
958
+ const swapIx = await program.methods
959
+ .vaultSwap(new anchor_1.BN(amount_in.toString()), new anchor_1.BN(minimum_amount_out.toString()), is_buy)
960
+ .accounts({
961
+ signer,
962
+ torchVault: torchVaultPda,
963
+ vaultWalletLink: vaultWalletLinkPda,
964
+ mint,
965
+ bondingCurve: bondingCurvePda,
966
+ vaultTokenAccount,
967
+ vaultWsolAccount,
968
+ raydiumProgram: constants_1.RAYDIUM_CPMM_PROGRAM,
969
+ raydiumAuthority: raydium.raydiumAuthority,
970
+ ammConfig: constants_1.RAYDIUM_AMM_CONFIG,
971
+ poolState: raydium.poolState,
972
+ poolTokenVault0: raydium.token0Vault,
973
+ poolTokenVault1: raydium.token1Vault,
974
+ observationState: raydium.observationState,
975
+ wsolMint: constants_1.WSOL_MINT,
976
+ tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
977
+ token2022Program: spl_token_1.TOKEN_2022_PROGRAM_ID,
978
+ associatedTokenProgram: spl_token_1.ASSOCIATED_TOKEN_PROGRAM_ID,
979
+ systemProgram: web3_js_1.SystemProgram.programId,
980
+ })
981
+ .instruction();
982
+ tx.add(swapIx);
983
+ await finalizeTransaction(connection, tx, signer);
984
+ const direction = is_buy ? 'Buy' : 'Sell';
985
+ const amountLabel = is_buy
986
+ ? `${amount_in / 1e9} SOL`
987
+ : `${amount_in / 1e6} tokens`;
988
+ return {
989
+ transaction: tx,
990
+ message: `${direction} ${amountLabel} via vault DEX swap`,
991
+ };
992
+ };
993
+ exports.buildVaultSwapTransaction = buildVaultSwapTransaction;
994
+ //# sourceMappingURL=transactions.js.map