@zoidz123/raydium-cli 0.1.0

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,113 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerConfigCommands = registerConfigCommands;
7
+ const inquirer_1 = __importDefault(require("inquirer"));
8
+ const config_manager_1 = require("../../lib/config-manager");
9
+ const output_1 = require("../../lib/output");
10
+ function registerConfigCommands(program) {
11
+ const config = program.command("config").description("Manage CLI configuration");
12
+ config
13
+ .command("init")
14
+ .description("Interactive config setup")
15
+ .action(async () => {
16
+ const current = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
17
+ const answers = await inquirer_1.default.prompt([
18
+ {
19
+ type: "input",
20
+ name: "rpcUrl",
21
+ message: "RPC URL",
22
+ default: current["rpc-url"],
23
+ validate: (input) => (input ? true : "RPC URL is required")
24
+ },
25
+ {
26
+ type: "input",
27
+ name: "slippage",
28
+ message: "Default slippage (%)",
29
+ default: String(current["default-slippage"]),
30
+ validate: (input) => (Number.isFinite(Number(input)) ? true : "Enter a valid number")
31
+ },
32
+ {
33
+ type: "list",
34
+ name: "explorer",
35
+ message: "Explorer",
36
+ choices: ["solscan", "solanaFm", "solanaExplorer"],
37
+ default: current.explorer
38
+ },
39
+ {
40
+ type: "input",
41
+ name: "priorityFee",
42
+ message: "Priority fee (SOL)",
43
+ default: String(current["priority-fee"]),
44
+ validate: (input) => (Number.isFinite(Number(input)) ? true : "Enter a valid number")
45
+ }
46
+ ]);
47
+ const nextConfig = {
48
+ ...current,
49
+ "rpc-url": answers.rpcUrl,
50
+ "default-slippage": Number(answers.slippage),
51
+ explorer: answers.explorer,
52
+ "priority-fee": Number(answers.priorityFee)
53
+ };
54
+ await (0, config_manager_1.saveConfig)(nextConfig);
55
+ if ((0, output_1.isJsonOutput)()) {
56
+ (0, output_1.logJson)({ ok: true, config: nextConfig });
57
+ }
58
+ else {
59
+ (0, output_1.logSuccess)("Config saved");
60
+ }
61
+ });
62
+ config
63
+ .command("set")
64
+ .description("Set a config value")
65
+ .argument("<key>")
66
+ .argument("<value>")
67
+ .action(async (key, value) => {
68
+ if (!(0, config_manager_1.isValidConfigKey)(key)) {
69
+ (0, output_1.logError)(`Unknown config key: ${key}`);
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+ const configData = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
74
+ const parsed = (0, config_manager_1.parseConfigValue)(key, value);
75
+ const nextConfig = { ...configData, [key]: parsed };
76
+ await (0, config_manager_1.saveConfig)(nextConfig);
77
+ if ((0, output_1.isJsonOutput)()) {
78
+ (0, output_1.logJson)({ ok: true, config: nextConfig });
79
+ }
80
+ else {
81
+ (0, output_1.logSuccess)(`Updated ${key}`);
82
+ }
83
+ });
84
+ config
85
+ .command("get")
86
+ .description("Get config values")
87
+ .argument("[key]")
88
+ .action(async (key) => {
89
+ const configData = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
90
+ if (key) {
91
+ if (!(0, config_manager_1.isValidConfigKey)(key)) {
92
+ (0, output_1.logError)(`Unknown config key: ${key}`);
93
+ process.exitCode = 1;
94
+ return;
95
+ }
96
+ if ((0, output_1.isJsonOutput)()) {
97
+ (0, output_1.logJson)({ [key]: configData[key] });
98
+ }
99
+ else {
100
+ (0, output_1.logInfo)(String(configData[key] ?? ""));
101
+ }
102
+ return;
103
+ }
104
+ if ((0, output_1.isJsonOutput)()) {
105
+ (0, output_1.logJson)(configData);
106
+ }
107
+ else {
108
+ Object.entries(configData).forEach(([itemKey, value]) => {
109
+ (0, output_1.logInfo)(`${itemKey}: ${value}`);
110
+ });
111
+ }
112
+ });
113
+ }
@@ -0,0 +1,480 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerCpmmCommands = registerCpmmCommands;
7
+ const node_util_1 = require("node:util");
8
+ const web3_js_1 = require("@solana/web3.js");
9
+ const raydium_sdk_v2_1 = require("@raydium-io/raydium-sdk-v2");
10
+ const bn_js_1 = __importDefault(require("bn.js"));
11
+ const raydium_client_1 = require("../../lib/raydium-client");
12
+ const config_manager_1 = require("../../lib/config-manager");
13
+ const wallet_manager_1 = require("../../lib/wallet-manager");
14
+ const prompt_1 = require("../../lib/prompt");
15
+ const output_1 = require("../../lib/output");
16
+ // Fetch lock position info from Raydium API
17
+ async function fetchCpmmLockPositionInfo(nftMint, cluster = "mainnet") {
18
+ const baseUrl = cluster === "devnet"
19
+ ? "https://dynamic-ipfs-devnet.raydium.io/lock/cpmm/position"
20
+ : "https://dynamic-ipfs.raydium.io/lock/cpmm/position";
21
+ const url = `${baseUrl}/${nftMint}`;
22
+ try {
23
+ const response = await fetch(url);
24
+ if (!response.ok) {
25
+ return null;
26
+ }
27
+ return await response.json();
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ const DEFAULT_COMPUTE_UNITS = 600000;
34
+ const FEE_RATE_DENOMINATOR = 1000000;
35
+ function registerCpmmCommands(program) {
36
+ const cpmm = program.command("cpmm").description("CPMM (Constant Product) pool commands");
37
+ // List available CPMM configs
38
+ cpmm
39
+ .command("configs")
40
+ .description("List available CPMM pool fee configurations")
41
+ .option("--devnet", "Use devnet API instead of mainnet")
42
+ .action(async (options) => {
43
+ const baseUrl = options.devnet
44
+ ? "https://api-v3.raydium.io/devnet/cpmm-config"
45
+ : "https://api-v3.raydium.io/main/cpmm-config";
46
+ let configData;
47
+ try {
48
+ configData = await (0, output_1.withSpinner)("Fetching CPMM configs", async () => {
49
+ const response = await fetch(baseUrl);
50
+ if (!response.ok) {
51
+ throw new Error(`API request failed: ${response.status}`);
52
+ }
53
+ return response.json();
54
+ });
55
+ }
56
+ catch (error) {
57
+ const message = error instanceof Error ? error.message : String(error);
58
+ (0, output_1.logError)("Failed to fetch CPMM configs", message);
59
+ process.exitCode = 1;
60
+ return;
61
+ }
62
+ if (!configData.success || !configData.data) {
63
+ (0, output_1.logError)("Invalid API response");
64
+ process.exitCode = 1;
65
+ return;
66
+ }
67
+ // Sort by index
68
+ const configs = configData.data.sort((a, b) => a.index - b.index);
69
+ if ((0, output_1.isJsonOutput)()) {
70
+ // Add calculated bps values for JSON output
71
+ const enrichedConfigs = configs.map(c => ({
72
+ ...c,
73
+ tradeFeeRateBps: (c.tradeFeeRate / FEE_RATE_DENOMINATOR) * 10000,
74
+ creatorFeeRateBps: (c.creatorFeeRate / FEE_RATE_DENOMINATOR) * 10000,
75
+ totalFeeBps: ((c.tradeFeeRate + c.creatorFeeRate) / FEE_RATE_DENOMINATOR) * 10000,
76
+ protocolFeePercent: (c.protocolFeeRate / FEE_RATE_DENOMINATOR) * 100,
77
+ fundFeePercent: (c.fundFeeRate / FEE_RATE_DENOMINATOR) * 100,
78
+ lpFeePercent: 100 - ((c.protocolFeeRate + c.fundFeeRate) / FEE_RATE_DENOMINATOR) * 100,
79
+ createPoolFeeSol: Number(c.createPoolFee) / 1e9,
80
+ }));
81
+ (0, output_1.logJson)({ configs: enrichedConfigs });
82
+ return;
83
+ }
84
+ (0, output_1.logInfo)("");
85
+ (0, output_1.logInfo)("Available CPMM Fee Configurations");
86
+ (0, output_1.logInfo)("══════════════════════════════════════════════════════════════════════════════");
87
+ (0, output_1.logInfo)("");
88
+ (0, output_1.logInfo)("Fee Structure Explanation:");
89
+ (0, output_1.logInfo)(" • tradeFeeRate: Fee charged on each swap (in bps)");
90
+ (0, output_1.logInfo)(" └─ Split between: LP (compounds) + Protocol (Raydium) + Fund");
91
+ (0, output_1.logInfo)(" • creatorFeeRate: Additional fee to pool creator (in bps)");
92
+ (0, output_1.logInfo)(" • Total Fee = tradeFeeRate + creatorFeeRate");
93
+ (0, output_1.logInfo)("");
94
+ for (const config of configs) {
95
+ const tradeBps = (config.tradeFeeRate / FEE_RATE_DENOMINATOR) * 10000;
96
+ const creatorBps = (config.creatorFeeRate / FEE_RATE_DENOMINATOR) * 10000;
97
+ const totalBps = tradeBps + creatorBps;
98
+ const protocolPct = (config.protocolFeeRate / FEE_RATE_DENOMINATOR) * 100;
99
+ const fundPct = (config.fundFeeRate / FEE_RATE_DENOMINATOR) * 100;
100
+ const lpPct = 100 - protocolPct - fundPct;
101
+ const createPoolSol = Number(config.createPoolFee) / 1e9;
102
+ (0, output_1.logInfo)(`Config #${config.index}`);
103
+ (0, output_1.logInfo)(` ID: ${config.id}`);
104
+ (0, output_1.logInfo)(` Trade Fee: ${tradeBps} bps (${tradeBps / 100}%)`);
105
+ (0, output_1.logInfo)(` ├─ LP Fee: ~${(tradeBps * lpPct / 100).toFixed(1)} bps (${lpPct.toFixed(0)}% of trade fee → compounds into pool)`);
106
+ (0, output_1.logInfo)(` ├─ Protocol: ~${(tradeBps * protocolPct / 100).toFixed(1)} bps (${protocolPct.toFixed(0)}% of trade fee → Raydium)`);
107
+ (0, output_1.logInfo)(` └─ Fund: ~${(tradeBps * fundPct / 100).toFixed(1)} bps (${fundPct.toFixed(0)}% of trade fee)`);
108
+ (0, output_1.logInfo)(` Creator Fee: ${creatorBps} bps (${creatorBps / 100}%) → pool creator`);
109
+ (0, output_1.logInfo)(` Total Fee: ${totalBps} bps (${totalBps / 100}%)`);
110
+ (0, output_1.logInfo)(` Pool Creation Fee: ${createPoolSol} SOL`);
111
+ (0, output_1.logInfo)("");
112
+ }
113
+ (0, output_1.logInfo)("──────────────────────────────────────────────────────────────────────────────");
114
+ (0, output_1.logInfo)("Note: These are the only available configs. Custom configs require Raydium.");
115
+ });
116
+ // Collect creator fees command
117
+ cpmm
118
+ .command("collect-creator-fees")
119
+ .description("Collect creator fees from a CPMM pool you created")
120
+ .requiredOption("--pool-id <address>", "Pool ID to collect from")
121
+ .option("--priority-fee <sol>", "Priority fee in SOL")
122
+ .option("--debug", "Print full error on failure")
123
+ .action(async (options) => {
124
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
125
+ // Validate pool ID
126
+ let poolId;
127
+ try {
128
+ poolId = new web3_js_1.PublicKey(options.poolId);
129
+ }
130
+ catch {
131
+ (0, output_1.logError)("Invalid pool ID address");
132
+ process.exitCode = 1;
133
+ return;
134
+ }
135
+ // Validate priority fee
136
+ const priorityFeeSol = options.priorityFee ? Number(options.priorityFee) : config["priority-fee"];
137
+ if (!Number.isFinite(priorityFeeSol) || priorityFeeSol < 0) {
138
+ (0, output_1.logError)("Invalid priority fee");
139
+ process.exitCode = 1;
140
+ return;
141
+ }
142
+ const priorityFeeLamports = priorityFeeSol * 1e9;
143
+ const priorityFeeMicroLamports = Math.round((priorityFeeLamports * 1e6) / DEFAULT_COMPUTE_UNITS);
144
+ // Check wallet
145
+ const walletName = config.activeWallet;
146
+ if (!walletName) {
147
+ (0, output_1.logError)("No active wallet set. Use 'raydium wallet use <name>' to set one.");
148
+ process.exitCode = 1;
149
+ return;
150
+ }
151
+ // Prompt for password and decrypt wallet
152
+ const password = await (0, prompt_1.promptPassword)("Enter wallet password");
153
+ let owner;
154
+ try {
155
+ owner = await (0, wallet_manager_1.decryptWallet)(walletName, password);
156
+ }
157
+ catch (error) {
158
+ (0, output_1.logError)("Failed to decrypt wallet", error.message);
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+ // Load Raydium with owner
163
+ const raydium = await (0, output_1.withSpinner)("Loading Raydium", () => (0, raydium_client_1.loadRaydium)({ owner, disableLoadToken: true }));
164
+ // Fetch pool info
165
+ let poolInfo;
166
+ try {
167
+ poolInfo = await (0, output_1.withSpinner)("Fetching pool info", async () => {
168
+ const data = await raydium.api.fetchPoolById({ ids: poolId.toBase58() });
169
+ if (!data || data.length === 0) {
170
+ throw new Error("Pool not found");
171
+ }
172
+ const pool = data[0];
173
+ if (pool.type !== "Standard" || !("lpMint" in pool)) {
174
+ throw new Error("Not a CPMM pool");
175
+ }
176
+ return pool;
177
+ });
178
+ }
179
+ catch (error) {
180
+ const message = error instanceof Error ? error.message : String(error);
181
+ (0, output_1.logError)("Failed to fetch pool info", message);
182
+ process.exitCode = 1;
183
+ return;
184
+ }
185
+ // Show preview
186
+ const mintA = poolInfo.mintA;
187
+ const mintB = poolInfo.mintB;
188
+ const symbolA = mintA.symbol || mintA.address.slice(0, 8) + "...";
189
+ const symbolB = mintB.symbol || mintB.address.slice(0, 8) + "...";
190
+ if ((0, output_1.isJsonOutput)()) {
191
+ (0, output_1.logJson)({
192
+ action: "collect-creator-fees",
193
+ poolId: poolId.toBase58(),
194
+ pair: `${symbolA}/${symbolB}`
195
+ });
196
+ }
197
+ else {
198
+ (0, output_1.logInfo)("");
199
+ (0, output_1.logInfo)(`Collecting Creator Fees`);
200
+ (0, output_1.logInfo)(` Pool: ${poolId.toBase58()}`);
201
+ (0, output_1.logInfo)(` Pair: ${symbolA}/${symbolB}`);
202
+ }
203
+ // Confirm
204
+ const ok = await (0, prompt_1.promptConfirm)("Proceed with collecting creator fees?", false);
205
+ if (!ok) {
206
+ (0, output_1.logInfo)("Cancelled");
207
+ return;
208
+ }
209
+ // Build and execute transaction
210
+ let txData;
211
+ try {
212
+ txData = await (0, output_1.withSpinner)("Building transaction", async () => {
213
+ return raydium.cpmm.collectCreatorFees({
214
+ poolInfo,
215
+ programId: raydium_sdk_v2_1.CREATE_CPMM_POOL_PROGRAM,
216
+ txVersion: raydium_sdk_v2_1.TxVersion.V0,
217
+ computeBudgetConfig: priorityFeeMicroLamports > 0
218
+ ? { units: DEFAULT_COMPUTE_UNITS, microLamports: priorityFeeMicroLamports }
219
+ : undefined
220
+ });
221
+ });
222
+ }
223
+ catch (error) {
224
+ const message = error instanceof Error ? error.message : String(error);
225
+ if (options.debug) {
226
+ const detail = error instanceof Error ? error.stack ?? message : (0, node_util_1.inspect)(error, { depth: 6 });
227
+ (0, output_1.logError)("Failed to build transaction", detail);
228
+ }
229
+ else {
230
+ (0, output_1.logError)("Failed to build transaction", message);
231
+ }
232
+ process.exitCode = 1;
233
+ return;
234
+ }
235
+ let result;
236
+ try {
237
+ result = await (0, output_1.withSpinner)("Sending transaction", () => txData.execute({ sendAndConfirm: true }));
238
+ }
239
+ catch (error) {
240
+ const message = error instanceof Error ? error.message : String(error ?? "Collect fees failed");
241
+ if (options.debug) {
242
+ const detail = error instanceof Error ? error.stack ?? message : (0, node_util_1.inspect)(error, { depth: 6 });
243
+ (0, output_1.logError)("Collect fees failed", detail);
244
+ const logs = error?.logs;
245
+ if (logs?.length) {
246
+ console.error(logs.join("\n"));
247
+ }
248
+ }
249
+ else {
250
+ (0, output_1.logError)("Collect fees failed", message);
251
+ }
252
+ process.exitCode = 1;
253
+ return;
254
+ }
255
+ if ((0, output_1.isJsonOutput)()) {
256
+ (0, output_1.logJson)({ txId: result.txId });
257
+ }
258
+ else {
259
+ (0, output_1.logSuccess)(`Creator fees collected: ${result.txId}`);
260
+ }
261
+ });
262
+ // Harvest LP fees command (for locked LP positions)
263
+ cpmm
264
+ .command("harvest-lp-fees")
265
+ .description("Harvest fees from a locked LP position")
266
+ .requiredOption("--pool-id <address>", "Pool ID")
267
+ .requiredOption("--nft-mint <address>", "Fee Key NFT mint address")
268
+ .option("--lp-fee-amount <amount>", "LP fee amount to harvest (in raw units, overrides --percent)")
269
+ .option("--percent <number>", "Percentage of available fees to harvest (default: 100)", "100")
270
+ .option("--priority-fee <sol>", "Priority fee in SOL")
271
+ .option("--debug", "Print full error on failure")
272
+ .action(async (options) => {
273
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
274
+ // Validate pool ID
275
+ let poolId;
276
+ try {
277
+ poolId = new web3_js_1.PublicKey(options.poolId);
278
+ }
279
+ catch {
280
+ (0, output_1.logError)("Invalid pool ID address");
281
+ process.exitCode = 1;
282
+ return;
283
+ }
284
+ // Validate NFT mint
285
+ let nftMint;
286
+ try {
287
+ nftMint = new web3_js_1.PublicKey(options.nftMint);
288
+ }
289
+ catch {
290
+ (0, output_1.logError)("Invalid NFT mint address");
291
+ process.exitCode = 1;
292
+ return;
293
+ }
294
+ // Validate percent
295
+ const percent = Number(options.percent ?? "100");
296
+ if (!Number.isFinite(percent) || percent <= 0 || percent > 100) {
297
+ (0, output_1.logError)("Invalid percent (must be 1-100)");
298
+ process.exitCode = 1;
299
+ return;
300
+ }
301
+ // Determine LP fee amount - either explicit or fetched from API
302
+ let lpFeeAmount;
303
+ let totalAvailableFee;
304
+ if (options.lpFeeAmount) {
305
+ // User provided explicit amount
306
+ const lpFeeAmountNum = Number(options.lpFeeAmount);
307
+ if (!Number.isFinite(lpFeeAmountNum) || lpFeeAmountNum < 0) {
308
+ (0, output_1.logError)("Invalid LP fee amount");
309
+ process.exitCode = 1;
310
+ return;
311
+ }
312
+ lpFeeAmount = new bn_js_1.default(options.lpFeeAmount);
313
+ }
314
+ else {
315
+ // Fetch from API - determine cluster from RPC URL
316
+ const isDevnet = config["rpc-url"]?.toLowerCase().includes("devnet") ?? false;
317
+ const cluster = isDevnet ? "devnet" : "mainnet";
318
+ const lockInfo = await (0, output_1.withSpinner)("Fetching lock position info", () => fetchCpmmLockPositionInfo(nftMint.toBase58(), cluster));
319
+ if (!lockInfo) {
320
+ (0, output_1.logError)("Failed to fetch lock position info. Use --lp-fee-amount to specify manually.");
321
+ process.exitCode = 1;
322
+ return;
323
+ }
324
+ totalAvailableFee = lockInfo.positionInfo.unclaimedFee.lp;
325
+ if (totalAvailableFee <= 0) {
326
+ (0, output_1.logError)("No unclaimed LP fees available");
327
+ process.exitCode = 1;
328
+ return;
329
+ }
330
+ // Calculate amount based on percentage
331
+ const amountToHarvest = Math.floor(totalAvailableFee * (percent / 100));
332
+ if (amountToHarvest <= 0) {
333
+ (0, output_1.logError)("Calculated harvest amount is zero");
334
+ process.exitCode = 1;
335
+ return;
336
+ }
337
+ lpFeeAmount = new bn_js_1.default(amountToHarvest);
338
+ }
339
+ // Validate priority fee
340
+ const priorityFeeSol = options.priorityFee ? Number(options.priorityFee) : config["priority-fee"];
341
+ if (!Number.isFinite(priorityFeeSol) || priorityFeeSol < 0) {
342
+ (0, output_1.logError)("Invalid priority fee");
343
+ process.exitCode = 1;
344
+ return;
345
+ }
346
+ const priorityFeeLamports = priorityFeeSol * 1e9;
347
+ const priorityFeeMicroLamports = Math.round((priorityFeeLamports * 1e6) / DEFAULT_COMPUTE_UNITS);
348
+ // Check wallet
349
+ const walletName = config.activeWallet;
350
+ if (!walletName) {
351
+ (0, output_1.logError)("No active wallet set. Use 'raydium wallet use <name>' to set one.");
352
+ process.exitCode = 1;
353
+ return;
354
+ }
355
+ // Prompt for password and decrypt wallet
356
+ const password = await (0, prompt_1.promptPassword)("Enter wallet password");
357
+ let owner;
358
+ try {
359
+ owner = await (0, wallet_manager_1.decryptWallet)(walletName, password);
360
+ }
361
+ catch (error) {
362
+ (0, output_1.logError)("Failed to decrypt wallet", error.message);
363
+ process.exitCode = 1;
364
+ return;
365
+ }
366
+ // Load Raydium with owner
367
+ const raydium = await (0, output_1.withSpinner)("Loading Raydium", () => (0, raydium_client_1.loadRaydium)({ owner, disableLoadToken: true }));
368
+ // Fetch pool info
369
+ let poolInfo;
370
+ try {
371
+ poolInfo = await (0, output_1.withSpinner)("Fetching pool info", async () => {
372
+ const data = await raydium.api.fetchPoolById({ ids: poolId.toBase58() });
373
+ if (!data || data.length === 0) {
374
+ throw new Error("Pool not found");
375
+ }
376
+ const pool = data[0];
377
+ if (pool.type !== "Standard" || !("lpMint" in pool)) {
378
+ throw new Error("Not a CPMM pool");
379
+ }
380
+ return pool;
381
+ });
382
+ }
383
+ catch (error) {
384
+ const message = error instanceof Error ? error.message : String(error);
385
+ (0, output_1.logError)("Failed to fetch pool info", message);
386
+ process.exitCode = 1;
387
+ return;
388
+ }
389
+ // Show preview
390
+ const mintA = poolInfo.mintA;
391
+ const mintB = poolInfo.mintB;
392
+ const symbolA = mintA.symbol || mintA.address.slice(0, 8) + "...";
393
+ const symbolB = mintB.symbol || mintB.address.slice(0, 8) + "...";
394
+ if ((0, output_1.isJsonOutput)()) {
395
+ (0, output_1.logJson)({
396
+ action: "harvest-lp-fees",
397
+ poolId: poolId.toBase58(),
398
+ pair: `${symbolA}/${symbolB}`,
399
+ nftMint: nftMint.toBase58(),
400
+ lpFeeAmount: lpFeeAmount.toString(),
401
+ ...(totalAvailableFee !== undefined && { totalAvailableFee, percent })
402
+ });
403
+ }
404
+ else {
405
+ (0, output_1.logInfo)("");
406
+ (0, output_1.logInfo)(`Harvesting LP Fees from Locked Position`);
407
+ (0, output_1.logInfo)(` Pool: ${poolId.toBase58()}`);
408
+ (0, output_1.logInfo)(` Pair: ${symbolA}/${symbolB}`);
409
+ (0, output_1.logInfo)(` NFT Mint: ${nftMint.toBase58()}`);
410
+ if (totalAvailableFee !== undefined) {
411
+ (0, output_1.logInfo)(` Available LP Fees: ${totalAvailableFee}`);
412
+ (0, output_1.logInfo)(` Harvesting: ${percent}% (${lpFeeAmount.toString()} LP)`);
413
+ }
414
+ else {
415
+ (0, output_1.logInfo)(` LP Fee Amount: ${lpFeeAmount.toString()}`);
416
+ }
417
+ }
418
+ // Confirm
419
+ const ok = await (0, prompt_1.promptConfirm)("Proceed with harvesting LP fees?", false);
420
+ if (!ok) {
421
+ (0, output_1.logInfo)("Cancelled");
422
+ return;
423
+ }
424
+ // Build and execute transaction
425
+ let txData;
426
+ try {
427
+ txData = await (0, output_1.withSpinner)("Building transaction", async () => {
428
+ return raydium.cpmm.harvestLockLp({
429
+ poolInfo,
430
+ nftMint,
431
+ lpFeeAmount,
432
+ programId: raydium_sdk_v2_1.LOCK_CPMM_PROGRAM,
433
+ authProgram: raydium_sdk_v2_1.LOCK_CPMM_AUTH,
434
+ txVersion: raydium_sdk_v2_1.TxVersion.V0,
435
+ computeBudgetConfig: priorityFeeMicroLamports > 0
436
+ ? { units: DEFAULT_COMPUTE_UNITS, microLamports: priorityFeeMicroLamports }
437
+ : undefined
438
+ });
439
+ });
440
+ }
441
+ catch (error) {
442
+ const message = error instanceof Error ? error.message : String(error);
443
+ if (options.debug) {
444
+ const detail = error instanceof Error ? error.stack ?? message : (0, node_util_1.inspect)(error, { depth: 6 });
445
+ (0, output_1.logError)("Failed to build transaction", detail);
446
+ }
447
+ else {
448
+ (0, output_1.logError)("Failed to build transaction", message);
449
+ }
450
+ process.exitCode = 1;
451
+ return;
452
+ }
453
+ let result;
454
+ try {
455
+ result = await (0, output_1.withSpinner)("Sending transaction", () => txData.execute({ sendAndConfirm: true }));
456
+ }
457
+ catch (error) {
458
+ const message = error instanceof Error ? error.message : String(error ?? "Harvest failed");
459
+ if (options.debug) {
460
+ const detail = error instanceof Error ? error.stack ?? message : (0, node_util_1.inspect)(error, { depth: 6 });
461
+ (0, output_1.logError)("Harvest failed", detail);
462
+ const logs = error?.logs;
463
+ if (logs?.length) {
464
+ console.error(logs.join("\n"));
465
+ }
466
+ }
467
+ else {
468
+ (0, output_1.logError)("Harvest failed", message);
469
+ }
470
+ process.exitCode = 1;
471
+ return;
472
+ }
473
+ if ((0, output_1.isJsonOutput)()) {
474
+ (0, output_1.logJson)({ txId: result.txId });
475
+ }
476
+ else {
477
+ (0, output_1.logSuccess)(`LP fees harvested: ${result.txId}`);
478
+ }
479
+ });
480
+ }