@zoidz123/raydium-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,267 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.registerWalletCommands = registerWalletCommands;
7
+ const inquirer_1 = __importDefault(require("inquirer"));
8
+ const bs58_1 = __importDefault(require("bs58"));
9
+ const bn_js_1 = __importDefault(require("bn.js"));
10
+ const web3_js_1 = require("@solana/web3.js");
11
+ const spl_token_1 = require("@solana/spl-token");
12
+ const config_manager_1 = require("../../lib/config-manager");
13
+ const wallet_manager_1 = require("../../lib/wallet-manager");
14
+ const prompt_1 = require("../../lib/prompt");
15
+ const output_1 = require("../../lib/output");
16
+ const connection_1 = require("../../lib/connection");
17
+ function formatAmount(raw, decimals) {
18
+ if (decimals <= 0)
19
+ return raw.toString();
20
+ const rawStr = raw.toString().padStart(decimals + 1, "0");
21
+ const whole = rawStr.slice(0, -decimals);
22
+ const frac = rawStr.slice(-decimals).replace(/0+$/, "");
23
+ return frac ? `${whole}.${frac}` : whole;
24
+ }
25
+ async function fetchRpcBalances(owner) {
26
+ const connection = await (0, connection_1.getConnection)();
27
+ const [solBalance, splAccounts, token2022Accounts] = await Promise.all([
28
+ connection.getBalance(owner),
29
+ connection.getTokenAccountsByOwner(owner, { programId: spl_token_1.TOKEN_PROGRAM_ID }),
30
+ connection.getTokenAccountsByOwner(owner, { programId: spl_token_1.TOKEN_2022_PROGRAM_ID })
31
+ ]);
32
+ const tokenAccounts = [...splAccounts.value, ...token2022Accounts.value].map(({ account }) => spl_token_1.AccountLayout.decode(account.data));
33
+ const mintSet = new Set();
34
+ tokenAccounts.forEach((account) => mintSet.add(account.mint.toBase58()));
35
+ const mintList = Array.from(mintSet);
36
+ const mintInfos = new Map();
37
+ const batchSize = 100;
38
+ for (let i = 0; i < mintList.length; i += batchSize) {
39
+ const batch = mintList.slice(i, i + batchSize);
40
+ const accounts = await connection.getMultipleAccountsInfo(batch.map((mint) => new web3_js_1.PublicKey(mint)));
41
+ accounts.forEach((info, idx) => {
42
+ if (!info)
43
+ return;
44
+ const decoded = spl_token_1.MintLayout.decode(info.data);
45
+ mintInfos.set(batch[idx], decoded.decimals);
46
+ });
47
+ }
48
+ const balances = [];
49
+ balances.push({
50
+ mint: "SOL",
51
+ symbol: "SOL",
52
+ name: "solana",
53
+ amount: formatAmount(new bn_js_1.default(solBalance.toString()), 9),
54
+ raw: solBalance.toString(),
55
+ decimals: 9
56
+ });
57
+ tokenAccounts.forEach((account) => {
58
+ const mint = account.mint.toBase58();
59
+ const decimals = mintInfos.get(mint) ?? 0;
60
+ const raw = new bn_js_1.default(account.amount.toString());
61
+ if (raw.isZero())
62
+ return;
63
+ const symbol = mint.slice(0, 6);
64
+ balances.push({
65
+ mint,
66
+ symbol,
67
+ name: symbol,
68
+ amount: formatAmount(raw, decimals),
69
+ raw: raw.toString(),
70
+ decimals
71
+ });
72
+ });
73
+ return balances;
74
+ }
75
+ function registerWalletCommands(program) {
76
+ const wallet = program.command("wallet").description("Manage wallets");
77
+ wallet
78
+ .command("create")
79
+ .description("Create a new wallet")
80
+ .argument("[name]")
81
+ .action(async (name) => {
82
+ const walletName = name ?? (await (0, prompt_1.promptInput)("Wallet name"));
83
+ const password = await (0, prompt_1.promptPassword)("Set wallet password", true);
84
+ const { mnemonic, wallet: walletFile } = await (0, wallet_manager_1.createWallet)(walletName, password);
85
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
86
+ if (!config.activeWallet) {
87
+ await (0, config_manager_1.saveConfig)({ ...config, activeWallet: walletName });
88
+ }
89
+ if ((0, output_1.isJsonOutput)()) {
90
+ (0, output_1.logJson)({ name: walletFile.name, publicKey: walletFile.publicKey, mnemonic });
91
+ }
92
+ else {
93
+ (0, output_1.logSuccess)(`Wallet created: ${walletFile.name}`);
94
+ (0, output_1.logInfo)(`Public key: ${walletFile.publicKey}`);
95
+ (0, output_1.logInfo)(`Seed phrase (write down, shown once): ${mnemonic}`);
96
+ }
97
+ });
98
+ wallet
99
+ .command("import")
100
+ .description("Import an existing wallet")
101
+ .argument("<name>")
102
+ .option("--private-key <base58>", "Base58-encoded private key")
103
+ .option("--seed-phrase <phrase>", "Seed phrase")
104
+ .action(async (name, options) => {
105
+ let { privateKey, seedPhrase } = options;
106
+ if (!privateKey && !seedPhrase) {
107
+ const answer = await inquirer_1.default.prompt([
108
+ {
109
+ type: "list",
110
+ name: "method",
111
+ message: "Import method",
112
+ choices: [
113
+ { name: "Private key (base58)", value: "private-key" },
114
+ { name: "Seed phrase", value: "seed-phrase" }
115
+ ]
116
+ }
117
+ ]);
118
+ if (answer.method === "private-key") {
119
+ privateKey = await (0, prompt_1.promptInput)("Private key (base58)");
120
+ }
121
+ else {
122
+ seedPhrase = await (0, prompt_1.promptInput)("Seed phrase");
123
+ }
124
+ }
125
+ const password = await (0, prompt_1.promptPassword)("Set wallet password", true);
126
+ if (privateKey) {
127
+ const walletFile = await (0, wallet_manager_1.importWalletFromPrivateKey)(name, privateKey, password);
128
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
129
+ if (!config.activeWallet) {
130
+ await (0, config_manager_1.saveConfig)({ ...config, activeWallet: walletFile.name });
131
+ }
132
+ if ((0, output_1.isJsonOutput)()) {
133
+ (0, output_1.logJson)({ name: walletFile.name, publicKey: walletFile.publicKey });
134
+ }
135
+ else {
136
+ (0, output_1.logSuccess)(`Wallet imported: ${walletFile.name}`);
137
+ (0, output_1.logInfo)(`Public key: ${walletFile.publicKey}`);
138
+ }
139
+ return;
140
+ }
141
+ if (!seedPhrase) {
142
+ (0, output_1.logError)("Missing seed phrase");
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+ const normalizedSeed = seedPhrase.trim().split(/\s+/).join(" ");
147
+ const walletFile = await (0, wallet_manager_1.importWalletFromMnemonic)(name, normalizedSeed, password);
148
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
149
+ if (!config.activeWallet) {
150
+ await (0, config_manager_1.saveConfig)({ ...config, activeWallet: walletFile.name });
151
+ }
152
+ if ((0, output_1.isJsonOutput)()) {
153
+ (0, output_1.logJson)({ name: walletFile.name, publicKey: walletFile.publicKey });
154
+ }
155
+ else {
156
+ (0, output_1.logSuccess)(`Wallet imported: ${walletFile.name}`);
157
+ (0, output_1.logInfo)(`Public key: ${walletFile.publicKey}`);
158
+ }
159
+ });
160
+ wallet
161
+ .command("list")
162
+ .description("List wallets")
163
+ .action(async () => {
164
+ const wallets = await (0, wallet_manager_1.listWallets)();
165
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
166
+ if ((0, output_1.isJsonOutput)()) {
167
+ (0, output_1.logJson)({ wallets, activeWallet: config.activeWallet });
168
+ return;
169
+ }
170
+ if (wallets.length === 0) {
171
+ (0, output_1.logInfo)("No wallets found");
172
+ return;
173
+ }
174
+ wallets.forEach((item) => {
175
+ const activeMark = item.name === config.activeWallet ? " (active)" : "";
176
+ (0, output_1.logInfo)(`${item.name}${activeMark} - ${item.publicKey}`);
177
+ });
178
+ });
179
+ wallet
180
+ .command("use")
181
+ .description("Set active wallet")
182
+ .argument("<name>")
183
+ .action(async (name) => {
184
+ (0, wallet_manager_1.assertValidWalletName)(name);
185
+ if (!(await (0, wallet_manager_1.walletExists)(name))) {
186
+ (0, output_1.logError)(`Wallet not found: ${name}`);
187
+ process.exitCode = 1;
188
+ return;
189
+ }
190
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
191
+ await (0, config_manager_1.saveConfig)({ ...config, activeWallet: name });
192
+ if ((0, output_1.isJsonOutput)()) {
193
+ (0, output_1.logJson)({ activeWallet: name });
194
+ }
195
+ else {
196
+ (0, output_1.logSuccess)(`Active wallet set to ${name}`);
197
+ }
198
+ });
199
+ wallet
200
+ .command("balance")
201
+ .description("Show wallet balances")
202
+ .argument("[name]")
203
+ .action(async (name) => {
204
+ const config = await (0, config_manager_1.loadConfig)({ createIfMissing: true });
205
+ const walletName = name ?? config.activeWallet;
206
+ if (!walletName) {
207
+ (0, output_1.logError)("No active wallet set");
208
+ process.exitCode = 1;
209
+ return;
210
+ }
211
+ (0, wallet_manager_1.assertValidWalletName)(walletName);
212
+ const owner = await (0, wallet_manager_1.getWalletPublicKey)(walletName);
213
+ const walletAddress = owner.toBase58();
214
+ const balances = await (0, output_1.withSpinner)("Fetching balances", () => fetchRpcBalances(owner));
215
+ if ((0, output_1.isJsonOutput)()) {
216
+ (0, output_1.logJson)({ wallet: walletName, publicKey: walletAddress, tokens: balances });
217
+ return;
218
+ }
219
+ (0, output_1.logInfo)(`Wallet: ${walletName}`);
220
+ (0, output_1.logInfo)(`Public key: ${walletAddress}`);
221
+ if (balances.length === 0) {
222
+ (0, output_1.logInfo)("No balances found");
223
+ return;
224
+ }
225
+ balances.forEach((item) => {
226
+ // For SOL, just show symbol. For tokens, show full mint address
227
+ if (item.mint === "SOL") {
228
+ (0, output_1.logInfo)(`SOL: ${item.amount}`);
229
+ }
230
+ else {
231
+ const label = item.name && item.name !== item.symbol
232
+ ? `${item.symbol} (${item.name})`
233
+ : item.symbol;
234
+ (0, output_1.logInfo)(`${label}: ${item.amount}`);
235
+ (0, output_1.logInfo)(` Mint: ${item.mint}`);
236
+ }
237
+ });
238
+ });
239
+ wallet
240
+ .command("export")
241
+ .description("Export a private key")
242
+ .argument("<name>")
243
+ .action(async (name) => {
244
+ (0, wallet_manager_1.assertValidWalletName)(name);
245
+ if (!(await (0, wallet_manager_1.walletExists)(name))) {
246
+ (0, output_1.logError)(`Wallet not found: ${name}`);
247
+ process.exitCode = 1;
248
+ return;
249
+ }
250
+ const ok = await (0, prompt_1.promptConfirm)("This will reveal your private key. Continue?", false);
251
+ if (!ok) {
252
+ (0, output_1.logInfo)("Cancelled");
253
+ return;
254
+ }
255
+ const password = await (0, prompt_1.promptPassword)("Enter wallet password");
256
+ const keypair = await (0, wallet_manager_1.decryptWallet)(name, password);
257
+ const privateKey = bs58_1.default.encode(keypair.secretKey);
258
+ if ((0, output_1.isJsonOutput)()) {
259
+ (0, output_1.logJson)({ name, publicKey: keypair.publicKey.toBase58(), privateKey });
260
+ }
261
+ else {
262
+ (0, output_1.logSuccess)("Private key exported");
263
+ (0, output_1.logInfo)(`Public key: ${keypair.publicKey.toBase58()}`);
264
+ (0, output_1.logInfo)(`Private key (base58): ${privateKey}`);
265
+ }
266
+ });
267
+ }
package/dist/index.js ADDED
@@ -0,0 +1,43 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const dotenv_1 = __importDefault(require("dotenv"));
9
+ const config_1 = require("./commands/config");
10
+ const wallet_1 = require("./commands/wallet");
11
+ const pools_1 = require("./commands/pools");
12
+ const swap_1 = require("./commands/swap");
13
+ const launchpad_1 = require("./commands/launchpad");
14
+ const clmm_1 = require("./commands/clmm");
15
+ const cpmm_1 = require("./commands/cpmm");
16
+ const output_1 = require("./lib/output");
17
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
18
+ const { version } = require("../package.json");
19
+ dotenv_1.default.config();
20
+ const program = new commander_1.Command();
21
+ program
22
+ .name("raydium")
23
+ .description("Raydium CLI")
24
+ .version(version)
25
+ .option("--json", "output json");
26
+ program.hook("preAction", (_thisCommand, actionCommand) => {
27
+ const opts = typeof actionCommand.optsWithGlobals === "function"
28
+ ? actionCommand.optsWithGlobals()
29
+ : actionCommand.opts();
30
+ (0, output_1.setJsonOutput)(Boolean(opts.json));
31
+ });
32
+ (0, config_1.registerConfigCommands)(program);
33
+ (0, wallet_1.registerWalletCommands)(program);
34
+ (0, pools_1.registerPoolCommands)(program);
35
+ (0, swap_1.registerSwapCommands)(program);
36
+ (0, launchpad_1.registerLaunchpadCommands)(program);
37
+ (0, clmm_1.registerClmmCommands)(program);
38
+ (0, cpmm_1.registerCpmmCommands)(program);
39
+ program.parseAsync(process.argv).catch((error) => {
40
+ const message = error instanceof Error ? error.message : String(error ?? "Unknown error");
41
+ (0, output_1.logError)(message);
42
+ process.exitCode = 1;
43
+ });
@@ -0,0 +1,296 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.sqrtPriceX64ToPrice = sqrtPriceX64ToPrice;
7
+ exports.tickToPrice = tickToPrice;
8
+ exports.tickToSqrtPriceX64 = tickToSqrtPriceX64;
9
+ exports.getAmountsFromLiquidity = getAmountsFromLiquidity;
10
+ exports.getAmountsForTickRange = getAmountsForTickRange;
11
+ exports.formatTokenAmount = formatTokenAmount;
12
+ exports.formatUsd = formatUsd;
13
+ exports.calculateUsdValue = calculateUsdValue;
14
+ exports.formatPrice = formatPrice;
15
+ exports.formatFeeRate = formatFeeRate;
16
+ exports.isPositionInRange = isPositionInRange;
17
+ exports.priceToTick = priceToTick;
18
+ exports.priceToAlignedTick = priceToAlignedTick;
19
+ exports.isTickAligned = isTickAligned;
20
+ exports.getTickSpacingFromFeeTier = getTickSpacingFromFeeTier;
21
+ exports.applySlippage = applySlippage;
22
+ exports.findPositionByNftMint = findPositionByNftMint;
23
+ exports.hasUnclaimedFees = hasUnclaimedFees;
24
+ exports.calculateWithdrawAmounts = calculateWithdrawAmounts;
25
+ const decimal_js_1 = __importDefault(require("decimal.js"));
26
+ const bn_js_1 = __importDefault(require("bn.js"));
27
+ // Q64.64 fixed point constant
28
+ const Q64 = new decimal_js_1.default(2).pow(64);
29
+ /**
30
+ * Convert sqrtPriceX64 (Q64.64 fixed point) to decimal price
31
+ * sqrtPriceX64 = sqrt(price) * 2^64
32
+ * price = (sqrtPriceX64 / 2^64)^2 = sqrtPriceX64^2 / 2^128
33
+ */
34
+ function sqrtPriceX64ToPrice(sqrtPriceX64, decimalsA, decimalsB) {
35
+ const sqrtPrice = new decimal_js_1.default(sqrtPriceX64.toString()).div(Q64);
36
+ const price = sqrtPrice.pow(2);
37
+ // Adjust for decimal differences: price is token1/token0, so we need to adjust
38
+ const decimalAdjustment = new decimal_js_1.default(10).pow(decimalsA - decimalsB);
39
+ return price.mul(decimalAdjustment);
40
+ }
41
+ /**
42
+ * Convert a tick index to price
43
+ * price = 1.0001^tick
44
+ */
45
+ function tickToPrice(tick, decimalsA, decimalsB) {
46
+ const price = new decimal_js_1.default(1.0001).pow(tick);
47
+ const decimalAdjustment = new decimal_js_1.default(10).pow(decimalsA - decimalsB);
48
+ return price.mul(decimalAdjustment);
49
+ }
50
+ /**
51
+ * Convert tick to sqrtPriceX64
52
+ * sqrtPriceX64 = 1.0001^(tick/2) * 2^64
53
+ */
54
+ function tickToSqrtPriceX64(tick) {
55
+ return new decimal_js_1.default(1.0001).pow(tick / 2).mul(Q64);
56
+ }
57
+ /**
58
+ * Calculate token amounts from liquidity for a position
59
+ * Based on Uniswap V3 math:
60
+ * - amount0 = L * (1/sqrt(P_lower) - 1/sqrt(P_upper)) when price <= lower
61
+ * - amount1 = L * (sqrt(P_upper) - sqrt(P_lower)) when price >= upper
62
+ * - Both when price is in range
63
+ */
64
+ function getAmountsFromLiquidity(liquidity, currentSqrtPriceX64, tickLower, tickUpper, decimalsA, decimalsB) {
65
+ const L = new decimal_js_1.default(liquidity.toString());
66
+ const sqrtPriceCurrent = new decimal_js_1.default(currentSqrtPriceX64.toString());
67
+ const sqrtPriceLower = tickToSqrtPriceX64(tickLower);
68
+ const sqrtPriceUpper = tickToSqrtPriceX64(tickUpper);
69
+ let amount0 = new decimal_js_1.default(0);
70
+ let amount1 = new decimal_js_1.default(0);
71
+ if (sqrtPriceCurrent.lte(sqrtPriceLower)) {
72
+ // Current price is below the range - all liquidity is in token0
73
+ // amount0 = L * (sqrt(P_upper) - sqrt(P_lower)) / (sqrt(P_lower) * sqrt(P_upper))
74
+ amount0 = L.mul(sqrtPriceUpper.sub(sqrtPriceLower))
75
+ .div(sqrtPriceLower.mul(sqrtPriceUpper))
76
+ .mul(Q64); // Multiply by Q64 because sqrtPrices are scaled
77
+ }
78
+ else if (sqrtPriceCurrent.gte(sqrtPriceUpper)) {
79
+ // Current price is above the range - all liquidity is in token1
80
+ // amount1 = L * (sqrt(P_upper) - sqrt(P_lower)) / Q64
81
+ amount1 = L.mul(sqrtPriceUpper.sub(sqrtPriceLower)).div(Q64);
82
+ }
83
+ else {
84
+ // Price is in range - split between both tokens
85
+ // amount0 = L * (sqrt(P_upper) - sqrt(P_current)) / (sqrt(P_current) * sqrt(P_upper))
86
+ amount0 = L.mul(sqrtPriceUpper.sub(sqrtPriceCurrent))
87
+ .div(sqrtPriceCurrent.mul(sqrtPriceUpper))
88
+ .mul(Q64);
89
+ // amount1 = L * (sqrt(P_current) - sqrt(P_lower)) / Q64
90
+ amount1 = L.mul(sqrtPriceCurrent.sub(sqrtPriceLower)).div(Q64);
91
+ }
92
+ // Convert to human-readable amounts by dividing by decimals
93
+ const amount0Human = amount0.div(new decimal_js_1.default(10).pow(decimalsA));
94
+ const amount1Human = amount1.div(new decimal_js_1.default(10).pow(decimalsB));
95
+ return { amount0: amount0Human, amount1: amount1Human };
96
+ }
97
+ /**
98
+ * Calculate token amounts from liquidity for a tick range (for tick listing)
99
+ * This shows how much liquidity is available in a given tick range
100
+ */
101
+ function getAmountsForTickRange(liquidityNet, tickIndex, tickSpacing, currentTick, currentSqrtPriceX64, decimalsA, decimalsB) {
102
+ // For a tick, liquidityNet represents the change in liquidity when crossing
103
+ // We calculate the amounts assuming the liquidity covers the next tick spacing
104
+ const tickLower = tickIndex;
105
+ const tickUpper = tickIndex + tickSpacing;
106
+ const L = new decimal_js_1.default(liquidityNet.toString()).abs();
107
+ if (L.isZero()) {
108
+ return { amount0: new decimal_js_1.default(0), amount1: new decimal_js_1.default(0) };
109
+ }
110
+ const sqrtPriceCurrent = new decimal_js_1.default(currentSqrtPriceX64.toString());
111
+ const sqrtPriceLower = tickToSqrtPriceX64(tickLower);
112
+ const sqrtPriceUpper = tickToSqrtPriceX64(tickUpper);
113
+ let amount0 = new decimal_js_1.default(0);
114
+ let amount1 = new decimal_js_1.default(0);
115
+ if (currentTick < tickLower) {
116
+ // Range is above current price - all in token0
117
+ amount0 = L.mul(sqrtPriceUpper.sub(sqrtPriceLower))
118
+ .div(sqrtPriceLower.mul(sqrtPriceUpper))
119
+ .mul(Q64);
120
+ }
121
+ else if (currentTick >= tickUpper) {
122
+ // Range is below current price - all in token1
123
+ amount1 = L.mul(sqrtPriceUpper.sub(sqrtPriceLower)).div(Q64);
124
+ }
125
+ else {
126
+ // Current price is in this range
127
+ amount0 = L.mul(sqrtPriceUpper.sub(sqrtPriceCurrent))
128
+ .div(sqrtPriceCurrent.mul(sqrtPriceUpper))
129
+ .mul(Q64);
130
+ amount1 = L.mul(sqrtPriceCurrent.sub(sqrtPriceLower)).div(Q64);
131
+ }
132
+ const amount0Human = amount0.div(new decimal_js_1.default(10).pow(decimalsA));
133
+ const amount1Human = amount1.div(new decimal_js_1.default(10).pow(decimalsB));
134
+ return { amount0: amount0Human, amount1: amount1Human };
135
+ }
136
+ /**
137
+ * Format a token amount for display
138
+ */
139
+ function formatTokenAmount(amount, decimals = 6) {
140
+ if (amount.isZero())
141
+ return "0";
142
+ const fixed = amount.toFixed(decimals);
143
+ // Remove trailing zeros after decimal
144
+ return fixed.replace(/\.?0+$/, "");
145
+ }
146
+ /**
147
+ * Format a USD value for display
148
+ */
149
+ function formatUsd(value) {
150
+ if (value === null)
151
+ return "";
152
+ const num = typeof value === "number" ? value : value.toNumber();
153
+ if (!Number.isFinite(num))
154
+ return "";
155
+ if (num < 0.01)
156
+ return "<$0.01";
157
+ if (num < 1)
158
+ return `$${num.toFixed(4)}`;
159
+ if (num < 1000)
160
+ return `$${num.toFixed(2)}`;
161
+ return `$${num.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
162
+ }
163
+ /**
164
+ * Calculate total USD value from two token amounts and their prices
165
+ * Returns null if neither price is available
166
+ */
167
+ function calculateUsdValue(amount0, amount1, price0, price1) {
168
+ if (price0 === null && price1 === null)
169
+ return null;
170
+ let total = new decimal_js_1.default(0);
171
+ if (price0 !== null)
172
+ total = total.add(amount0.mul(price0));
173
+ if (price1 !== null)
174
+ total = total.add(amount1.mul(price1));
175
+ return total;
176
+ }
177
+ /**
178
+ * Format a price with appropriate precision
179
+ */
180
+ function formatPrice(price) {
181
+ const num = price.toNumber();
182
+ if (num === 0)
183
+ return "0";
184
+ if (num < 0.000001)
185
+ return price.toExponential(4);
186
+ if (num < 0.0001)
187
+ return price.toFixed(8);
188
+ if (num < 0.01)
189
+ return price.toFixed(6);
190
+ if (num < 1)
191
+ return price.toFixed(4);
192
+ if (num < 100)
193
+ return price.toFixed(4);
194
+ if (num < 10000)
195
+ return price.toFixed(2);
196
+ return price.toFixed(0);
197
+ }
198
+ /**
199
+ * Format fee rate (e.g., 0.25% = 2500 basis points in 1e6)
200
+ */
201
+ function formatFeeRate(feeRate) {
202
+ const percent = (feeRate / 1000000) * 100;
203
+ return `${percent}%`;
204
+ }
205
+ /**
206
+ * Check if a position is in range
207
+ */
208
+ function isPositionInRange(tickLower, tickUpper, currentTick) {
209
+ return currentTick >= tickLower && currentTick < tickUpper;
210
+ }
211
+ /**
212
+ * Convert price to tick, aligned to the given tick spacing
213
+ * price = 1.0001^tick, so tick = log(price) / log(1.0001)
214
+ */
215
+ function priceToTick(price, decimalsA, decimalsB) {
216
+ // Adjust price for decimals (reverse of tickToPrice)
217
+ const decimalAdjustment = new decimal_js_1.default(10).pow(decimalsA - decimalsB);
218
+ const adjustedPrice = price.div(decimalAdjustment);
219
+ // tick = log(adjustedPrice) / log(1.0001)
220
+ return Math.floor(adjustedPrice.ln().div(new decimal_js_1.default(1.0001).ln()).toNumber());
221
+ }
222
+ /**
223
+ * Convert price to tick aligned to the given tick spacing
224
+ * Rounds down to nearest valid tick
225
+ */
226
+ function priceToAlignedTick(price, tickSpacing, decimalsA, decimalsB) {
227
+ const tick = priceToTick(price, decimalsA, decimalsB);
228
+ return Math.floor(tick / tickSpacing) * tickSpacing;
229
+ }
230
+ /**
231
+ * Check if a tick is aligned to the given tick spacing
232
+ */
233
+ function isTickAligned(tick, tickSpacing) {
234
+ return tick % tickSpacing === 0;
235
+ }
236
+ /**
237
+ * Fee tier (in basis points) to tick spacing mapping
238
+ * Common Raydium CLMM fee tiers:
239
+ * - 100 bps (1%) -> tick spacing 100
240
+ * - 500 bps (0.5%) -> tick spacing 10
241
+ * - 2500 bps (0.25%) -> tick spacing 60
242
+ * - 10000 bps (1%) -> tick spacing 200
243
+ *
244
+ * Note: The actual mapping depends on the AMM config.
245
+ * This provides common defaults.
246
+ */
247
+ function getTickSpacingFromFeeTier(feeTierBps) {
248
+ switch (feeTierBps) {
249
+ case 100:
250
+ return 1;
251
+ case 500:
252
+ return 10;
253
+ case 2500:
254
+ return 60;
255
+ case 3000:
256
+ return 60;
257
+ case 10000:
258
+ return 200;
259
+ default:
260
+ throw new Error(`Unknown fee tier: ${feeTierBps} bps`);
261
+ }
262
+ }
263
+ /**
264
+ * Apply slippage to an amount
265
+ * @param amount The amount to apply slippage to
266
+ * @param slippagePercent Slippage as a percentage (e.g., 1 for 1%)
267
+ * @param isMin If true, calculates minimum (amount - slippage), else maximum (amount + slippage)
268
+ */
269
+ function applySlippage(amount, slippagePercent, isMin) {
270
+ const slippageBps = Math.floor(slippagePercent * 100); // Convert percent to basis points
271
+ const factor = isMin ? 10000 - slippageBps : 10000 + slippageBps;
272
+ return amount.mul(new bn_js_1.default(factor)).div(new bn_js_1.default(10000));
273
+ }
274
+ /**
275
+ * Find a position by its NFT mint address from an array of positions
276
+ */
277
+ function findPositionByNftMint(positions, nftMint) {
278
+ const nftMintStr = nftMint.toBase58();
279
+ return positions.find((p) => p.nftMint?.toBase58() === nftMintStr);
280
+ }
281
+ /**
282
+ * Check if a position has unclaimed fees
283
+ */
284
+ function hasUnclaimedFees(position) {
285
+ const feesA = position.tokenFeesOwedA;
286
+ const feesB = position.tokenFeesOwedB;
287
+ const hasFeesA = feesA && !feesA.isZero?.();
288
+ const hasFeesB = feesB && !feesB.isZero?.();
289
+ return hasFeesA || hasFeesB || false;
290
+ }
291
+ /**
292
+ * Calculate amounts to receive when removing liquidity
293
+ */
294
+ function calculateWithdrawAmounts(liquidity, totalLiquidity, currentSqrtPriceX64, tickLower, tickUpper, decimalsA, decimalsB) {
295
+ return getAmountsFromLiquidity(liquidity.toString(), currentSqrtPriceX64.toString(), tickLower, tickUpper, decimalsA, decimalsB);
296
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SOLANA_CODEX_NETWORK_ID = void 0;
4
+ exports.getCodexClient = getCodexClient;
5
+ const sdk_1 = require("@codex-data/sdk");
6
+ let cachedClient = null;
7
+ function getCodexClient() {
8
+ if (cachedClient)
9
+ return cachedClient;
10
+ const apiKey = process.env.CODEX_API_KEY;
11
+ if (!apiKey) {
12
+ throw new Error("Missing CODEX_API_KEY in environment");
13
+ }
14
+ cachedClient = new sdk_1.Codex(apiKey);
15
+ return cachedClient;
16
+ }
17
+ exports.SOLANA_CODEX_NETWORK_ID = 1399811149;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ensureConfigDir = ensureConfigDir;
7
+ exports.loadConfig = loadConfig;
8
+ exports.saveConfig = saveConfig;
9
+ exports.isValidConfigKey = isValidConfigKey;
10
+ exports.parseConfigValue = parseConfigValue;
11
+ const promises_1 = __importDefault(require("fs/promises"));
12
+ const paths_1 = require("./paths");
13
+ const config_1 = require("../types/config");
14
+ const NUMBER_KEYS = ["default-slippage", "priority-fee"];
15
+ const EXPLORER_VALUES = ["solscan", "solanaFm", "solanaExplorer"];
16
+ async function ensureConfigDir() {
17
+ await promises_1.default.mkdir(paths_1.CONFIG_DIR, { recursive: true });
18
+ }
19
+ async function loadConfig(options) {
20
+ try {
21
+ const raw = await promises_1.default.readFile(paths_1.CONFIG_PATH, "utf8");
22
+ const parsed = JSON.parse(raw);
23
+ return { ...config_1.DEFAULT_CONFIG, ...parsed };
24
+ }
25
+ catch (error) {
26
+ if (error.code === "ENOENT") {
27
+ const config = { ...config_1.DEFAULT_CONFIG };
28
+ if (options?.createIfMissing) {
29
+ await saveConfig(config);
30
+ }
31
+ return config;
32
+ }
33
+ throw error;
34
+ }
35
+ }
36
+ async function saveConfig(config) {
37
+ await ensureConfigDir();
38
+ await promises_1.default.writeFile(paths_1.CONFIG_PATH, JSON.stringify(config, null, 2));
39
+ }
40
+ function isValidConfigKey(key) {
41
+ return Object.prototype.hasOwnProperty.call(config_1.DEFAULT_CONFIG, key);
42
+ }
43
+ function parseConfigValue(key, value) {
44
+ if (NUMBER_KEYS.includes(key)) {
45
+ const num = Number(value);
46
+ if (!Number.isFinite(num))
47
+ throw new Error(`Invalid number for ${key}: ${value}`);
48
+ return num;
49
+ }
50
+ if (key === "explorer") {
51
+ if (!EXPLORER_VALUES.includes(value)) {
52
+ throw new Error(`Invalid explorer value. Use one of: ${EXPLORER_VALUES.join(", ")}`);
53
+ }
54
+ return value;
55
+ }
56
+ if (key === "activeWallet") {
57
+ if (value === "null")
58
+ return null;
59
+ return value;
60
+ }
61
+ if (key === "pinata-jwt") {
62
+ if (value === "null" || value === "")
63
+ return null;
64
+ return value;
65
+ }
66
+ return value;
67
+ }