naracli 1.0.85 → 1.0.87
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/dist/nara-cli-bundle.cjs +51365 -50853
- package/package.json +2 -2
- package/src/cli/commands/dex.ts +618 -25
- package/src/cli/commands/quest.ts +2 -10
package/src/cli/commands/dex.ts
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
import { Connection, PublicKey, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
|
7
7
|
import BN from "bn.js";
|
|
8
|
+
import DLMM from "@meteora-ag/dlmm";
|
|
8
9
|
import { loadWallet, getRpcUrl } from "../utils/wallet";
|
|
9
10
|
import {
|
|
10
11
|
printError,
|
|
@@ -28,6 +29,37 @@ function identifyPoolType(owner: string): PoolType | null {
|
|
|
28
29
|
return null;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
/** Get current point (slot or timestamp) without relying on RPC getBlockTime which can fail on recent slots. */
|
|
33
|
+
async function getCurrentPointSafe(connection: Connection, activationType: number): Promise<BN> {
|
|
34
|
+
// activationType 0 = slot, 1 = timestamp
|
|
35
|
+
if (activationType === 1) {
|
|
36
|
+
return new BN(Math.floor(Date.now() / 1000));
|
|
37
|
+
}
|
|
38
|
+
const slot = await connection.getSlot();
|
|
39
|
+
return new BN(slot);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const KNOWN_TOKENS: Record<string, string> = {
|
|
43
|
+
"So11111111111111111111111111111111111111112": "NARA",
|
|
44
|
+
"8P7UGWjq86N3WUmwEgKeGHJZLcoMJqr5jnRUmeBN7YwR": "USDC",
|
|
45
|
+
"Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB": "USDT",
|
|
46
|
+
"7fKh7DqPZmsYPHdGvt9Qw2rZkSEGp9F5dBa3XuuuhavU": "SOL",
|
|
47
|
+
"AqJX47z8UT6k6gFpJjzvcAAP4NJkfykW8U8za1evry7J": "POINT",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
function tokenSymbol(mint: string): string {
|
|
51
|
+
return KNOWN_TOKENS[mint] ?? mint.slice(0, 4) + "..";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Resolve token symbol shortcut (e.g. "NARA") to mint address, or pass through if already a pubkey. */
|
|
55
|
+
function resolveTokenMint(input: string): string {
|
|
56
|
+
const upper = input.toUpperCase();
|
|
57
|
+
for (const [mint, symbol] of Object.entries(KNOWN_TOKENS)) {
|
|
58
|
+
if (symbol === upper) return mint;
|
|
59
|
+
}
|
|
60
|
+
return input;
|
|
61
|
+
}
|
|
62
|
+
|
|
31
63
|
async function getMintDecimals(connection: Connection, mint: PublicKey): Promise<number> {
|
|
32
64
|
try {
|
|
33
65
|
const info = await connection.getParsedAccountInfo(mint);
|
|
@@ -37,6 +69,480 @@ async function getMintDecimals(connection: Connection, mint: PublicKey): Promise
|
|
|
37
69
|
return 9;
|
|
38
70
|
}
|
|
39
71
|
|
|
72
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
73
|
+
// POOLS (by token)
|
|
74
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
75
|
+
|
|
76
|
+
// Account offsets for mint fields (8-byte discriminator included)
|
|
77
|
+
const CPAMM_TOKENA_OFFSET = 168;
|
|
78
|
+
const CPAMM_TOKENB_OFFSET = 200;
|
|
79
|
+
const DLMM_TOKENX_OFFSET = 88;
|
|
80
|
+
const DLMM_TOKENY_OFFSET = 120;
|
|
81
|
+
const DLMM_LBPAIR_SIZE = 904;
|
|
82
|
+
const DBC_BASEMINT_OFFSET = 136;
|
|
83
|
+
|
|
84
|
+
async function findProgramAccountsByMint(
|
|
85
|
+
connection: Connection, programId: string, mint: PublicKey, offsets: number[]
|
|
86
|
+
): Promise<PublicKey[]> {
|
|
87
|
+
const pubkeys = new Set<string>();
|
|
88
|
+
for (const offset of offsets) {
|
|
89
|
+
try {
|
|
90
|
+
const accounts = await connection.getProgramAccounts(new PublicKey(programId), {
|
|
91
|
+
filters: [{ memcmp: { offset, bytes: mint.toBase58() } }],
|
|
92
|
+
dataSlice: { offset: 0, length: 0 },
|
|
93
|
+
});
|
|
94
|
+
for (const a of accounts) pubkeys.add(a.pubkey.toBase58());
|
|
95
|
+
} catch {}
|
|
96
|
+
}
|
|
97
|
+
return Array.from(pubkeys).map(s => new PublicKey(s));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function findDlmmPoolsByMint(connection: Connection, mint: PublicKey): Promise<PublicKey[]> {
|
|
101
|
+
const pubkeys = new Set<string>();
|
|
102
|
+
for (const offset of [DLMM_TOKENX_OFFSET, DLMM_TOKENY_OFFSET]) {
|
|
103
|
+
try {
|
|
104
|
+
const accounts = await connection.getProgramAccounts(new PublicKey(DLMM_PROGRAM_ID), {
|
|
105
|
+
filters: [
|
|
106
|
+
{ dataSize: DLMM_LBPAIR_SIZE },
|
|
107
|
+
{ memcmp: { offset, bytes: mint.toBase58() } },
|
|
108
|
+
],
|
|
109
|
+
dataSlice: { offset: 0, length: 0 },
|
|
110
|
+
});
|
|
111
|
+
for (const a of accounts) pubkeys.add(a.pubkey.toBase58());
|
|
112
|
+
} catch {}
|
|
113
|
+
}
|
|
114
|
+
return Array.from(pubkeys).map(s => new PublicKey(s));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function handlePools(token: string, options: GlobalOptions) {
|
|
118
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
119
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
120
|
+
const tokenMint = new PublicKey(token);
|
|
121
|
+
const tokenDecimals = await getMintDecimals(connection, tokenMint);
|
|
122
|
+
|
|
123
|
+
if (!options.json) printInfo(`Searching pools containing ${token}...`);
|
|
124
|
+
|
|
125
|
+
// Find pool addresses for each pool type via memcmp filters
|
|
126
|
+
const [cpammAddrs, dlmmAddrs, dbcAddrs] = await Promise.all([
|
|
127
|
+
findProgramAccountsByMint(connection, CPAMM_PROGRAM_ID, tokenMint, [CPAMM_TOKENA_OFFSET, CPAMM_TOKENB_OFFSET]),
|
|
128
|
+
findDlmmPoolsByMint(connection, tokenMint),
|
|
129
|
+
findProgramAccountsByMint(connection, DBC_PROGRAM_ID, tokenMint, [DBC_BASEMINT_OFFSET]),
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const results: any[] = [];
|
|
133
|
+
|
|
134
|
+
// Decode CPAMM pools
|
|
135
|
+
if (cpammAddrs.length > 0) {
|
|
136
|
+
try {
|
|
137
|
+
const { CpAmm, getReservesAmountForConcentratedLiquidity } = await import("@meteora-ag/cp-amm-sdk");
|
|
138
|
+
const cpAmm = new CpAmm(connection);
|
|
139
|
+
for (const addr of cpammAddrs) {
|
|
140
|
+
try {
|
|
141
|
+
const state = await cpAmm.fetchPoolState(addr);
|
|
142
|
+
const decA = await getMintDecimals(connection, state.tokenAMint);
|
|
143
|
+
const decB = await getMintDecimals(connection, state.tokenBMint);
|
|
144
|
+
const [resA, resB] = getReservesAmountForConcentratedLiquidity(
|
|
145
|
+
state.sqrtPrice, state.sqrtMinPrice, state.sqrtMaxPrice, state.liquidity
|
|
146
|
+
);
|
|
147
|
+
const amountA = Number(resA.toString()) / 10 ** decA;
|
|
148
|
+
const amountB = Number(resB.toString()) / 10 ** decB;
|
|
149
|
+
// price = (sqrtPrice / 2^64)^2 * 10^(decA - decB) = B per A
|
|
150
|
+
const sqrtNum = Number(state.sqrtPrice.toString()) / 2 ** 64;
|
|
151
|
+
const priceBA = sqrtNum * sqrtNum * 10 ** (decA - decB);
|
|
152
|
+
|
|
153
|
+
results.push({
|
|
154
|
+
type: "DAMM v2",
|
|
155
|
+
pool: addr.toBase58(),
|
|
156
|
+
tokenA: state.tokenAMint.toBase58(),
|
|
157
|
+
tokenB: state.tokenBMint.toBase58(),
|
|
158
|
+
amountA, amountB,
|
|
159
|
+
price: priceBA,
|
|
160
|
+
});
|
|
161
|
+
} catch {}
|
|
162
|
+
}
|
|
163
|
+
} catch {}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Decode DLMM pools
|
|
167
|
+
if (dlmmAddrs.length > 0) {
|
|
168
|
+
try {
|
|
169
|
+
const { default: DLMM } = await import("@meteora-ag/dlmm");
|
|
170
|
+
const dlmms = await DLMM.createMultiple(connection, dlmmAddrs);
|
|
171
|
+
for (const dlmm of dlmms) {
|
|
172
|
+
try {
|
|
173
|
+
const activeBin = await dlmm.getActiveBin();
|
|
174
|
+
const reserves = await Promise.all([
|
|
175
|
+
connection.getTokenAccountBalance(dlmm.tokenX.reserve).catch(() => null),
|
|
176
|
+
connection.getTokenAccountBalance(dlmm.tokenY.reserve).catch(() => null),
|
|
177
|
+
]);
|
|
178
|
+
const amountX = reserves[0] ? Number(reserves[0].value.uiAmount ?? 0) : 0;
|
|
179
|
+
const amountY = reserves[1] ? Number(reserves[1].value.uiAmount ?? 0) : 0;
|
|
180
|
+
results.push({
|
|
181
|
+
type: "DLMM",
|
|
182
|
+
pool: dlmm.pubkey.toBase58(),
|
|
183
|
+
tokenA: dlmm.tokenX.publicKey.toBase58(),
|
|
184
|
+
tokenB: dlmm.tokenY.publicKey.toBase58(),
|
|
185
|
+
amountA: amountX,
|
|
186
|
+
amountB: amountY,
|
|
187
|
+
price: Number(activeBin.price),
|
|
188
|
+
});
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
} catch {}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Decode DBC pools
|
|
195
|
+
if (dbcAddrs.length > 0) {
|
|
196
|
+
try {
|
|
197
|
+
const { DynamicBondingCurveClient } = await import("@meteora-ag/dynamic-bonding-curve-sdk");
|
|
198
|
+
const client = DynamicBondingCurveClient.create(connection);
|
|
199
|
+
for (const addr of dbcAddrs) {
|
|
200
|
+
try {
|
|
201
|
+
const pool = await client.pool.getPool(addr);
|
|
202
|
+
const config = await client.pool.getPoolConfig(pool.config);
|
|
203
|
+
const decBase = await getMintDecimals(connection, pool.baseMint);
|
|
204
|
+
const decQuote = await getMintDecimals(connection, pool.quoteMint);
|
|
205
|
+
|
|
206
|
+
const baseBal = await connection.getTokenAccountBalance(pool.baseVault).catch(() => null);
|
|
207
|
+
const quoteBal = await connection.getTokenAccountBalance(pool.quoteVault).catch(() => null);
|
|
208
|
+
const amountBase = baseBal ? Number(baseBal.value.uiAmount ?? 0) : 0;
|
|
209
|
+
const amountQuote = quoteBal ? Number(quoteBal.value.uiAmount ?? 0) : 0;
|
|
210
|
+
|
|
211
|
+
// sqrtPrice Q64 → price = (sqrtPrice / 2^64)^2 * 10^(decBase - decQuote)
|
|
212
|
+
const sqrtNum = Number(pool.sqrtPrice?.toString() ?? "0") / 2 ** 64;
|
|
213
|
+
const price = sqrtNum * sqrtNum * 10 ** (decBase - decQuote);
|
|
214
|
+
|
|
215
|
+
results.push({
|
|
216
|
+
type: "DBC",
|
|
217
|
+
pool: addr.toBase58(),
|
|
218
|
+
tokenA: pool.baseMint.toBase58(),
|
|
219
|
+
tokenB: pool.quoteMint.toBase58(),
|
|
220
|
+
amountA: amountBase,
|
|
221
|
+
amountB: amountQuote,
|
|
222
|
+
price,
|
|
223
|
+
});
|
|
224
|
+
} catch {}
|
|
225
|
+
}
|
|
226
|
+
} catch {}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (options.json) {
|
|
230
|
+
formatOutput(results, true);
|
|
231
|
+
} else {
|
|
232
|
+
if (results.length === 0) {
|
|
233
|
+
printInfo("No pools found for this token.");
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
console.log("");
|
|
237
|
+
for (const r of results) {
|
|
238
|
+
const symA = tokenSymbol(r.tokenA);
|
|
239
|
+
const symB = tokenSymbol(r.tokenB);
|
|
240
|
+
console.log(` [${r.type}] ${symA}/${symB} ${r.pool}`);
|
|
241
|
+
console.log(` ${symA}: ${r.tokenA}`);
|
|
242
|
+
console.log(` ${symB}: ${r.tokenB}`);
|
|
243
|
+
console.log(` Reserves: ${r.amountA.toFixed(4)} ${symA} / ${r.amountB.toFixed(4)} ${symB}`);
|
|
244
|
+
console.log(` Price: 1 ${symA} = ${r.price.toFixed(6)} ${symB}`);
|
|
245
|
+
console.log("");
|
|
246
|
+
}
|
|
247
|
+
console.log(` Total: ${results.length} pool(s)`);
|
|
248
|
+
console.log("");
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
253
|
+
// SMART ROUTER (via https://smart-router.nara.build/)
|
|
254
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
255
|
+
|
|
256
|
+
const SMART_ROUTER_URL = process.env.SMART_ROUTER_URL || "https://smart-router.nara.build";
|
|
257
|
+
|
|
258
|
+
async function handleSmartQuote(
|
|
259
|
+
inputToken: string, outputToken: string, amount: string,
|
|
260
|
+
options: GlobalOptions
|
|
261
|
+
) {
|
|
262
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
263
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
264
|
+
const inputMint = new PublicKey(resolveTokenMint(inputToken));
|
|
265
|
+
const outputMint = new PublicKey(resolveTokenMint(outputToken));
|
|
266
|
+
const inputDecimals = await getMintDecimals(connection, inputMint);
|
|
267
|
+
const outputDecimals = await getMintDecimals(connection, outputMint);
|
|
268
|
+
const rawAmount = BigInt(Math.floor(parseFloat(amount) * 10 ** inputDecimals));
|
|
269
|
+
|
|
270
|
+
if (!options.json) printInfo("Fetching quote from smart router...");
|
|
271
|
+
const url = `${SMART_ROUTER_URL}/quote?input_mint=${inputMint.toBase58()}&output_mint=${outputMint.toBase58()}&amount_in=${rawAmount}`;
|
|
272
|
+
const res = await fetch(url);
|
|
273
|
+
const data = await res.json() as any;
|
|
274
|
+
if (!res.ok || data.error) {
|
|
275
|
+
printError(`Smart router: ${data.error ?? res.status}`);
|
|
276
|
+
process.exit(1);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const amountInNum = Number(data.amount_in) / 10 ** inputDecimals;
|
|
280
|
+
const amountOutNum = Number(data.amount_out) / 10 ** outputDecimals;
|
|
281
|
+
const minOutNum = Number(data.min_amount_out) / 10 ** outputDecimals;
|
|
282
|
+
const price = amountInNum > 0 ? amountOutNum / amountInNum : 0;
|
|
283
|
+
|
|
284
|
+
if (options.json) {
|
|
285
|
+
formatOutput(data, true);
|
|
286
|
+
} else {
|
|
287
|
+
const symIn = tokenSymbol(inputMint.toBase58());
|
|
288
|
+
const symOut = tokenSymbol(outputMint.toBase58());
|
|
289
|
+
console.log("");
|
|
290
|
+
console.log(` Input: ${amountInNum} ${symIn}`);
|
|
291
|
+
console.log(` Output: ${amountOutNum.toFixed(outputDecimals)} ${symOut}`);
|
|
292
|
+
console.log(` Min output: ${minOutNum.toFixed(outputDecimals)} ${symOut}`);
|
|
293
|
+
console.log(` Price: 1 ${symIn} = ${price.toFixed(6)} ${symOut}`);
|
|
294
|
+
if (Array.isArray(data.route_legs) && data.route_legs.length > 0) {
|
|
295
|
+
console.log(` Route:`);
|
|
296
|
+
for (const leg of data.route_legs) {
|
|
297
|
+
for (const hop of leg.path ?? []) {
|
|
298
|
+
const symHopIn = tokenSymbol(hop.token_in);
|
|
299
|
+
const symHopOut = tokenSymbol(hop.token_out);
|
|
300
|
+
console.log(` ${symHopIn} → ${symHopOut} [${hop.pool_type}] ${hop.pool_id}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
console.log("");
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function handleSmartSwap(
|
|
309
|
+
inputToken: string, outputToken: string, amount: string,
|
|
310
|
+
options: GlobalOptions & { slippage?: string }
|
|
311
|
+
) {
|
|
312
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
313
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
314
|
+
const wallet = await loadWallet(options.wallet);
|
|
315
|
+
const inputMint = new PublicKey(resolveTokenMint(inputToken));
|
|
316
|
+
const outputMint = new PublicKey(resolveTokenMint(outputToken));
|
|
317
|
+
const inputDecimals = await getMintDecimals(connection, inputMint);
|
|
318
|
+
const outputDecimals = await getMintDecimals(connection, outputMint);
|
|
319
|
+
const rawAmount = BigInt(Math.floor(parseFloat(amount) * 10 ** inputDecimals));
|
|
320
|
+
const slippageBps = options.slippage ? Math.round(parseFloat(options.slippage) * 100) : 100;
|
|
321
|
+
|
|
322
|
+
const symIn = tokenSymbol(inputMint.toBase58());
|
|
323
|
+
const symOut = tokenSymbol(outputMint.toBase58());
|
|
324
|
+
|
|
325
|
+
if (!options.json) printInfo(`Creating order: ${amount} ${symIn} → ${symOut} (slippage ${slippageBps / 100}%)...`);
|
|
326
|
+
const orderRes = await fetch(`${SMART_ROUTER_URL}/order`, {
|
|
327
|
+
method: "POST",
|
|
328
|
+
headers: { "Content-Type": "application/json" },
|
|
329
|
+
body: JSON.stringify({
|
|
330
|
+
input_mint: inputMint.toBase58(),
|
|
331
|
+
output_mint: outputMint.toBase58(),
|
|
332
|
+
amount_in: Number(rawAmount),
|
|
333
|
+
slippage_bps: slippageBps,
|
|
334
|
+
user_pubkey: wallet.publicKey.toBase58(),
|
|
335
|
+
}),
|
|
336
|
+
});
|
|
337
|
+
const order = await orderRes.json() as any;
|
|
338
|
+
if (!orderRes.ok || order.error) {
|
|
339
|
+
printError(`Order failed: ${order.error ?? orderRes.status}`);
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (!options.json) {
|
|
344
|
+
const amountOutNum = Number(order.amount_out) / 10 ** outputDecimals;
|
|
345
|
+
const minOutNum = Number(order.min_amount_out) / 10 ** outputDecimals;
|
|
346
|
+
console.log(` Expected out: ${amountOutNum.toFixed(outputDecimals)} ${symOut}`);
|
|
347
|
+
console.log(` Min out: ${minOutNum.toFixed(outputDecimals)} ${symOut}`);
|
|
348
|
+
console.log(` Order ID: ${order.order_id}`);
|
|
349
|
+
printInfo("Signing and submitting...");
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Sign tx (supports both versioned and legacy)
|
|
353
|
+
const { VersionedTransaction, Transaction } = await import("@solana/web3.js");
|
|
354
|
+
const txBuf = Buffer.from(order.unsigned_tx_base64, "base64");
|
|
355
|
+
let signedB64: string;
|
|
356
|
+
try {
|
|
357
|
+
const vtx = VersionedTransaction.deserialize(new Uint8Array(txBuf));
|
|
358
|
+
vtx.sign([wallet]);
|
|
359
|
+
signedB64 = Buffer.from(vtx.serialize()).toString("base64");
|
|
360
|
+
} catch {
|
|
361
|
+
const ltx = Transaction.from(txBuf);
|
|
362
|
+
ltx.sign(wallet);
|
|
363
|
+
signedB64 = ltx.serialize().toString("base64");
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const execRes = await fetch(`${SMART_ROUTER_URL}/execute`, {
|
|
367
|
+
method: "POST",
|
|
368
|
+
headers: { "Content-Type": "application/json" },
|
|
369
|
+
body: JSON.stringify({ order_id: order.order_id, signed_tx_base64: signedB64 }),
|
|
370
|
+
});
|
|
371
|
+
const exec = await execRes.json() as any;
|
|
372
|
+
if (!execRes.ok || exec.error) {
|
|
373
|
+
printError(`Execute failed: ${exec.error ?? execRes.status}`);
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (options.json) {
|
|
378
|
+
formatOutput({ order, exec }, true);
|
|
379
|
+
} else {
|
|
380
|
+
printSuccess(`Swap ${exec.confirmed ? "confirmed" : "submitted"}!`);
|
|
381
|
+
console.log(` Transaction: ${exec.signature}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
386
|
+
// QUOTE
|
|
387
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
388
|
+
|
|
389
|
+
async function handleQuote(
|
|
390
|
+
pool: string, inputToken: string, amount: string,
|
|
391
|
+
options: GlobalOptions & { slippage?: string }
|
|
392
|
+
) {
|
|
393
|
+
const rpcUrl = getRpcUrl(options.rpcUrl);
|
|
394
|
+
const connection = new Connection(rpcUrl, "confirmed");
|
|
395
|
+
const poolAddress = new PublicKey(pool);
|
|
396
|
+
const inputMint = new PublicKey(resolveTokenMint(inputToken));
|
|
397
|
+
const slippageBps = options.slippage ? Math.round(parseFloat(options.slippage) * 100) : 100;
|
|
398
|
+
|
|
399
|
+
// Detect pool type
|
|
400
|
+
if (!options.json) printInfo("Detecting pool type...");
|
|
401
|
+
const accountInfo = await connection.getAccountInfo(poolAddress);
|
|
402
|
+
if (!accountInfo) { printError("Pool account not found"); process.exit(1); }
|
|
403
|
+
|
|
404
|
+
const poolType = identifyPoolType(accountInfo.owner.toBase58());
|
|
405
|
+
if (!poolType) {
|
|
406
|
+
printError(`Unrecognized pool. Owner: ${accountInfo.owner.toBase58()}. Supported: DAMM v2, DLMM, DBC`);
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
if (!options.json) printInfo(`Pool type: ${poolType.toUpperCase()}`);
|
|
410
|
+
|
|
411
|
+
const inputDecimals = await getMintDecimals(connection, inputMint);
|
|
412
|
+
const rawAmount = new BN(Math.floor(parseFloat(amount) * 10 ** inputDecimals).toString());
|
|
413
|
+
|
|
414
|
+
let outputMint: PublicKey;
|
|
415
|
+
let amountOut: BN;
|
|
416
|
+
let minOut: BN;
|
|
417
|
+
let fee: BN;
|
|
418
|
+
let outputDecimals = 9;
|
|
419
|
+
|
|
420
|
+
let feeBps: number | null = null;
|
|
421
|
+
|
|
422
|
+
if (poolType === "cpamm") {
|
|
423
|
+
const {
|
|
424
|
+
CpAmm, swapQuoteExactInput,
|
|
425
|
+
getBaseFeeHandlerFromPodAlignedData, feeNumeratorToBps,
|
|
426
|
+
} = await import("@meteora-ag/cp-amm-sdk");
|
|
427
|
+
const cpAmm = new CpAmm(connection);
|
|
428
|
+
const poolState = await cpAmm.fetchPoolState(poolAddress);
|
|
429
|
+
|
|
430
|
+
// Decode pool fee from poolFees.baseFee
|
|
431
|
+
try {
|
|
432
|
+
const rawData = (poolState.poolFees as any)?.baseFee?.baseFeeInfo?.data;
|
|
433
|
+
if (rawData) {
|
|
434
|
+
let bytes: number[];
|
|
435
|
+
if (Array.isArray(rawData)) bytes = rawData;
|
|
436
|
+
else if (rawData instanceof Uint8Array) bytes = Array.from(rawData);
|
|
437
|
+
else if ((rawData as any)?.data && Array.isArray((rawData as any).data)) bytes = (rawData as any).data;
|
|
438
|
+
else bytes = Array.from(Buffer.from(rawData));
|
|
439
|
+
const handler = getBaseFeeHandlerFromPodAlignedData(bytes);
|
|
440
|
+
feeBps = Number(feeNumeratorToBps(handler.getMinFeeNumerator()));
|
|
441
|
+
}
|
|
442
|
+
} catch {}
|
|
443
|
+
const aToB = inputMint.equals(poolState.tokenAMint);
|
|
444
|
+
if (!aToB && !inputMint.equals(poolState.tokenBMint)) {
|
|
445
|
+
printError(`Input token not in pool`); process.exit(1);
|
|
446
|
+
}
|
|
447
|
+
outputMint = aToB ? poolState.tokenBMint : poolState.tokenAMint;
|
|
448
|
+
outputDecimals = await getMintDecimals(connection, outputMint);
|
|
449
|
+
|
|
450
|
+
const currentPoint = await getCurrentPointSafe(connection, poolState.activationType);
|
|
451
|
+
const quote = swapQuoteExactInput(
|
|
452
|
+
poolState, currentPoint, rawAmount,
|
|
453
|
+
slippageBps, aToB, false,
|
|
454
|
+
inputDecimals, outputDecimals,
|
|
455
|
+
);
|
|
456
|
+
const q = quote as any;
|
|
457
|
+
amountOut = new BN(q.outputAmount?.toString() ?? "0");
|
|
458
|
+
minOut = q.minimumAmountOut ?? new BN(0);
|
|
459
|
+
// Sum LP/protocol/referral fees (all in input token lamports)
|
|
460
|
+
fee = new BN((q.claimingFee ?? "0").toString())
|
|
461
|
+
.add(new BN((q.protocolFee ?? "0").toString()))
|
|
462
|
+
.add(new BN((q.compoundingFee ?? "0").toString()))
|
|
463
|
+
.add(new BN((q.referralFee ?? "0").toString()));
|
|
464
|
+
} else if (poolType === "dlmm") {
|
|
465
|
+
const { default: DLMM } = await import("@meteora-ag/dlmm");
|
|
466
|
+
const dlmm = await DLMM.create(connection, poolAddress);
|
|
467
|
+
const swapForY = inputMint.equals(dlmm.tokenX.publicKey);
|
|
468
|
+
if (!swapForY && !inputMint.equals(dlmm.tokenY.publicKey)) {
|
|
469
|
+
printError(`Input token not in pool`); process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
outputMint = swapForY ? dlmm.tokenY.publicKey : dlmm.tokenX.publicKey;
|
|
472
|
+
outputDecimals = await getMintDecimals(connection, outputMint);
|
|
473
|
+
|
|
474
|
+
const binArrays = await dlmm.getBinArrayForSwap(swapForY);
|
|
475
|
+
const quote = dlmm.swapQuote(rawAmount, swapForY, new BN(slippageBps), binArrays);
|
|
476
|
+
amountOut = quote.outAmount;
|
|
477
|
+
minOut = quote.minOutAmount;
|
|
478
|
+
fee = quote.fee;
|
|
479
|
+
// Compute DLMM fee bps from baseFactor + binStep + baseFeePowerFactor
|
|
480
|
+
try {
|
|
481
|
+
const params = (dlmm.lbPair as any).parameters;
|
|
482
|
+
const baseFactor = Number(params?.baseFactor ?? 0);
|
|
483
|
+
const binStep = Number((dlmm.lbPair as any).binStep ?? 0);
|
|
484
|
+
const pf = Number(params?.baseFeePowerFactor ?? 0);
|
|
485
|
+
if (baseFactor > 0 && binStep > 0) {
|
|
486
|
+
const fi: any = (DLMM as any).calculateFeeInfo(baseFactor, binStep, pf);
|
|
487
|
+
feeBps = Number(fi.baseFeeRatePercentage) * 100; // percent → bps
|
|
488
|
+
}
|
|
489
|
+
} catch {}
|
|
490
|
+
} else {
|
|
491
|
+
// DBC
|
|
492
|
+
const { DynamicBondingCurveClient } = await import("@meteora-ag/dynamic-bonding-curve-sdk");
|
|
493
|
+
const client = DynamicBondingCurveClient.create(connection);
|
|
494
|
+
const p = await client.pool.getPool(poolAddress);
|
|
495
|
+
const swapBaseForQuote = inputMint.equals(p.baseMint);
|
|
496
|
+
if (!swapBaseForQuote && !inputMint.equals(p.quoteMint)) {
|
|
497
|
+
printError(`Input token not in pool`); process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
outputMint = swapBaseForQuote ? p.quoteMint : p.baseMint;
|
|
500
|
+
outputDecimals = await getMintDecimals(connection, outputMint);
|
|
501
|
+
|
|
502
|
+
const config = await client.pool.getPoolConfig(p.config);
|
|
503
|
+
const quote = client.pool.swapQuote({
|
|
504
|
+
virtualPool: p, config, swapBaseForQuote,
|
|
505
|
+
amountIn: rawAmount, slippageBps, hasReferral: false,
|
|
506
|
+
currentPoint: p.currentPoint,
|
|
507
|
+
});
|
|
508
|
+
amountOut = new BN((quote.outputAmount ?? quote.amountOut ?? "0").toString());
|
|
509
|
+
minOut = quote.minimumAmountOut ?? new BN(0);
|
|
510
|
+
fee = new BN((quote.fee ?? quote.totalFee ?? "0").toString());
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const amountInNum = Number(rawAmount.toString()) / 10 ** inputDecimals;
|
|
514
|
+
const amountOutNum = Number(amountOut.toString()) / 10 ** outputDecimals;
|
|
515
|
+
const minOutNum = Number(minOut.toString()) / 10 ** outputDecimals;
|
|
516
|
+
// If feeBps known, compute fee from input; otherwise fallback to SDK-reported fee
|
|
517
|
+
const feeNum = feeBps !== null ? amountInNum * (feeBps / 10000) : Number(fee.toString()) / 10 ** inputDecimals;
|
|
518
|
+
const price = amountInNum > 0 ? amountOutNum / amountInNum : 0;
|
|
519
|
+
|
|
520
|
+
if (options.json) {
|
|
521
|
+
formatOutput({
|
|
522
|
+
poolType, inputMint: inputMint.toBase58(), outputMint: outputMint.toBase58(),
|
|
523
|
+
amountIn: amountInNum, amountOut: amountOutNum,
|
|
524
|
+
minOut: minOutNum, fee: feeNum, feeBps, price,
|
|
525
|
+
slippageBps,
|
|
526
|
+
}, true);
|
|
527
|
+
} else {
|
|
528
|
+
console.log("");
|
|
529
|
+
const symIn = tokenSymbol(inputMint.toBase58());
|
|
530
|
+
const symOut = tokenSymbol(outputMint.toBase58());
|
|
531
|
+
console.log(` Pool type: ${poolType.toUpperCase()}`);
|
|
532
|
+
console.log(` Input: ${amountInNum} ${symIn} (${inputMint.toBase58()})`);
|
|
533
|
+
console.log(` Output: ${amountOutNum.toFixed(outputDecimals)} ${symOut} (${outputMint.toBase58()})`);
|
|
534
|
+
console.log(` Min output: ${minOutNum.toFixed(outputDecimals)} ${symOut} (@ ${slippageBps / 100}% slippage)`);
|
|
535
|
+
if (feeBps !== null) {
|
|
536
|
+
console.log(` Fee: ${(feeBps / 100).toFixed(2)}%`);
|
|
537
|
+
} else {
|
|
538
|
+
const feeStr = feeNum.toFixed(inputDecimals).replace(/\.?0+$/, "");
|
|
539
|
+
console.log(` Fee: ${feeStr} ${symIn}`);
|
|
540
|
+
}
|
|
541
|
+
console.log(` Price: 1 ${symIn} = ${price.toFixed(6)} ${symOut}`);
|
|
542
|
+
console.log("");
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
40
546
|
// ═══════════════════════════════════════════════════════════════════
|
|
41
547
|
// SWAP
|
|
42
548
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -49,7 +555,7 @@ async function swapCpAmm(
|
|
|
49
555
|
amountIn: BN,
|
|
50
556
|
slippageBps: number,
|
|
51
557
|
) {
|
|
52
|
-
const { CpAmm, SwapMode } = await import("@meteora-ag/cp-amm-sdk");
|
|
558
|
+
const { CpAmm, SwapMode, swapQuoteExactInput } = await import("@meteora-ag/cp-amm-sdk");
|
|
53
559
|
const cpAmm = new CpAmm(connection);
|
|
54
560
|
const poolState = await cpAmm.fetchPoolState(poolAddress);
|
|
55
561
|
|
|
@@ -60,26 +566,33 @@ async function swapCpAmm(
|
|
|
60
566
|
throw new Error(`Input token ${inputMint.toBase58()} not in pool (A: ${tokenAMint.toBase58()}, B: ${tokenBMint.toBase58()})`);
|
|
61
567
|
}
|
|
62
568
|
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
569
|
+
const decA = await getMintDecimals(connection, tokenAMint);
|
|
570
|
+
const decB = await getMintDecimals(connection, tokenBMint);
|
|
571
|
+
const currentPoint = await getCurrentPointSafe(connection, poolState.activationType);
|
|
572
|
+
const quote = swapQuoteExactInput(
|
|
573
|
+
poolState, currentPoint, amountIn,
|
|
574
|
+
slippageBps / 10000, aToB, false, decA, decB,
|
|
67
575
|
);
|
|
68
576
|
|
|
69
577
|
const outputMint = aToB ? tokenBMint : tokenAMint;
|
|
70
|
-
const minOut = quote.minimumAmountOut ?? new BN(0);
|
|
578
|
+
const minOut = (quote as any).minimumAmountOut ?? new BN(0);
|
|
579
|
+
|
|
580
|
+
// Get token program for each mint (SPL Token or Token-2022)
|
|
581
|
+
const [mintAInfo, mintBInfo] = await connection.getMultipleAccountsInfo([tokenAMint, tokenBMint]);
|
|
582
|
+
const tokenAProgram = mintAInfo!.owner;
|
|
583
|
+
const tokenBProgram = mintBInfo!.owner;
|
|
71
584
|
|
|
72
585
|
const txBuilder = cpAmm.swap2({
|
|
73
586
|
payer: wallet.publicKey, pool: poolAddress,
|
|
74
587
|
inputTokenMint: inputMint, outputTokenMint: outputMint,
|
|
75
588
|
tokenAMint, tokenBMint,
|
|
76
589
|
tokenAVault: poolState.tokenAVault, tokenBVault: poolState.tokenBVault,
|
|
77
|
-
tokenAProgram
|
|
590
|
+
tokenAProgram, tokenBProgram,
|
|
78
591
|
referralTokenAccount: null, poolState,
|
|
79
592
|
swapMode: SwapMode.ExactIn, amountIn, minimumAmountOut: minOut,
|
|
80
593
|
});
|
|
81
594
|
|
|
82
|
-
const tx = await txBuilder
|
|
595
|
+
const tx = await txBuilder;
|
|
83
596
|
tx.feePayer = wallet.publicKey;
|
|
84
597
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
85
598
|
tx.sign(wallet);
|
|
@@ -173,7 +686,7 @@ async function handleSwap(
|
|
|
173
686
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
174
687
|
const wallet = await loadWallet(options.wallet);
|
|
175
688
|
const poolAddress = new PublicKey(pool);
|
|
176
|
-
const inputMint = new PublicKey(inputToken);
|
|
689
|
+
const inputMint = new PublicKey(resolveTokenMint(inputToken));
|
|
177
690
|
const slippageBps = options.slippage ? Math.round(parseFloat(options.slippage) * 100) : 100;
|
|
178
691
|
|
|
179
692
|
// Detect pool type
|
|
@@ -191,7 +704,8 @@ async function handleSwap(
|
|
|
191
704
|
const decimals = await getMintDecimals(connection, inputMint);
|
|
192
705
|
const rawAmount = new BN(Math.floor(parseFloat(amount) * 10 ** decimals).toString());
|
|
193
706
|
|
|
194
|
-
|
|
707
|
+
const symIn = tokenSymbol(inputMint.toBase58());
|
|
708
|
+
if (!options.json) printInfo(`Swapping ${amount} ${symIn} (slippage: ${slippageBps / 100}%)...`);
|
|
195
709
|
|
|
196
710
|
let result: { signature: string; outputMint: PublicKey; minOut: BN };
|
|
197
711
|
switch (poolType) {
|
|
@@ -203,10 +717,13 @@ async function handleSwap(
|
|
|
203
717
|
if (options.json) {
|
|
204
718
|
formatOutput({ signature: result.signature, poolType, inputMint: inputMint.toBase58(), outputMint: result.outputMint.toBase58(), amountIn: amount, minAmountOut: result.minOut.toString() }, true);
|
|
205
719
|
} else {
|
|
720
|
+
const symOut = tokenSymbol(result.outputMint.toBase58());
|
|
721
|
+
const outDec = await getMintDecimals(connection, result.outputMint);
|
|
722
|
+
const minOutStr = (Number(result.minOut.toString()) / 10 ** outDec).toFixed(outDec).replace(/\.?0+$/, "");
|
|
206
723
|
printSuccess("Swap submitted!");
|
|
207
724
|
console.log(` Transaction: ${result.signature}`);
|
|
208
|
-
console.log(` Output
|
|
209
|
-
console.log(` Min output: ${
|
|
725
|
+
console.log(` Output: ${symOut} (${result.outputMint.toBase58()})`);
|
|
726
|
+
console.log(` Min output: ${minOutStr} ${symOut}`);
|
|
210
727
|
}
|
|
211
728
|
}
|
|
212
729
|
|
|
@@ -222,7 +739,7 @@ async function handleAddLiquidity(
|
|
|
222
739
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
223
740
|
const wallet = await loadWallet(options.wallet);
|
|
224
741
|
const poolAddress = new PublicKey(pool);
|
|
225
|
-
const inputMint = new PublicKey(inputToken);
|
|
742
|
+
const inputMint = new PublicKey(resolveTokenMint(inputToken));
|
|
226
743
|
const slippageBps = options.slippage ? Math.round(parseFloat(options.slippage) * 100) : 100;
|
|
227
744
|
|
|
228
745
|
// Detect pool type
|
|
@@ -332,7 +849,7 @@ async function handleAddLiquidity(
|
|
|
332
849
|
tokenAProgram: poolState.tokenAProgram, tokenBProgram: poolState.tokenBProgram,
|
|
333
850
|
});
|
|
334
851
|
|
|
335
|
-
const tx = await txBuilder
|
|
852
|
+
const tx = await txBuilder;
|
|
336
853
|
tx.feePayer = wallet.publicKey;
|
|
337
854
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
338
855
|
tx.sign(wallet);
|
|
@@ -352,7 +869,7 @@ async function handleAddLiquidity(
|
|
|
352
869
|
poolState,
|
|
353
870
|
});
|
|
354
871
|
|
|
355
|
-
const tx = await txBuilder
|
|
872
|
+
const tx = await txBuilder;
|
|
356
873
|
tx.feePayer = wallet.publicKey;
|
|
357
874
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
358
875
|
tx.sign(wallet, positionNft);
|
|
@@ -556,7 +1073,7 @@ async function handleCreateCpAmm(
|
|
|
556
1073
|
tokenAProgram, tokenBProgram,
|
|
557
1074
|
});
|
|
558
1075
|
|
|
559
|
-
const tx = await txBuilder
|
|
1076
|
+
const tx = await txBuilder;
|
|
560
1077
|
tx.feePayer = wallet.publicKey;
|
|
561
1078
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
562
1079
|
tx.sign(wallet, positionNft);
|
|
@@ -844,7 +1361,7 @@ async function handleRemoveLiquidity(
|
|
|
844
1361
|
txBuilder = cpAmm.removeLiquidity({ ...commonParams, liquidityDelta });
|
|
845
1362
|
}
|
|
846
1363
|
|
|
847
|
-
const tx = await txBuilder
|
|
1364
|
+
const tx = await txBuilder;
|
|
848
1365
|
tx.feePayer = wallet.publicKey;
|
|
849
1366
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
850
1367
|
tx.sign(wallet);
|
|
@@ -936,7 +1453,7 @@ async function handleClaimFee(
|
|
|
936
1453
|
receiver: wallet.publicKey,
|
|
937
1454
|
});
|
|
938
1455
|
|
|
939
|
-
const tx = await txBuilder
|
|
1456
|
+
const tx = await txBuilder;
|
|
940
1457
|
tx.feePayer = wallet.publicKey;
|
|
941
1458
|
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
942
1459
|
tx.sign(wallet);
|
|
@@ -977,8 +1494,84 @@ async function handleClaimFee(
|
|
|
977
1494
|
|
|
978
1495
|
export function registerDexCommands(program: Command): void {
|
|
979
1496
|
const dex = program
|
|
980
|
-
.command("dex"
|
|
981
|
-
.description("DEX
|
|
1497
|
+
.command("dex")
|
|
1498
|
+
.description("DEX — swap tokens via smart router or specific Meteora pool (DAMM v2 / DLMM / DBC)")
|
|
1499
|
+
.addHelpText("after", `
|
|
1500
|
+
Token symbols (NARA, USDC, USDT, SOL) can be used instead of mint addresses.
|
|
1501
|
+
|
|
1502
|
+
Examples:
|
|
1503
|
+
# Discover pools
|
|
1504
|
+
npx naracli dex pools # List NARA pools (default)
|
|
1505
|
+
npx naracli dex pools USDC # List USDC pools
|
|
1506
|
+
|
|
1507
|
+
# Smart routing (best price across DAMM v2 / DLMM / DBC)
|
|
1508
|
+
npx naracli dex smart-quote NARA USDC 1 # Quote: sell 1 NARA for USDC
|
|
1509
|
+
npx naracli dex smart-quote USDC NARA 10 # Quote: buy NARA with 10 USDC
|
|
1510
|
+
npx naracli dex smart-swap NARA USDC 1 --slippage 0.5 # Execute: sell 1 NARA → USDC
|
|
1511
|
+
npx naracli dex smart-swap USDC NARA 10 # Execute: buy NARA with 10 USDC
|
|
1512
|
+
|
|
1513
|
+
# Single-pool quote / swap
|
|
1514
|
+
npx naracli dex quote <pool-address> NARA 1 # Quote on a specific pool
|
|
1515
|
+
npx naracli dex swap <pool-address> NARA 1 --slippage 0.5`);
|
|
1516
|
+
|
|
1517
|
+
// dex pools
|
|
1518
|
+
dex
|
|
1519
|
+
.command("pools [token-mint]")
|
|
1520
|
+
.description("Find Meteora pools containing a given token (default: NARA), show reserves and price")
|
|
1521
|
+
.action(async (token: string | undefined, _opts: any, cmd: Command) => {
|
|
1522
|
+
try {
|
|
1523
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
1524
|
+
const mint = token || "So11111111111111111111111111111111111111112";
|
|
1525
|
+
await handlePools(mint, globalOpts);
|
|
1526
|
+
} catch (error: any) {
|
|
1527
|
+
printError(error.message);
|
|
1528
|
+
process.exit(1);
|
|
1529
|
+
}
|
|
1530
|
+
});
|
|
1531
|
+
|
|
1532
|
+
// dex smart-quote
|
|
1533
|
+
dex
|
|
1534
|
+
.command("smart-quote <input-mint> <output-mint> <amount>")
|
|
1535
|
+
.description("Get a best-route swap quote via nara smart router (aggregates DAMM v2 / DLMM / DBC)")
|
|
1536
|
+
.action(async (inputToken: string, outputToken: string, amount: string, _opts: any, cmd: Command) => {
|
|
1537
|
+
try {
|
|
1538
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
1539
|
+
await handleSmartQuote(inputToken, outputToken, amount, globalOpts);
|
|
1540
|
+
} catch (error: any) {
|
|
1541
|
+
printError(error.message);
|
|
1542
|
+
process.exit(1);
|
|
1543
|
+
}
|
|
1544
|
+
});
|
|
1545
|
+
|
|
1546
|
+
// dex smart-swap
|
|
1547
|
+
dex
|
|
1548
|
+
.command("smart-swap <input-mint> <output-mint> <amount>")
|
|
1549
|
+
.description("Execute a best-route swap via nara smart router")
|
|
1550
|
+
.option("--slippage <percent>", "Slippage tolerance in percent (default: 1)")
|
|
1551
|
+
.action(async (inputToken: string, outputToken: string, amount: string, opts: any, cmd: Command) => {
|
|
1552
|
+
try {
|
|
1553
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
1554
|
+
await handleSmartSwap(inputToken, outputToken, amount, { ...globalOpts, slippage: opts.slippage });
|
|
1555
|
+
} catch (error: any) {
|
|
1556
|
+
printError(error.message);
|
|
1557
|
+
process.exit(1);
|
|
1558
|
+
}
|
|
1559
|
+
});
|
|
1560
|
+
|
|
1561
|
+
// dex quote
|
|
1562
|
+
dex
|
|
1563
|
+
.command("quote <pool> <input-token-mint> <amount>")
|
|
1564
|
+
.description("Get a swap quote without executing (shows expected output, min output, fee, price)")
|
|
1565
|
+
.option("--slippage <percent>", "Slippage tolerance in percent (default: 1)")
|
|
1566
|
+
.action(async (pool: string, inputToken: string, amount: string, opts: any, cmd: Command) => {
|
|
1567
|
+
try {
|
|
1568
|
+
const globalOpts = cmd.optsWithGlobals() as GlobalOptions;
|
|
1569
|
+
await handleQuote(pool, inputToken, amount, { ...globalOpts, slippage: opts.slippage });
|
|
1570
|
+
} catch (error: any) {
|
|
1571
|
+
printError(error.message);
|
|
1572
|
+
process.exit(1);
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
982
1575
|
|
|
983
1576
|
// dex swap
|
|
984
1577
|
dex
|
|
@@ -1001,7 +1594,7 @@ Examples:
|
|
|
1001
1594
|
|
|
1002
1595
|
// dex add-liquidity
|
|
1003
1596
|
dex
|
|
1004
|
-
.command("add
|
|
1597
|
+
.command("liquidity-add <pool> <token-mint> <amount>", { hidden: true })
|
|
1005
1598
|
.description("Add liquidity to a Meteora pool (DAMM v2 / DLMM). Calculates the paired token amount from pool price.")
|
|
1006
1599
|
.option("--amount-b <number>", "Explicitly set paired token amount (skip price calculation)")
|
|
1007
1600
|
.option("--position <address>", "Existing position address (creates new if omitted)")
|
|
@@ -1022,7 +1615,7 @@ Examples:
|
|
|
1022
1615
|
|
|
1023
1616
|
// dex liquidity-positions
|
|
1024
1617
|
dex
|
|
1025
|
-
.command("liquidity-positions [owner-address]")
|
|
1618
|
+
.command("liquidity-positions [owner-address]", { hidden: true })
|
|
1026
1619
|
.description("List all liquidity positions across DAMM v2 and DLMM pools. Defaults to your wallet.")
|
|
1027
1620
|
.action(async (ownerAddress: string | undefined, _opts: any, cmd: Command) => {
|
|
1028
1621
|
try {
|
|
@@ -1036,7 +1629,7 @@ Examples:
|
|
|
1036
1629
|
|
|
1037
1630
|
// dex remove-liquidity
|
|
1038
1631
|
dex
|
|
1039
|
-
.command("remove
|
|
1632
|
+
.command("liquidity-remove <pool> <position>", { hidden: true })
|
|
1040
1633
|
.description("Remove liquidity from a Meteora pool position (DAMM v2 / DLMM)")
|
|
1041
1634
|
.option("--bps <number>", "Basis points to remove (10000 = 100%, default: 10000)")
|
|
1042
1635
|
.option("--all", "Remove all liquidity and close position")
|
|
@@ -1052,7 +1645,7 @@ Examples:
|
|
|
1052
1645
|
|
|
1053
1646
|
// dex claim-fee
|
|
1054
1647
|
dex
|
|
1055
|
-
.command("claim-fee <pool> <position>")
|
|
1648
|
+
.command("claim-fee <pool> <position>", { hidden: true })
|
|
1056
1649
|
.description("Claim accumulated trading fees from a position (DAMM v2 / DLMM)")
|
|
1057
1650
|
.action(async (pool: string, position: string, _opts: any, cmd: Command) => {
|
|
1058
1651
|
try {
|
|
@@ -1066,7 +1659,7 @@ Examples:
|
|
|
1066
1659
|
|
|
1067
1660
|
// dex create-pool
|
|
1068
1661
|
const createPool = dex
|
|
1069
|
-
.command("create-pool")
|
|
1662
|
+
.command("create-pool", { hidden: true })
|
|
1070
1663
|
.description("Create a new liquidity pool on Meteora");
|
|
1071
1664
|
|
|
1072
1665
|
// dex create-pool cpamm
|