@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,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
|
+
}
|