@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,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerPoolCommands = registerPoolCommands;
4
+ const raydium_sdk_v2_1 = require("@raydium-io/raydium-sdk-v2");
5
+ const raydium_client_1 = require("../../lib/raydium-client");
6
+ const output_1 = require("../../lib/output");
7
+ function mapPoolType(type) {
8
+ switch (type) {
9
+ case "standard":
10
+ return raydium_sdk_v2_1.PoolFetchType.Standard;
11
+ case "concentrated":
12
+ return raydium_sdk_v2_1.PoolFetchType.Concentrated;
13
+ case "all":
14
+ default:
15
+ return raydium_sdk_v2_1.PoolFetchType.All;
16
+ }
17
+ }
18
+ function registerPoolCommands(program) {
19
+ const pools = program.command("pools").description("Pool utilities");
20
+ pools
21
+ .command("list")
22
+ .description("List pools")
23
+ .option("--type <type>", "all|standard|concentrated", "all")
24
+ .option("--mint-a <mint>", "Filter by mint A")
25
+ .option("--mint-b <mint>", "Filter by mint B")
26
+ .option("--limit <number>", "Limit results", "100")
27
+ .option("--page <number>", "Page number", "1")
28
+ .action(async (options) => {
29
+ const limit = Number(options.limit);
30
+ const page = Number(options.page);
31
+ const raydium = await (0, output_1.withSpinner)("Fetching pools", () => (0, raydium_client_1.loadRaydium)({ disableLoadToken: true }));
32
+ const poolType = mapPoolType(options.type);
33
+ let data;
34
+ let outputPage = Number.isFinite(page) ? page : undefined;
35
+ if (options.mintA || options.mintB) {
36
+ const mintA = options.mintA ?? options.mintB;
37
+ const mintB = options.mintA ? options.mintB : undefined;
38
+ const mintPage = Number.isFinite(page) && page > 0 ? page : 1;
39
+ outputPage = mintPage;
40
+ data = await raydium.api.fetchPoolByMints({
41
+ mint1: mintA,
42
+ mint2: mintB,
43
+ type: poolType,
44
+ page: mintPage
45
+ });
46
+ }
47
+ else {
48
+ const listPage = Number.isFinite(page) && page > 0 ? page : 1;
49
+ outputPage = listPage;
50
+ data = await raydium.api.getPoolList({
51
+ type: poolType,
52
+ page: listPage,
53
+ pageSize: Number.isFinite(limit) ? limit : 100
54
+ });
55
+ }
56
+ const poolsList = data.data ?? [];
57
+ const results = Number.isFinite(limit) ? poolsList.slice(0, limit) : poolsList;
58
+ if ((0, output_1.isJsonOutput)()) {
59
+ (0, output_1.logJson)({
60
+ pools: results,
61
+ page: outputPage,
62
+ count: data.count,
63
+ hasNextPage: data.hasNextPage
64
+ });
65
+ return;
66
+ }
67
+ if (results.length === 0) {
68
+ (0, output_1.logInfo)("No pools found");
69
+ return;
70
+ }
71
+ (0, output_1.logInfo)(`Showing ${results.length} pools (page ${outputPage}, total: ${data.count})\n`);
72
+ results.forEach((pool) => {
73
+ (0, output_1.logInfo)(`${pool.id} (${pool.type})`);
74
+ (0, output_1.logInfo)(` mintA: ${pool.mintA.address}`);
75
+ (0, output_1.logInfo)(` mintB: ${pool.mintB.address}`);
76
+ if ("lpMint" in pool && pool.lpMint) {
77
+ (0, output_1.logInfo)(` lpMint: ${pool.lpMint.address}`);
78
+ }
79
+ });
80
+ });
81
+ }
@@ -0,0 +1,490 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSwapCommands = registerSwapCommands;
4
+ const node_util_1 = require("node:util");
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ const raydium_sdk_v2_1 = require("@raydium-io/raydium-sdk-v2");
7
+ const spl_token_1 = require("@solana/spl-token");
8
+ const config_manager_1 = require("../../lib/config-manager");
9
+ const wallet_manager_1 = require("../../lib/wallet-manager");
10
+ const prompt_1 = require("../../lib/prompt");
11
+ const output_1 = require("../../lib/output");
12
+ const raydium_client_1 = require("../../lib/raydium-client");
13
+ const connection_1 = require("../../lib/connection");
14
+ const WRAPPED_SOL_MINT = "So11111111111111111111111111111111111111112";
15
+ const VALID_AMM_PROGRAM_IDS = new Set([
16
+ raydium_sdk_v2_1.AMM_V4.toBase58(),
17
+ raydium_sdk_v2_1.AMM_STABLE.toBase58(),
18
+ raydium_sdk_v2_1.DEVNET_PROGRAM_ID.AMM_V4.toBase58(),
19
+ raydium_sdk_v2_1.DEVNET_PROGRAM_ID.AMM_STABLE.toBase58()
20
+ ]);
21
+ function buildTokenFromInfo(info) {
22
+ const isToken2022 = info.programId === spl_token_1.TOKEN_2022_PROGRAM_ID.toBase58();
23
+ return new raydium_sdk_v2_1.Token({
24
+ mint: info.address,
25
+ decimals: info.decimals,
26
+ symbol: info.symbol,
27
+ name: info.name,
28
+ isToken2022
29
+ });
30
+ }
31
+ // Trade API functions
32
+ const TRADE_API_HOST = "https://transaction-v1.raydium.io";
33
+ async function fetchTradeQuote(params) {
34
+ const url = `${TRADE_API_HOST}/compute/swap-base-in?inputMint=${params.inputMint}&outputMint=${params.outputMint}&amount=${params.amount}&slippageBps=${params.slippageBps}&txVersion=V0`;
35
+ const res = await fetch(url);
36
+ if (!res.ok) {
37
+ const text = await res.text();
38
+ throw new Error(`Trade API error: HTTP ${res.status} - ${text}`);
39
+ }
40
+ const json = await res.json();
41
+ if (!json.success) {
42
+ const errDetail = json.msg || json.message || json.error || JSON.stringify(json);
43
+ throw new Error(`Trade API quote failed: ${errDetail}`);
44
+ }
45
+ return json;
46
+ }
47
+ async function serializeSwapTx(params) {
48
+ const res = await fetch(`${TRADE_API_HOST}/transaction/swap-base-in`, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json" },
51
+ body: JSON.stringify({
52
+ swapResponse: params.swapResponse,
53
+ wallet: params.wallet,
54
+ txVersion: "V0",
55
+ wrapSol: params.wrapSol,
56
+ unwrapSol: params.unwrapSol,
57
+ inputAccount: params.inputAccount,
58
+ outputAccount: params.outputAccount,
59
+ computeUnitPriceMicroLamports: String(params.computeUnitPriceMicroLamports)
60
+ })
61
+ });
62
+ if (!res.ok) {
63
+ const text = await res.text();
64
+ throw new Error(`Trade API serialize error: HTTP ${res.status} - ${text}`);
65
+ }
66
+ const json = await res.json();
67
+ if (!json.success) {
68
+ const errDetail = json.msg || json.message || json.error || JSON.stringify(json);
69
+ throw new Error(`Trade API serialize failed: ${errDetail}`);
70
+ }
71
+ return json;
72
+ }
73
+ async function getTokenDecimals(mint) {
74
+ if (mint === WRAPPED_SOL_MINT)
75
+ return 9;
76
+ const connection = await (0, connection_1.getConnection)();
77
+ const info = await connection.getParsedAccountInfo(new web3_js_1.PublicKey(mint));
78
+ if (!info.value || !("parsed" in info.value.data)) {
79
+ throw new Error(`Could not fetch mint info for ${mint}`);
80
+ }
81
+ return info.value.data.parsed.info.decimals;
82
+ }
83
+ function registerSwapCommands(program) {
84
+ // Trade API swap (auto-routing)
85
+ const tradeApiSwap = async (options) => {
86
+ const inputMintStr = options.inputMint;
87
+ const outputMintStr = options.outputMint;
88
+ // Get input token decimals and convert amount to lamports
89
+ const inputDecimals = await (0, output_1.withSpinner)("Fetching token info", () => getTokenDecimals(inputMintStr));
90
+ const amountLamports = BigInt(Math.floor(Number(options.amount) * 10 ** inputDecimals));
91
+ // Fetch quote from Trade API
92
+ const slippageBps = Math.round(options.slippage * 10000);
93
+ let quote;
94
+ try {
95
+ quote = await (0, output_1.withSpinner)("Fetching swap quote", () => fetchTradeQuote({
96
+ inputMint: inputMintStr,
97
+ outputMint: outputMintStr,
98
+ amount: amountLamports.toString(),
99
+ slippageBps
100
+ }));
101
+ }
102
+ catch (error) {
103
+ const msg = error instanceof Error ? error.message : String(error);
104
+ (0, output_1.logError)("Failed to fetch quote", msg);
105
+ process.exitCode = 1;
106
+ return;
107
+ }
108
+ // Get output token decimals for display
109
+ const outputDecimals = await getTokenDecimals(outputMintStr);
110
+ const estimatedOutput = Number(quote.data.outputAmount) / 10 ** outputDecimals;
111
+ const minimumOutput = Number(quote.data.otherAmountThreshold) / 10 ** outputDecimals;
112
+ // Format route display
113
+ const routeDisplay = quote.data.routePlan.length > 1
114
+ ? `${quote.data.routePlan.length}-hop via ${quote.data.routePlan.map(r => r.poolId.slice(0, 4) + "..." + r.poolId.slice(-3)).join(" -> ")}`
115
+ : `Direct via ${quote.data.routePlan[0]?.poolId.slice(0, 4)}...${quote.data.routePlan[0]?.poolId.slice(-3)}`;
116
+ if ((0, output_1.isJsonOutput)()) {
117
+ (0, output_1.logJson)({
118
+ route: quote.data.routePlan.map(r => r.poolId),
119
+ input: { amount: options.amount, mint: inputMintStr },
120
+ output: {
121
+ estimated: estimatedOutput.toString(),
122
+ minimum: minimumOutput.toString(),
123
+ mint: outputMintStr
124
+ },
125
+ priceImpactPct: quote.data.priceImpactPct,
126
+ slippage: options.slippage
127
+ });
128
+ }
129
+ else {
130
+ (0, output_1.logInfo)(`Route: ${routeDisplay}`);
131
+ (0, output_1.logInfo)(`Input: ${options.amount} (${inputMintStr.slice(0, 6)}...)`);
132
+ (0, output_1.logInfo)(`Estimated output: ${estimatedOutput.toFixed(6)} (${outputMintStr.slice(0, 6)}...)`);
133
+ (0, output_1.logInfo)(`Minimum output: ${minimumOutput.toFixed(6)}`);
134
+ (0, output_1.logInfo)(`Price impact: ${quote.data.priceImpactPct.toFixed(2)}%`);
135
+ (0, output_1.logInfo)(`Slippage: ${options.slippagePercent}%`);
136
+ }
137
+ const ok = await (0, prompt_1.promptConfirm)("Proceed with swap?", false);
138
+ if (!ok) {
139
+ (0, output_1.logInfo)("Cancelled");
140
+ return;
141
+ }
142
+ // Serialize transaction
143
+ const inputIsSol = inputMintStr === WRAPPED_SOL_MINT;
144
+ const outputIsSol = outputMintStr === WRAPPED_SOL_MINT;
145
+ // Get token accounts (ATAs) for non-SOL tokens
146
+ const inputAccount = inputIsSol
147
+ ? undefined
148
+ : (0, spl_token_1.getAssociatedTokenAddressSync)(new web3_js_1.PublicKey(inputMintStr), options.owner.publicKey).toBase58();
149
+ const outputAccount = outputIsSol
150
+ ? undefined
151
+ : (0, spl_token_1.getAssociatedTokenAddressSync)(new web3_js_1.PublicKey(outputMintStr), options.owner.publicKey).toBase58();
152
+ let txResponse;
153
+ try {
154
+ txResponse = await (0, output_1.withSpinner)("Building swap transaction", () => serializeSwapTx({
155
+ swapResponse: quote,
156
+ wallet: options.owner.publicKey.toBase58(),
157
+ wrapSol: inputIsSol,
158
+ unwrapSol: outputIsSol,
159
+ inputAccount,
160
+ outputAccount,
161
+ computeUnitPriceMicroLamports: options.priorityFeeMicroLamports
162
+ }));
163
+ }
164
+ catch (error) {
165
+ const msg = error instanceof Error ? error.message : String(error);
166
+ (0, output_1.logError)("Failed to build transaction", msg);
167
+ process.exitCode = 1;
168
+ return;
169
+ }
170
+ // Sign and send transactions
171
+ const connection = await (0, connection_1.getConnection)();
172
+ const txIds = [];
173
+ try {
174
+ for (const txData of txResponse.data) {
175
+ const txBuf = Buffer.from(txData.transaction, "base64");
176
+ const tx = web3_js_1.VersionedTransaction.deserialize(txBuf);
177
+ tx.sign([options.owner]);
178
+ const txId = await (0, output_1.withSpinner)("Sending transaction", async () => {
179
+ const sig = await connection.sendRawTransaction(tx.serialize(), {
180
+ skipPreflight: false,
181
+ preflightCommitment: "confirmed"
182
+ });
183
+ await connection.confirmTransaction(sig, "confirmed");
184
+ return sig;
185
+ });
186
+ txIds.push(txId);
187
+ }
188
+ }
189
+ catch (error) {
190
+ const message = error instanceof Error ? error.message : String(error ?? "Swap failed");
191
+ if (options.debug) {
192
+ const detail = error instanceof Error ? error.stack ?? message : (0, node_util_1.inspect)(error, { depth: 6 });
193
+ (0, output_1.logError)("Swap failed", detail);
194
+ const logs = error?.logs;
195
+ if (logs?.length) {
196
+ console.error(logs.join("\n"));
197
+ }
198
+ }
199
+ else {
200
+ (0, output_1.logError)("Swap failed", message);
201
+ }
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+ if ((0, output_1.isJsonOutput)()) {
206
+ (0, output_1.logJson)({ txIds });
207
+ }
208
+ else {
209
+ for (const txId of txIds) {
210
+ (0, output_1.logSuccess)(`Swap submitted: ${txId}`);
211
+ }
212
+ }
213
+ };
214
+ // Direct AMM swap (existing logic)
215
+ const directAmmSwap = async (options) => {
216
+ let poolId;
217
+ let inputMint;
218
+ let outputMint;
219
+ try {
220
+ poolId = new web3_js_1.PublicKey(options.poolId).toBase58();
221
+ inputMint = new web3_js_1.PublicKey(options.inputMint);
222
+ outputMint = options.outputMint ? new web3_js_1.PublicKey(options.outputMint) : undefined;
223
+ }
224
+ catch {
225
+ (0, output_1.logError)("Invalid pool or mint address");
226
+ process.exitCode = 1;
227
+ return;
228
+ }
229
+ const raydium = await (0, output_1.withSpinner)("Loading Raydium", () => (0, raydium_client_1.loadRaydium)({ owner: options.owner, disableLoadToken: true }));
230
+ let poolInfo;
231
+ let poolKeys;
232
+ let rpcData;
233
+ if (raydium.cluster === "mainnet") {
234
+ try {
235
+ const data = await (0, output_1.withSpinner)("Fetching pool info", async () => {
236
+ const apiData = await raydium.api.fetchPoolById({ ids: poolId });
237
+ const info = apiData[0];
238
+ if (!info)
239
+ throw new Error("Pool not found");
240
+ if (!VALID_AMM_PROGRAM_IDS.has(info.programId))
241
+ throw new Error("Pool is not a standard AMM pool");
242
+ const keys = await raydium.liquidity.getAmmPoolKeys(poolId);
243
+ const rpc = await raydium.liquidity.getRpcPoolInfo(poolId);
244
+ return { info, keys, rpc };
245
+ });
246
+ poolInfo = data.info;
247
+ poolKeys = data.keys;
248
+ rpcData = data.rpc;
249
+ }
250
+ catch (error) {
251
+ const msg = error instanceof Error ? error.message : String(error);
252
+ (0, output_1.logError)("Failed to fetch pool info", msg);
253
+ process.exitCode = 1;
254
+ return;
255
+ }
256
+ }
257
+ else {
258
+ const data = await (0, output_1.withSpinner)("Fetching pool info", () => raydium.liquidity.getPoolInfoFromRpc({ poolId }));
259
+ if (!data.poolInfo) {
260
+ (0, output_1.logError)("Pool not found");
261
+ process.exitCode = 1;
262
+ return;
263
+ }
264
+ if (!VALID_AMM_PROGRAM_IDS.has(data.poolInfo.programId)) {
265
+ (0, output_1.logError)("Pool is not a standard AMM pool");
266
+ process.exitCode = 1;
267
+ return;
268
+ }
269
+ poolInfo = data.poolInfo;
270
+ poolKeys = data.poolKeys;
271
+ rpcData = data.poolRpcData;
272
+ }
273
+ const mintA = poolInfo.mintA;
274
+ const mintB = poolInfo.mintB;
275
+ const mintAAddress = mintA.address;
276
+ const mintBAddress = mintB.address;
277
+ const inputMintStr = inputMint.toBase58();
278
+ if (inputMintStr !== mintAAddress && inputMintStr !== mintBAddress) {
279
+ (0, output_1.logError)("Input mint does not match pool mints");
280
+ process.exitCode = 1;
281
+ return;
282
+ }
283
+ const derivedOutputMint = inputMintStr === mintAAddress ? mintBAddress : mintAAddress;
284
+ const outputMintStr = outputMint ? outputMint.toBase58() : derivedOutputMint;
285
+ if (outputMintStr !== derivedOutputMint) {
286
+ (0, output_1.logError)("Output mint does not match pool mints");
287
+ process.exitCode = 1;
288
+ return;
289
+ }
290
+ const inputTokenInfo = inputMintStr === mintAAddress ? mintA : mintB;
291
+ const outputTokenInfo = inputMintStr === mintAAddress ? mintB : mintA;
292
+ const inputToken = buildTokenFromInfo(inputTokenInfo);
293
+ const inputTokenAmount = new raydium_sdk_v2_1.TokenAmount(inputToken, options.amount, false);
294
+ const computeOut = raydium.liquidity.computeAmountOut({
295
+ poolInfo: {
296
+ ...poolInfo,
297
+ baseReserve: rpcData.baseReserve,
298
+ quoteReserve: rpcData.quoteReserve,
299
+ status: rpcData.status.toNumber(),
300
+ version: 4
301
+ },
302
+ amountIn: inputTokenAmount.raw,
303
+ mintIn: inputMintStr,
304
+ mintOut: outputMintStr,
305
+ slippage: options.slippage
306
+ });
307
+ const outputToken = buildTokenFromInfo(outputTokenInfo);
308
+ const estimatedOut = new raydium_sdk_v2_1.TokenAmount(outputToken, computeOut.amountOut, true);
309
+ const minOut = new raydium_sdk_v2_1.TokenAmount(outputToken, computeOut.minAmountOut, true);
310
+ const outSymbol = outputTokenInfo.symbol || outputTokenInfo.name || outputTokenInfo.address.slice(0, 6);
311
+ const inSymbol = inputTokenInfo.symbol || inputTokenInfo.name || inputTokenInfo.address.slice(0, 6);
312
+ if ((0, output_1.isJsonOutput)()) {
313
+ (0, output_1.logJson)({
314
+ poolId,
315
+ input: { amount: options.amount, symbol: inSymbol, mint: inputTokenInfo.address },
316
+ output: {
317
+ estimated: estimatedOut.toExact(),
318
+ minimum: minOut.toExact(),
319
+ symbol: outSymbol,
320
+ mint: outputTokenInfo.address
321
+ },
322
+ slippage: options.slippage
323
+ });
324
+ }
325
+ else {
326
+ (0, output_1.logInfo)(`Pool: ${poolId}`);
327
+ (0, output_1.logInfo)(`Input: ${options.amount} ${inSymbol}`);
328
+ (0, output_1.logInfo)(`Estimated output: ${estimatedOut.toExact()} ${outSymbol}`);
329
+ (0, output_1.logInfo)(`Minimum output: ${minOut.toExact()} ${outSymbol}`);
330
+ (0, output_1.logInfo)(`Slippage: ${options.slippagePercent}%`);
331
+ }
332
+ const ok = await (0, prompt_1.promptConfirm)("Proceed with swap?", false);
333
+ if (!ok) {
334
+ (0, output_1.logInfo)("Cancelled");
335
+ return;
336
+ }
337
+ const DEFAULT_COMPUTE_UNITS = 600000;
338
+ const computeBudgetConfig = options.priorityFeeMicroLamports > 0 ? { units: DEFAULT_COMPUTE_UNITS, microLamports: options.priorityFeeMicroLamports } : undefined;
339
+ const inputIsSol = inputMintStr === WRAPPED_SOL_MINT;
340
+ const outputIsSol = outputMintStr === WRAPPED_SOL_MINT;
341
+ const txData = await (0, output_1.withSpinner)("Building swap transaction", () => raydium.liquidity.swap({
342
+ txVersion: raydium_sdk_v2_1.TxVersion.V0,
343
+ poolInfo,
344
+ poolKeys,
345
+ amountIn: inputTokenAmount.raw,
346
+ amountOut: computeOut.minAmountOut,
347
+ inputMint: inputMintStr,
348
+ fixedSide: "in",
349
+ config: {
350
+ associatedOnly: true,
351
+ inputUseSolBalance: inputIsSol,
352
+ outputUseSolBalance: outputIsSol
353
+ },
354
+ computeBudgetConfig
355
+ }));
356
+ let result;
357
+ try {
358
+ result = await (0, output_1.withSpinner)("Sending transaction", () => txData.execute({ sendAndConfirm: true }));
359
+ }
360
+ catch (error) {
361
+ const message = error instanceof Error ? error.message : String(error ?? "Swap failed");
362
+ if (options.debug) {
363
+ const detail = error instanceof Error ? error.stack ?? message : (0, node_util_1.inspect)(error, { depth: 6 });
364
+ (0, output_1.logError)("Swap failed", detail);
365
+ const logs = error?.logs;
366
+ if (logs?.length) {
367
+ console.error(logs.join("\n"));
368
+ }
369
+ else if (detail) {
370
+ console.error(detail);
371
+ }
372
+ }
373
+ else {
374
+ (0, output_1.logError)("Swap failed", message);
375
+ }
376
+ process.exitCode = 1;
377
+ return;
378
+ }
379
+ if ((0, output_1.isJsonOutput)()) {
380
+ (0, output_1.logJson)({ txId: result.txId });
381
+ }
382
+ else {
383
+ (0, output_1.logSuccess)(`Swap submitted: ${result.txId}`);
384
+ }
385
+ };
386
+ const swapAction = async (options) => {
387
+ // Validate required options based on mode
388
+ if (!options.inputMint || !options.amount) {
389
+ (0, output_1.logError)("Missing required options: --input-mint, --amount");
390
+ process.exitCode = 1;
391
+ return;
392
+ }
393
+ // If no pool-id, require output-mint for Trade API routing
394
+ if (!options.poolId && !options.outputMint) {
395
+ (0, output_1.logError)("--output-mint is required when --pool-id is not provided");
396
+ process.exitCode = 1;
397
+ return;
398
+ }
399
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
400
+ const slippagePercent = options.slippage ? Number(options.slippage) : config["default-slippage"];
401
+ if (!Number.isFinite(slippagePercent) || slippagePercent < 0) {
402
+ (0, output_1.logError)("Invalid slippage percent");
403
+ process.exitCode = 1;
404
+ return;
405
+ }
406
+ const slippage = slippagePercent / 100;
407
+ const priorityFeeSol = options.priorityFee ? Number(options.priorityFee) : config["priority-fee"];
408
+ if (!Number.isFinite(priorityFeeSol) || priorityFeeSol < 0) {
409
+ (0, output_1.logError)("Invalid priority fee");
410
+ process.exitCode = 1;
411
+ return;
412
+ }
413
+ const DEFAULT_COMPUTE_UNITS = 600000;
414
+ const priorityFeeLamports = priorityFeeSol * 1e9;
415
+ const priorityFeeMicroLamports = Math.round((priorityFeeLamports * 1e6) / DEFAULT_COMPUTE_UNITS);
416
+ const walletName = config.activeWallet;
417
+ if (!walletName) {
418
+ (0, output_1.logError)("No active wallet set");
419
+ process.exitCode = 1;
420
+ return;
421
+ }
422
+ // Validate mint addresses
423
+ try {
424
+ new web3_js_1.PublicKey(options.inputMint);
425
+ if (options.outputMint)
426
+ new web3_js_1.PublicKey(options.outputMint);
427
+ if (options.poolId)
428
+ new web3_js_1.PublicKey(options.poolId);
429
+ }
430
+ catch {
431
+ (0, output_1.logError)("Invalid mint or pool address");
432
+ process.exitCode = 1;
433
+ return;
434
+ }
435
+ if (Number(options.amount) <= 0) {
436
+ (0, output_1.logError)("Amount must be greater than zero");
437
+ process.exitCode = 1;
438
+ return;
439
+ }
440
+ const password = await (0, prompt_1.promptPassword)("Enter wallet password");
441
+ let owner;
442
+ try {
443
+ owner = await (0, wallet_manager_1.decryptWallet)(walletName, password);
444
+ }
445
+ catch (error) {
446
+ (0, output_1.logError)("Failed to decrypt wallet", error.message);
447
+ process.exitCode = 1;
448
+ return;
449
+ }
450
+ // Route to appropriate swap method
451
+ if (options.poolId) {
452
+ // Direct AMM swap
453
+ await directAmmSwap({
454
+ poolId: options.poolId,
455
+ inputMint: options.inputMint,
456
+ outputMint: options.outputMint,
457
+ amount: options.amount,
458
+ slippage,
459
+ slippagePercent,
460
+ priorityFeeMicroLamports,
461
+ owner,
462
+ debug: options.debug
463
+ });
464
+ }
465
+ else {
466
+ // Trade API swap (auto-routing)
467
+ await tradeApiSwap({
468
+ inputMint: options.inputMint,
469
+ outputMint: options.outputMint,
470
+ amount: options.amount,
471
+ slippage,
472
+ slippagePercent,
473
+ priorityFeeMicroLamports,
474
+ owner,
475
+ debug: options.debug
476
+ });
477
+ }
478
+ };
479
+ program
480
+ .command("swap")
481
+ .description("Swap tokens (omit --pool-id for auto-routing via Trade API)")
482
+ .option("--pool-id <pool>", "AMM pool address (omit for auto-routing)")
483
+ .requiredOption("--input-mint <mint>", "Input token mint")
484
+ .requiredOption("--amount <number>", "Amount to swap")
485
+ .option("--output-mint <mint>", "Output token mint (required if --pool-id not provided)")
486
+ .option("--slippage <percent>", "Slippage tolerance")
487
+ .option("--priority-fee <sol>", "Priority fee in SOL")
488
+ .option("--debug", "Print full error object on failure")
489
+ .action(swapAction);
490
+ }
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerTokenCommands = registerTokenCommands;
4
+ const codex_sdk_1 = require("../../lib/codex-sdk");
5
+ const output_1 = require("../../lib/output");
6
+ function registerTokenCommands(program) {
7
+ const tokens = program.command("tokens").description("Token utilities");
8
+ tokens
9
+ .command("list")
10
+ .description("List tokens")
11
+ .option("--search <text>", "Search by symbol, name, or mint")
12
+ .option("--limit <number>", "Max results (default 10, max 50)", "10")
13
+ .option("--offset <number>", "Offset for pagination", "0")
14
+ .action(async (options) => {
15
+ const limitInput = Number(options.limit);
16
+ const offsetInput = Number(options.offset);
17
+ const limit = Number.isFinite(limitInput) ? Math.min(Math.max(limitInput, 1), 50) : 10;
18
+ const offset = Number.isFinite(offsetInput) && offsetInput >= 0 ? offsetInput : 0;
19
+ const filters = {
20
+ liquidity: { gte: 25000 },
21
+ txnCount24: { gte: 50 },
22
+ network: [codex_sdk_1.SOLANA_CODEX_NETWORK_ID],
23
+ creatorAddress: null,
24
+ potentialScam: false
25
+ };
26
+ const filterQuery = `
27
+ query FilterTokens(
28
+ $filters: TokenFilters
29
+ $statsType: TokenPairStatisticsType
30
+ $excludeTokens: [String]
31
+ $phrase: String
32
+ $tokens: [String]
33
+ $rankings: [TokenRanking]
34
+ $limit: Int
35
+ $offset: Int
36
+ ) {
37
+ filterTokens(
38
+ filters: $filters
39
+ statsType: $statsType
40
+ excludeTokens: $excludeTokens
41
+ phrase: $phrase
42
+ tokens: $tokens
43
+ rankings: $rankings
44
+ limit: $limit
45
+ offset: $offset
46
+ ) {
47
+ results {
48
+ priceUSD
49
+ volume24
50
+ token {
51
+ address
52
+ decimals
53
+ name
54
+ symbol
55
+ }
56
+ }
57
+ count
58
+ page
59
+ }
60
+ }
61
+ `;
62
+ const sdk = (0, codex_sdk_1.getCodexClient)();
63
+ const response = await (0, output_1.withSpinner)("Loading token list", () => sdk.send(filterQuery, {
64
+ filters,
65
+ statsType: "FILTERED",
66
+ phrase: options.search ?? null,
67
+ rankings: [{ attribute: "volume24", direction: "DESC" }],
68
+ limit,
69
+ offset
70
+ }));
71
+ const results = response.filterTokens?.results ?? [];
72
+ if ((0, output_1.isJsonOutput)()) {
73
+ (0, output_1.logJson)({
74
+ tokens: results,
75
+ count: response.filterTokens?.count ?? results.length,
76
+ page: response.filterTokens?.page ?? 0
77
+ });
78
+ return;
79
+ }
80
+ if (results.length === 0) {
81
+ (0, output_1.logInfo)("No tokens found");
82
+ return;
83
+ }
84
+ results.forEach((item) => {
85
+ const token = item.token;
86
+ (0, output_1.logInfo)(`${token.symbol} ${token.name}`.trim());
87
+ (0, output_1.logInfo)(` mint: ${token.address}`);
88
+ (0, output_1.logInfo)(` decimals: ${token.decimals}`);
89
+ (0, output_1.logInfo)(` priceUSD: ${item.priceUSD}`);
90
+ (0, output_1.logInfo)(` volume24: ${item.volume24}`);
91
+ });
92
+ });
93
+ }