@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.
- package/LICENSE +21 -0
- package/README.md +74 -0
- package/dist/commands/clmm/index.js +2118 -0
- package/dist/commands/config/index.js +113 -0
- package/dist/commands/cpmm/index.js +480 -0
- package/dist/commands/launchpad/index.js +1677 -0
- package/dist/commands/pools/index.js +81 -0
- package/dist/commands/swap/index.js +490 -0
- package/dist/commands/tokens/index.js +93 -0
- package/dist/commands/wallet/index.js +267 -0
- package/dist/index.js +43 -0
- package/dist/lib/clmm-utils.js +296 -0
- package/dist/lib/codex-sdk.js +17 -0
- package/dist/lib/config-manager.js +67 -0
- package/dist/lib/connection.js +9 -0
- package/dist/lib/ipfs.js +117 -0
- package/dist/lib/output.js +59 -0
- package/dist/lib/paths.js +11 -0
- package/dist/lib/prompt.js +58 -0
- package/dist/lib/raydium-client.js +23 -0
- package/dist/lib/token-price.js +45 -0
- package/dist/lib/wallet-manager.js +173 -0
- package/dist/types/config.js +11 -0
- package/package.json +56 -0
|
@@ -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
|
+
}
|