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