@clawdvault/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1024 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1024 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
18
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
19
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
20
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
21
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
22
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
23
|
+
mod
|
|
24
|
+
));
|
|
25
|
+
|
|
26
|
+
// src/index.ts
|
|
27
|
+
var import_commander5 = require("commander");
|
|
28
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
29
|
+
|
|
30
|
+
// src/commands/tokens.ts
|
|
31
|
+
var import_commander = require("commander");
|
|
32
|
+
var import_chalk2 = __toESM(require("chalk"));
|
|
33
|
+
var import_cli_table3 = __toESM(require("cli-table3"));
|
|
34
|
+
var import_sdk2 = require("@clawdvault/sdk");
|
|
35
|
+
|
|
36
|
+
// src/utils.ts
|
|
37
|
+
var import_sdk = require("@clawdvault/sdk");
|
|
38
|
+
var import_chalk = __toESM(require("chalk"));
|
|
39
|
+
var import_ora = __toESM(require("ora"));
|
|
40
|
+
var fs = __toESM(require("fs"));
|
|
41
|
+
var path = __toESM(require("path"));
|
|
42
|
+
var os = __toESM(require("os"));
|
|
43
|
+
function getConfigDir() {
|
|
44
|
+
return path.join(os.homedir(), ".clawdvault");
|
|
45
|
+
}
|
|
46
|
+
function getWalletPath() {
|
|
47
|
+
if (process.env.CLAWDVAULT_WALLET) {
|
|
48
|
+
return process.env.CLAWDVAULT_WALLET;
|
|
49
|
+
}
|
|
50
|
+
const configWallet = path.join(getConfigDir(), "wallet.json");
|
|
51
|
+
if (fs.existsSync(configWallet)) {
|
|
52
|
+
return configWallet;
|
|
53
|
+
}
|
|
54
|
+
const solanaWallet = path.join(os.homedir(), ".config", "solana", "id.json");
|
|
55
|
+
if (fs.existsSync(solanaWallet)) {
|
|
56
|
+
return solanaWallet;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
function loadSigner(walletPathOrKey) {
|
|
61
|
+
const pathOrKey = walletPathOrKey || getWalletPath();
|
|
62
|
+
if (!pathOrKey) {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
if (fs.existsSync(pathOrKey)) {
|
|
66
|
+
try {
|
|
67
|
+
return import_sdk.KeypairSigner.fromFile(pathOrKey);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
return new import_sdk.KeypairSigner(pathOrKey);
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function getBaseUrl() {
|
|
79
|
+
return process.env.CLAWDVAULT_API_URL;
|
|
80
|
+
}
|
|
81
|
+
function createClientWithWallet(walletPath) {
|
|
82
|
+
const signer = loadSigner(walletPath);
|
|
83
|
+
const baseUrl = getBaseUrl();
|
|
84
|
+
const client = (0, import_sdk.createClient)({ signer: signer || void 0, baseUrl });
|
|
85
|
+
return {
|
|
86
|
+
client,
|
|
87
|
+
signer,
|
|
88
|
+
walletAddress: signer?.publicKey.toBase58() ?? null
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function createReadOnlyClient() {
|
|
92
|
+
const baseUrl = getBaseUrl();
|
|
93
|
+
return (0, import_sdk.createClient)({ baseUrl });
|
|
94
|
+
}
|
|
95
|
+
function spinner(text) {
|
|
96
|
+
return (0, import_ora.default)({ text, color: "cyan" });
|
|
97
|
+
}
|
|
98
|
+
function formatSol(amount) {
|
|
99
|
+
return `${amount.toFixed(6)} SOL`;
|
|
100
|
+
}
|
|
101
|
+
function formatTokens(amount) {
|
|
102
|
+
if (amount >= 1e9) {
|
|
103
|
+
return `${(amount / 1e9).toFixed(2)}B`;
|
|
104
|
+
}
|
|
105
|
+
if (amount >= 1e6) {
|
|
106
|
+
return `${(amount / 1e6).toFixed(2)}M`;
|
|
107
|
+
}
|
|
108
|
+
if (amount >= 1e3) {
|
|
109
|
+
return `${(amount / 1e3).toFixed(2)}K`;
|
|
110
|
+
}
|
|
111
|
+
return amount.toFixed(2);
|
|
112
|
+
}
|
|
113
|
+
function formatUsd(amount) {
|
|
114
|
+
return `$${amount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
115
|
+
}
|
|
116
|
+
function formatPercent(value) {
|
|
117
|
+
const formatted = (value * 100).toFixed(2);
|
|
118
|
+
return value >= 0 ? import_chalk.default.green(`+${formatted}%`) : import_chalk.default.red(`${formatted}%`);
|
|
119
|
+
}
|
|
120
|
+
function shortenAddress(address, chars = 4) {
|
|
121
|
+
return `${address.slice(0, chars)}...${address.slice(-chars)}`;
|
|
122
|
+
}
|
|
123
|
+
function success(message) {
|
|
124
|
+
console.log(import_chalk.default.green("\u2713"), message);
|
|
125
|
+
}
|
|
126
|
+
function error(message) {
|
|
127
|
+
console.error(import_chalk.default.red("\u2717"), message);
|
|
128
|
+
}
|
|
129
|
+
function warn(message) {
|
|
130
|
+
console.log(import_chalk.default.yellow("\u26A0"), message);
|
|
131
|
+
}
|
|
132
|
+
function info(message) {
|
|
133
|
+
console.log(import_chalk.default.blue("\u2139"), message);
|
|
134
|
+
}
|
|
135
|
+
function link(url) {
|
|
136
|
+
return import_chalk.default.cyan.underline(url);
|
|
137
|
+
}
|
|
138
|
+
function handleError(err) {
|
|
139
|
+
if (err instanceof Error) {
|
|
140
|
+
const apiError = err;
|
|
141
|
+
if (apiError.response?.error) {
|
|
142
|
+
error(apiError.response.error);
|
|
143
|
+
} else {
|
|
144
|
+
error(err.message);
|
|
145
|
+
}
|
|
146
|
+
} else {
|
|
147
|
+
error(String(err));
|
|
148
|
+
}
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
function requireWallet(signer) {
|
|
152
|
+
if (!signer) {
|
|
153
|
+
error("Wallet required for this operation");
|
|
154
|
+
info("Set CLAWDVAULT_WALLET environment variable or use --wallet flag");
|
|
155
|
+
info("Or place wallet.json in ~/.clawdvault/wallet.json");
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/commands/tokens.ts
|
|
161
|
+
var tokensCommand = new import_commander.Command("tokens").description("List tokens");
|
|
162
|
+
tokensCommand.command("list").description("List all tokens").option("-s, --sort <field>", "Sort by field (created_at, market_cap, volume, price)", "created_at").option("-p, --page <number>", "Page number", "1").option("-l, --limit <number>", "Items per page", "20").option("--graduated", "Show only graduated tokens").option("--not-graduated", "Show only non-graduated tokens").option("--json", "Output as JSON").action(async (options) => {
|
|
163
|
+
const spin = spinner("Fetching tokens...").start();
|
|
164
|
+
try {
|
|
165
|
+
const client = (0, import_sdk2.createClient)();
|
|
166
|
+
let graduated;
|
|
167
|
+
if (options.graduated) graduated = true;
|
|
168
|
+
if (options.notGraduated) graduated = false;
|
|
169
|
+
const result = await client.listTokens({
|
|
170
|
+
sort: options.sort,
|
|
171
|
+
page: parseInt(options.page),
|
|
172
|
+
limit: parseInt(options.limit),
|
|
173
|
+
graduated
|
|
174
|
+
});
|
|
175
|
+
spin.stop();
|
|
176
|
+
if (options.json) {
|
|
177
|
+
console.log(JSON.stringify(result, null, 2));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
console.log(import_chalk2.default.bold(`
|
|
181
|
+
\u{1F4CA} Tokens (Page ${result.page}, ${result.tokens.length} of ${result.total})
|
|
182
|
+
`));
|
|
183
|
+
const table = new import_cli_table3.default({
|
|
184
|
+
head: [
|
|
185
|
+
import_chalk2.default.cyan("Symbol"),
|
|
186
|
+
import_chalk2.default.cyan("Name"),
|
|
187
|
+
import_chalk2.default.cyan("Price"),
|
|
188
|
+
import_chalk2.default.cyan("Market Cap"),
|
|
189
|
+
import_chalk2.default.cyan("24h Vol"),
|
|
190
|
+
import_chalk2.default.cyan("Status"),
|
|
191
|
+
import_chalk2.default.cyan("Mint")
|
|
192
|
+
],
|
|
193
|
+
style: { head: [], border: [] }
|
|
194
|
+
});
|
|
195
|
+
for (const token of result.tokens) {
|
|
196
|
+
const status = token.graduated ? import_chalk2.default.green("\u{1F393} Graduated") : import_chalk2.default.yellow("\u{1F4C8} Bonding");
|
|
197
|
+
table.push([
|
|
198
|
+
import_chalk2.default.bold(token.symbol),
|
|
199
|
+
token.name,
|
|
200
|
+
formatSol(token.price_sol),
|
|
201
|
+
formatSol(token.market_cap_sol),
|
|
202
|
+
token.volume_24h ? formatSol(token.volume_24h) : "-",
|
|
203
|
+
status,
|
|
204
|
+
shortenAddress(token.mint)
|
|
205
|
+
]);
|
|
206
|
+
}
|
|
207
|
+
console.log(table.toString());
|
|
208
|
+
console.log();
|
|
209
|
+
} catch (err) {
|
|
210
|
+
spin.stop();
|
|
211
|
+
handleError(err);
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
tokensCommand.action(async () => {
|
|
215
|
+
const listCmd = tokensCommand.commands.find((c) => c.name() === "list");
|
|
216
|
+
if (listCmd) {
|
|
217
|
+
await listCmd.parseAsync([], { from: "user" });
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// src/commands/token.ts
|
|
222
|
+
var import_commander2 = require("commander");
|
|
223
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
224
|
+
var import_cli_table32 = __toESM(require("cli-table3"));
|
|
225
|
+
var fs2 = __toESM(require("fs"));
|
|
226
|
+
var tokenCommand = new import_commander2.Command("token").description("Token operations");
|
|
227
|
+
tokenCommand.command("get <mint>").description("Get token details").option("--json", "Output as JSON").action(async (mint, options) => {
|
|
228
|
+
const spin = spinner("Fetching token...").start();
|
|
229
|
+
try {
|
|
230
|
+
const client = createReadOnlyClient();
|
|
231
|
+
const result = await client.getToken(mint);
|
|
232
|
+
spin.stop();
|
|
233
|
+
if (options.json) {
|
|
234
|
+
console.log(JSON.stringify(result, null, 2));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const { token, trades } = result;
|
|
238
|
+
console.log(import_chalk3.default.bold(`
|
|
239
|
+
\u{1FA99} ${token.name} (${token.symbol})
|
|
240
|
+
`));
|
|
241
|
+
const table = new import_cli_table32.default({
|
|
242
|
+
style: { head: [], border: [] }
|
|
243
|
+
});
|
|
244
|
+
table.push(
|
|
245
|
+
{ [import_chalk3.default.cyan("Mint")]: token.mint },
|
|
246
|
+
{ [import_chalk3.default.cyan("Creator")]: shortenAddress(token.creator) },
|
|
247
|
+
{ [import_chalk3.default.cyan("Price")]: formatSol(token.price_sol) },
|
|
248
|
+
{ [import_chalk3.default.cyan("Market Cap")]: formatSol(token.market_cap_sol) },
|
|
249
|
+
{ [import_chalk3.default.cyan("Status")]: token.graduated ? "\u{1F393} Graduated" : "\u{1F4C8} Bonding Curve" }
|
|
250
|
+
);
|
|
251
|
+
if (token.description) {
|
|
252
|
+
table.push({ [import_chalk3.default.cyan("Description")]: token.description });
|
|
253
|
+
}
|
|
254
|
+
if (token.twitter) {
|
|
255
|
+
table.push({ [import_chalk3.default.cyan("Twitter")]: token.twitter });
|
|
256
|
+
}
|
|
257
|
+
if (token.telegram) {
|
|
258
|
+
table.push({ [import_chalk3.default.cyan("Telegram")]: token.telegram });
|
|
259
|
+
}
|
|
260
|
+
if (token.website) {
|
|
261
|
+
table.push({ [import_chalk3.default.cyan("Website")]: token.website });
|
|
262
|
+
}
|
|
263
|
+
console.log(table.toString());
|
|
264
|
+
if (trades.length > 0) {
|
|
265
|
+
console.log(import_chalk3.default.bold(`
|
|
266
|
+
\u{1F4CA} Recent Trades
|
|
267
|
+
`));
|
|
268
|
+
const tradesTable = new import_cli_table32.default({
|
|
269
|
+
head: [
|
|
270
|
+
import_chalk3.default.cyan("Type"),
|
|
271
|
+
import_chalk3.default.cyan("SOL"),
|
|
272
|
+
import_chalk3.default.cyan("Tokens"),
|
|
273
|
+
import_chalk3.default.cyan("Trader"),
|
|
274
|
+
import_chalk3.default.cyan("Time")
|
|
275
|
+
],
|
|
276
|
+
style: { head: [], border: [] }
|
|
277
|
+
});
|
|
278
|
+
for (const trade of trades.slice(0, 10)) {
|
|
279
|
+
const typeStr = trade.type === "buy" ? import_chalk3.default.green("BUY") : import_chalk3.default.red("SELL");
|
|
280
|
+
tradesTable.push([
|
|
281
|
+
typeStr,
|
|
282
|
+
formatSol(trade.sol_amount),
|
|
283
|
+
formatTokens(trade.token_amount),
|
|
284
|
+
shortenAddress(trade.trader),
|
|
285
|
+
new Date(trade.created_at).toLocaleString()
|
|
286
|
+
]);
|
|
287
|
+
}
|
|
288
|
+
console.log(tradesTable.toString());
|
|
289
|
+
}
|
|
290
|
+
console.log();
|
|
291
|
+
} catch (err) {
|
|
292
|
+
spin.stop();
|
|
293
|
+
handleError(err);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
tokenCommand.command("create").description("Create a new token").requiredOption("-n, --name <name>", "Token name").requiredOption("-s, --symbol <symbol>", "Token symbol").option("-d, --description <desc>", "Token description").option("-i, --image <path>", "Image file path").option("--initial-buy <sol>", "Initial buy amount in SOL").option("--twitter <url>", "Twitter URL").option("--telegram <url>", "Telegram URL").option("--website <url>", "Website URL").option("-w, --wallet <path>", "Wallet file path").action(async (options) => {
|
|
297
|
+
const { client, signer } = createClientWithWallet(options.wallet);
|
|
298
|
+
requireWallet(signer);
|
|
299
|
+
let imageUrl;
|
|
300
|
+
if (options.image) {
|
|
301
|
+
if (!fs2.existsSync(options.image)) {
|
|
302
|
+
throw new Error(`Image file not found: ${options.image}`);
|
|
303
|
+
}
|
|
304
|
+
const uploadSpin = spinner("Uploading image...").start();
|
|
305
|
+
try {
|
|
306
|
+
const upload = await client.uploadImageFromPath(options.image);
|
|
307
|
+
imageUrl = upload.url;
|
|
308
|
+
uploadSpin.succeed("Image uploaded");
|
|
309
|
+
} catch (err) {
|
|
310
|
+
uploadSpin.fail("Image upload failed");
|
|
311
|
+
throw err;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
const createSpin = spinner("Creating token...").start();
|
|
315
|
+
try {
|
|
316
|
+
const result = await client.createToken({
|
|
317
|
+
name: options.name,
|
|
318
|
+
symbol: options.symbol,
|
|
319
|
+
description: options.description,
|
|
320
|
+
image: imageUrl,
|
|
321
|
+
initialBuy: options.initialBuy ? parseFloat(options.initialBuy) : void 0,
|
|
322
|
+
twitter: options.twitter,
|
|
323
|
+
telegram: options.telegram,
|
|
324
|
+
website: options.website
|
|
325
|
+
});
|
|
326
|
+
createSpin.stop();
|
|
327
|
+
success(`Token created successfully!`);
|
|
328
|
+
console.log();
|
|
329
|
+
info(`Name: ${result.token.name}`);
|
|
330
|
+
info(`Symbol: ${result.token.symbol}`);
|
|
331
|
+
info(`Mint: ${result.mint}`);
|
|
332
|
+
info(`Signature: ${result.signature}`);
|
|
333
|
+
console.log();
|
|
334
|
+
info(`Explorer: ${link(result.explorer)}`);
|
|
335
|
+
info(`View: ${link(`https://clawdvault.com/${result.mint}`)}`);
|
|
336
|
+
console.log();
|
|
337
|
+
} catch (err) {
|
|
338
|
+
createSpin.stop();
|
|
339
|
+
handleError(err);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
tokenCommand.command("stats <mint>").description("Get on-chain stats").option("--json", "Output as JSON").action(async (mint, options) => {
|
|
343
|
+
const spin = spinner("Fetching stats...").start();
|
|
344
|
+
try {
|
|
345
|
+
const client = createReadOnlyClient();
|
|
346
|
+
const result = await client.getStats(mint);
|
|
347
|
+
spin.stop();
|
|
348
|
+
if (options.json) {
|
|
349
|
+
console.log(JSON.stringify(result, null, 2));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
console.log(import_chalk3.default.bold(`
|
|
353
|
+
\u{1F4C8} On-Chain Stats
|
|
354
|
+
`));
|
|
355
|
+
const { onChain } = result;
|
|
356
|
+
const table = new import_cli_table32.default({
|
|
357
|
+
style: { head: [], border: [] }
|
|
358
|
+
});
|
|
359
|
+
table.push(
|
|
360
|
+
{ [import_chalk3.default.cyan("Price")]: formatSol(onChain.price) },
|
|
361
|
+
{ [import_chalk3.default.cyan("Market Cap")]: formatSol(onChain.marketCap) },
|
|
362
|
+
{ [import_chalk3.default.cyan("Total Supply")]: formatTokens(onChain.totalSupply) },
|
|
363
|
+
{ [import_chalk3.default.cyan("Circulating")]: formatTokens(onChain.circulatingSupply) },
|
|
364
|
+
{ [import_chalk3.default.cyan("Curve Balance")]: formatTokens(onChain.bondingCurveBalance) },
|
|
365
|
+
{ [import_chalk3.default.cyan("Curve SOL")]: formatSol(onChain.bondingCurveSol) },
|
|
366
|
+
{ [import_chalk3.default.cyan("Status")]: onChain.graduated ? "\u{1F393} Graduated" : "\u{1F4C8} Bonding" }
|
|
367
|
+
);
|
|
368
|
+
console.log(table.toString());
|
|
369
|
+
console.log();
|
|
370
|
+
} catch (err) {
|
|
371
|
+
spin.stop();
|
|
372
|
+
handleError(err);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
tokenCommand.command("holders <mint>").description("Get top token holders").option("--json", "Output as JSON").action(async (mint, options) => {
|
|
376
|
+
const spin = spinner("Fetching holders...").start();
|
|
377
|
+
try {
|
|
378
|
+
const client = createReadOnlyClient();
|
|
379
|
+
const result = await client.getHolders(mint);
|
|
380
|
+
spin.stop();
|
|
381
|
+
if (options.json) {
|
|
382
|
+
console.log(JSON.stringify(result, null, 2));
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
console.log(import_chalk3.default.bold(`
|
|
386
|
+
\u{1F465} Top Holders
|
|
387
|
+
`));
|
|
388
|
+
const table = new import_cli_table32.default({
|
|
389
|
+
head: [
|
|
390
|
+
import_chalk3.default.cyan("#"),
|
|
391
|
+
import_chalk3.default.cyan("Address"),
|
|
392
|
+
import_chalk3.default.cyan("Balance"),
|
|
393
|
+
import_chalk3.default.cyan("%"),
|
|
394
|
+
import_chalk3.default.cyan("Label")
|
|
395
|
+
],
|
|
396
|
+
style: { head: [], border: [] }
|
|
397
|
+
});
|
|
398
|
+
result.holders.forEach((holder, i) => {
|
|
399
|
+
table.push([
|
|
400
|
+
i + 1,
|
|
401
|
+
shortenAddress(holder.address),
|
|
402
|
+
formatTokens(holder.balance),
|
|
403
|
+
`${holder.percentage.toFixed(2)}%`,
|
|
404
|
+
holder.label || "-"
|
|
405
|
+
]);
|
|
406
|
+
});
|
|
407
|
+
console.log(table.toString());
|
|
408
|
+
console.log();
|
|
409
|
+
} catch (err) {
|
|
410
|
+
spin.stop();
|
|
411
|
+
handleError(err);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// src/commands/trade.ts
|
|
416
|
+
var import_commander3 = require("commander");
|
|
417
|
+
var import_chalk4 = __toESM(require("chalk"));
|
|
418
|
+
var import_cli_table33 = __toESM(require("cli-table3"));
|
|
419
|
+
var tradeCommand = new import_commander3.Command("trade").description("Trading operations");
|
|
420
|
+
tradeCommand.command("buy").description("Buy tokens").requiredOption("-m, --mint <address>", "Token mint address").requiredOption("-a, --sol <amount>", "Amount of SOL to spend").option("-s, --slippage <percent>", "Slippage tolerance (default: 1%)", "1").option("-w, --wallet <path>", "Wallet file path").option("--simulate", "Only simulate, don't execute").action(async (options) => {
|
|
421
|
+
const { client, signer, walletAddress } = createClientWithWallet(options.wallet);
|
|
422
|
+
requireWallet(signer);
|
|
423
|
+
const solAmount = parseFloat(options.sol);
|
|
424
|
+
const slippage = parseFloat(options.slippage) / 100;
|
|
425
|
+
const quoteSpin = spinner("Getting quote...").start();
|
|
426
|
+
try {
|
|
427
|
+
const quote = await client.getQuote({
|
|
428
|
+
mint: options.mint,
|
|
429
|
+
type: "buy",
|
|
430
|
+
amount: solAmount
|
|
431
|
+
});
|
|
432
|
+
quoteSpin.stop();
|
|
433
|
+
console.log(import_chalk4.default.bold("\n\u{1F4CA} Trade Preview\n"));
|
|
434
|
+
const table = new import_cli_table33.default({
|
|
435
|
+
style: { head: [], border: [] }
|
|
436
|
+
});
|
|
437
|
+
table.push(
|
|
438
|
+
{ [import_chalk4.default.cyan("Input")]: `${solAmount} SOL` },
|
|
439
|
+
{ [import_chalk4.default.cyan("Output")]: `~${formatTokens(quote.output)} tokens` },
|
|
440
|
+
{ [import_chalk4.default.cyan("Price")]: formatSol(quote.current_price) },
|
|
441
|
+
{ [import_chalk4.default.cyan("Fee")]: formatSol(quote.fee) },
|
|
442
|
+
{ [import_chalk4.default.cyan("Price Impact")]: formatPercent(quote.price_impact) },
|
|
443
|
+
{ [import_chalk4.default.cyan("Slippage")]: `${(slippage * 100).toFixed(1)}%` }
|
|
444
|
+
);
|
|
445
|
+
console.log(table.toString());
|
|
446
|
+
if (options.simulate) {
|
|
447
|
+
info("Simulation only - no transaction executed");
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
if (quote.price_impact > 0.05) {
|
|
451
|
+
warn(`High price impact: ${(quote.price_impact * 100).toFixed(2)}%`);
|
|
452
|
+
}
|
|
453
|
+
console.log();
|
|
454
|
+
const tradeSpin = spinner("Executing trade...").start();
|
|
455
|
+
const jupiterStatus = await client.getJupiterStatus(options.mint);
|
|
456
|
+
let result;
|
|
457
|
+
if (jupiterStatus.graduated) {
|
|
458
|
+
info("Token graduated - routing through Jupiter");
|
|
459
|
+
result = await client.buyJupiter(options.mint, solAmount, Math.floor(slippage * 1e4));
|
|
460
|
+
} else {
|
|
461
|
+
result = await client.buy(options.mint, solAmount, slippage);
|
|
462
|
+
}
|
|
463
|
+
tradeSpin.stop();
|
|
464
|
+
success("Trade executed successfully!");
|
|
465
|
+
console.log();
|
|
466
|
+
info(`Signature: ${result.signature}`);
|
|
467
|
+
info(`SOL spent: ${formatSol(result.trade.solAmount)}`);
|
|
468
|
+
info(`Tokens received: ${formatTokens(result.trade.tokenAmount)}`);
|
|
469
|
+
console.log();
|
|
470
|
+
info(`Explorer: ${link(result.explorer)}`);
|
|
471
|
+
console.log();
|
|
472
|
+
} catch (err) {
|
|
473
|
+
quoteSpin.stop();
|
|
474
|
+
handleError(err);
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
tradeCommand.command("sell").description("Sell tokens").requiredOption("-m, --mint <address>", "Token mint address").option("-a, --amount <tokens>", "Amount of tokens to sell").option("-p, --percent <percent>", "Percentage of holdings to sell").option("-s, --slippage <percent>", "Slippage tolerance (default: 1%)", "1").option("-w, --wallet <path>", "Wallet file path").option("--simulate", "Only simulate, don't execute").action(async (options) => {
|
|
478
|
+
const { client, signer, walletAddress } = createClientWithWallet(options.wallet);
|
|
479
|
+
requireWallet(signer);
|
|
480
|
+
if (!options.amount && !options.percent) {
|
|
481
|
+
throw new Error("Must specify --amount or --percent");
|
|
482
|
+
}
|
|
483
|
+
const slippage = parseFloat(options.slippage) / 100;
|
|
484
|
+
const balanceSpin = spinner("Checking balance...").start();
|
|
485
|
+
try {
|
|
486
|
+
const { balance } = await client.getMyBalance(options.mint);
|
|
487
|
+
balanceSpin.stop();
|
|
488
|
+
if (balance <= 0) {
|
|
489
|
+
throw new Error("No tokens to sell");
|
|
490
|
+
}
|
|
491
|
+
let tokenAmount;
|
|
492
|
+
if (options.percent) {
|
|
493
|
+
const percent = parseFloat(options.percent);
|
|
494
|
+
tokenAmount = balance * (percent / 100);
|
|
495
|
+
info(`Selling ${percent}% of ${formatTokens(balance)} tokens`);
|
|
496
|
+
} else {
|
|
497
|
+
tokenAmount = parseFloat(options.amount);
|
|
498
|
+
if (tokenAmount > balance) {
|
|
499
|
+
throw new Error(`Insufficient balance. Have ${formatTokens(balance)}, trying to sell ${formatTokens(tokenAmount)}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
const quoteSpin = spinner("Getting quote...").start();
|
|
503
|
+
const quote = await client.getQuote({
|
|
504
|
+
mint: options.mint,
|
|
505
|
+
type: "sell",
|
|
506
|
+
amount: tokenAmount
|
|
507
|
+
});
|
|
508
|
+
quoteSpin.stop();
|
|
509
|
+
console.log(import_chalk4.default.bold("\n\u{1F4CA} Trade Preview\n"));
|
|
510
|
+
const table = new import_cli_table33.default({
|
|
511
|
+
style: { head: [], border: [] }
|
|
512
|
+
});
|
|
513
|
+
table.push(
|
|
514
|
+
{ [import_chalk4.default.cyan("Input")]: `${formatTokens(tokenAmount)} tokens` },
|
|
515
|
+
{ [import_chalk4.default.cyan("Output")]: `~${formatSol(quote.output)}` },
|
|
516
|
+
{ [import_chalk4.default.cyan("Price")]: formatSol(quote.current_price) },
|
|
517
|
+
{ [import_chalk4.default.cyan("Fee")]: formatSol(quote.fee) },
|
|
518
|
+
{ [import_chalk4.default.cyan("Price Impact")]: formatPercent(-quote.price_impact) },
|
|
519
|
+
{ [import_chalk4.default.cyan("Slippage")]: `${(slippage * 100).toFixed(1)}%` }
|
|
520
|
+
);
|
|
521
|
+
console.log(table.toString());
|
|
522
|
+
if (options.simulate) {
|
|
523
|
+
info("Simulation only - no transaction executed");
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
if (quote.price_impact > 0.05) {
|
|
527
|
+
warn(`High price impact: ${(quote.price_impact * 100).toFixed(2)}%`);
|
|
528
|
+
}
|
|
529
|
+
console.log();
|
|
530
|
+
const tradeSpin = spinner("Executing trade...").start();
|
|
531
|
+
const jupiterStatus = await client.getJupiterStatus(options.mint);
|
|
532
|
+
let result;
|
|
533
|
+
if (jupiterStatus.graduated) {
|
|
534
|
+
info("Token graduated - routing through Jupiter");
|
|
535
|
+
result = await client.sellJupiter(options.mint, tokenAmount, Math.floor(slippage * 1e4));
|
|
536
|
+
} else {
|
|
537
|
+
result = await client.sell(options.mint, tokenAmount, slippage);
|
|
538
|
+
}
|
|
539
|
+
tradeSpin.stop();
|
|
540
|
+
success("Trade executed successfully!");
|
|
541
|
+
console.log();
|
|
542
|
+
info(`Signature: ${result.signature}`);
|
|
543
|
+
info(`Tokens sold: ${formatTokens(result.trade.tokenAmount)}`);
|
|
544
|
+
info(`SOL received: ${formatSol(result.trade.solAmount)}`);
|
|
545
|
+
console.log();
|
|
546
|
+
info(`Explorer: ${link(result.explorer)}`);
|
|
547
|
+
console.log();
|
|
548
|
+
} catch (err) {
|
|
549
|
+
balanceSpin.stop();
|
|
550
|
+
handleError(err);
|
|
551
|
+
}
|
|
552
|
+
});
|
|
553
|
+
tradeCommand.command("quote").description("Get a price quote without executing").requiredOption("-m, --mint <address>", "Token mint address").requiredOption("-t, --type <type>", "Trade type (buy or sell)").requiredOption("-a, --amount <amount>", "Amount (SOL for buy, tokens for sell)").option("--json", "Output as JSON").action(async (options) => {
|
|
554
|
+
const spin = spinner("Getting quote...").start();
|
|
555
|
+
try {
|
|
556
|
+
const client = createReadOnlyClient();
|
|
557
|
+
const quote = await client.getQuote({
|
|
558
|
+
mint: options.mint,
|
|
559
|
+
type: options.type,
|
|
560
|
+
amount: parseFloat(options.amount)
|
|
561
|
+
});
|
|
562
|
+
spin.stop();
|
|
563
|
+
if (options.json) {
|
|
564
|
+
console.log(JSON.stringify(quote, null, 2));
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const isBuy = options.type === "buy";
|
|
568
|
+
console.log(import_chalk4.default.bold(`
|
|
569
|
+
\u{1F4B1} ${isBuy ? "Buy" : "Sell"} Quote
|
|
570
|
+
`));
|
|
571
|
+
const table = new import_cli_table33.default({
|
|
572
|
+
style: { head: [], border: [] }
|
|
573
|
+
});
|
|
574
|
+
table.push(
|
|
575
|
+
{ [import_chalk4.default.cyan("Input")]: isBuy ? `${options.amount} SOL` : `${formatTokens(parseFloat(options.amount))} tokens` },
|
|
576
|
+
{ [import_chalk4.default.cyan("Output")]: isBuy ? `~${formatTokens(quote.output)} tokens` : `~${formatSol(quote.output)}` },
|
|
577
|
+
{ [import_chalk4.default.cyan("Price")]: formatSol(quote.current_price) },
|
|
578
|
+
{ [import_chalk4.default.cyan("Fee")]: formatSol(quote.fee) },
|
|
579
|
+
{ [import_chalk4.default.cyan("Price Impact")]: formatPercent(isBuy ? quote.price_impact : -quote.price_impact) }
|
|
580
|
+
);
|
|
581
|
+
console.log(table.toString());
|
|
582
|
+
console.log();
|
|
583
|
+
} catch (err) {
|
|
584
|
+
spin.stop();
|
|
585
|
+
handleError(err);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
tradeCommand.command("history").description("Get trade history for a token").requiredOption("-m, --mint <address>", "Token mint address").option("-l, --limit <number>", "Number of trades", "20").option("--json", "Output as JSON").action(async (options) => {
|
|
589
|
+
const spin = spinner("Fetching trades...").start();
|
|
590
|
+
try {
|
|
591
|
+
const client = createReadOnlyClient();
|
|
592
|
+
const result = await client.getTrades({
|
|
593
|
+
mint: options.mint,
|
|
594
|
+
limit: parseInt(options.limit)
|
|
595
|
+
});
|
|
596
|
+
spin.stop();
|
|
597
|
+
if (options.json) {
|
|
598
|
+
console.log(JSON.stringify(result, null, 2));
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
console.log(import_chalk4.default.bold(`
|
|
602
|
+
\u{1F4DC} Trade History
|
|
603
|
+
`));
|
|
604
|
+
const table = new import_cli_table33.default({
|
|
605
|
+
head: [
|
|
606
|
+
import_chalk4.default.cyan("Type"),
|
|
607
|
+
import_chalk4.default.cyan("SOL"),
|
|
608
|
+
import_chalk4.default.cyan("Tokens"),
|
|
609
|
+
import_chalk4.default.cyan("Price"),
|
|
610
|
+
import_chalk4.default.cyan("Trader"),
|
|
611
|
+
import_chalk4.default.cyan("Time")
|
|
612
|
+
],
|
|
613
|
+
style: { head: [], border: [] }
|
|
614
|
+
});
|
|
615
|
+
for (const trade of result.trades) {
|
|
616
|
+
const typeStr = trade.type === "buy" ? import_chalk4.default.green("BUY") : import_chalk4.default.red("SELL");
|
|
617
|
+
table.push([
|
|
618
|
+
typeStr,
|
|
619
|
+
formatSol(trade.sol_amount),
|
|
620
|
+
formatTokens(trade.token_amount),
|
|
621
|
+
formatSol(trade.price_sol || trade.price || 0),
|
|
622
|
+
shortenAddress(trade.trader),
|
|
623
|
+
new Date(trade.created_at).toLocaleString()
|
|
624
|
+
]);
|
|
625
|
+
}
|
|
626
|
+
console.log(table.toString());
|
|
627
|
+
console.log();
|
|
628
|
+
} catch (err) {
|
|
629
|
+
spin.stop();
|
|
630
|
+
handleError(err);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// src/commands/wallet.ts
|
|
635
|
+
var import_commander4 = require("commander");
|
|
636
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
637
|
+
var import_cli_table34 = __toESM(require("cli-table3"));
|
|
638
|
+
var fs3 = __toESM(require("fs"));
|
|
639
|
+
var path2 = __toESM(require("path"));
|
|
640
|
+
var import_web3 = require("@solana/web3.js");
|
|
641
|
+
var import_spl_token = require("@solana/spl-token");
|
|
642
|
+
var walletCommand = new import_commander4.Command("wallet").description("Wallet operations");
|
|
643
|
+
walletCommand.command("info").description("Show wallet information").option("-w, --wallet <path>", "Wallet file path").action(async (options) => {
|
|
644
|
+
try {
|
|
645
|
+
const { signer, walletAddress } = createClientWithWallet(options.wallet);
|
|
646
|
+
if (!signer) {
|
|
647
|
+
warn("No wallet configured");
|
|
648
|
+
info("Set CLAWDVAULT_WALLET environment variable");
|
|
649
|
+
info("Or use: clawdvault wallet init");
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
console.log(import_chalk5.default.bold("\n\u{1F45B} Wallet Info\n"));
|
|
653
|
+
const table = new import_cli_table34.default({
|
|
654
|
+
style: { head: [], border: [] }
|
|
655
|
+
});
|
|
656
|
+
table.push(
|
|
657
|
+
{ [import_chalk5.default.cyan("Address")]: walletAddress },
|
|
658
|
+
{ [import_chalk5.default.cyan("Source")]: options.wallet || getWalletPath() || "environment" }
|
|
659
|
+
);
|
|
660
|
+
console.log(table.toString());
|
|
661
|
+
console.log();
|
|
662
|
+
} catch (err) {
|
|
663
|
+
handleError(err);
|
|
664
|
+
}
|
|
665
|
+
});
|
|
666
|
+
walletCommand.command("init").description("Generate a new wallet").option("-o, --output <path>", "Output file path (default: ~/.clawdvault/wallet.json)").option("--force", "Overwrite existing wallet").action(async (options) => {
|
|
667
|
+
try {
|
|
668
|
+
const configDir = getConfigDir();
|
|
669
|
+
const outputPath = options.output || path2.join(configDir, "wallet.json");
|
|
670
|
+
if (fs3.existsSync(outputPath) && !options.force) {
|
|
671
|
+
warn(`Wallet already exists at ${outputPath}`);
|
|
672
|
+
info("Use --force to overwrite");
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (!fs3.existsSync(configDir)) {
|
|
676
|
+
fs3.mkdirSync(configDir, { recursive: true });
|
|
677
|
+
}
|
|
678
|
+
const keypair = import_web3.Keypair.generate();
|
|
679
|
+
const secretKey = Array.from(keypair.secretKey);
|
|
680
|
+
fs3.writeFileSync(outputPath, JSON.stringify(secretKey));
|
|
681
|
+
fs3.chmodSync(outputPath, 384);
|
|
682
|
+
success("Wallet created successfully!");
|
|
683
|
+
console.log();
|
|
684
|
+
info(`Address: ${keypair.publicKey.toBase58()}`);
|
|
685
|
+
info(`Saved to: ${outputPath}`);
|
|
686
|
+
console.log();
|
|
687
|
+
warn("\u26A0\uFE0F IMPORTANT: Back up your wallet file! Loss of this file means loss of funds.");
|
|
688
|
+
console.log();
|
|
689
|
+
} catch (err) {
|
|
690
|
+
handleError(err);
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
walletCommand.command("balance").description("Get wallet balance for a token").requiredOption("-m, --mint <address>", "Token mint address").option("-w, --wallet <path>", "Wallet file path").option("--json", "Output as JSON").action(async (options) => {
|
|
694
|
+
const spin = spinner("Fetching balance...").start();
|
|
695
|
+
try {
|
|
696
|
+
const { client, signer, walletAddress } = createClientWithWallet(options.wallet);
|
|
697
|
+
if (!signer) {
|
|
698
|
+
spin.stop();
|
|
699
|
+
warn("No wallet configured");
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
const result = await client.getBalance(walletAddress, options.mint);
|
|
703
|
+
spin.stop();
|
|
704
|
+
if (options.json) {
|
|
705
|
+
console.log(JSON.stringify(result, null, 2));
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
console.log(import_chalk5.default.bold("\n\u{1F4B0} Token Balance\n"));
|
|
709
|
+
const table = new import_cli_table34.default({
|
|
710
|
+
style: { head: [], border: [] }
|
|
711
|
+
});
|
|
712
|
+
table.push(
|
|
713
|
+
{ [import_chalk5.default.cyan("Wallet")]: result.wallet },
|
|
714
|
+
{ [import_chalk5.default.cyan("Token")]: result.mint },
|
|
715
|
+
{ [import_chalk5.default.cyan("Balance")]: formatTokens(result.balance) }
|
|
716
|
+
);
|
|
717
|
+
console.log(table.toString());
|
|
718
|
+
console.log();
|
|
719
|
+
} catch (err) {
|
|
720
|
+
spin.stop();
|
|
721
|
+
handleError(err);
|
|
722
|
+
}
|
|
723
|
+
});
|
|
724
|
+
walletCommand.command("address").description("Show address for a wallet file or private key").option("-w, --wallet <path>", "Wallet file path").option("-k, --key <key>", "Private key (base58)").action(async (options) => {
|
|
725
|
+
try {
|
|
726
|
+
let signer;
|
|
727
|
+
if (options.key) {
|
|
728
|
+
signer = loadSigner(options.key);
|
|
729
|
+
} else {
|
|
730
|
+
const { signer: s } = createClientWithWallet(options.wallet);
|
|
731
|
+
signer = s;
|
|
732
|
+
}
|
|
733
|
+
if (!signer) {
|
|
734
|
+
warn("No wallet found");
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
console.log(signer.publicKey.toBase58());
|
|
738
|
+
} catch (err) {
|
|
739
|
+
handleError(err);
|
|
740
|
+
}
|
|
741
|
+
});
|
|
742
|
+
walletCommand.command("network").description("Show network status").option("--json", "Output as JSON").action(async (options) => {
|
|
743
|
+
const spin = spinner("Fetching network status...").start();
|
|
744
|
+
try {
|
|
745
|
+
const { client } = createClientWithWallet();
|
|
746
|
+
const result = await client.getNetworkStatus();
|
|
747
|
+
spin.stop();
|
|
748
|
+
if (options.json) {
|
|
749
|
+
console.log(JSON.stringify(result, null, 2));
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
console.log(import_chalk5.default.bold("\n\u{1F310} Network Status\n"));
|
|
753
|
+
const table = new import_cli_table34.default({
|
|
754
|
+
style: { head: [], border: [] }
|
|
755
|
+
});
|
|
756
|
+
table.push(
|
|
757
|
+
{ [import_chalk5.default.cyan("Network")]: result.network },
|
|
758
|
+
{ [import_chalk5.default.cyan("Program ID")]: result.programId },
|
|
759
|
+
{ [import_chalk5.default.cyan("RPC URL")]: result.rpcUrl },
|
|
760
|
+
{ [import_chalk5.default.cyan("Initialized")]: result.configInitialized ? "\u2713 Yes" : "\u2717 No" }
|
|
761
|
+
);
|
|
762
|
+
console.log(table.toString());
|
|
763
|
+
console.log();
|
|
764
|
+
} catch (err) {
|
|
765
|
+
spin.stop();
|
|
766
|
+
handleError(err);
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
walletCommand.command("sol-price").description("Get current SOL/USD price").option("--json", "Output as JSON").action(async (options) => {
|
|
770
|
+
const spin = spinner("Fetching SOL price...").start();
|
|
771
|
+
try {
|
|
772
|
+
const { client } = createClientWithWallet();
|
|
773
|
+
const result = await client.getSolPrice();
|
|
774
|
+
spin.stop();
|
|
775
|
+
if (options.json) {
|
|
776
|
+
console.log(JSON.stringify(result, null, 2));
|
|
777
|
+
return;
|
|
778
|
+
}
|
|
779
|
+
console.log(import_chalk5.default.bold(`
|
|
780
|
+
\u{1F4B2} SOL Price: $${result.price.toFixed(2)}
|
|
781
|
+
`));
|
|
782
|
+
if (result.cached) {
|
|
783
|
+
info(`Cached ${result.age}s ago from ${result.source}`);
|
|
784
|
+
}
|
|
785
|
+
console.log();
|
|
786
|
+
} catch (err) {
|
|
787
|
+
spin.stop();
|
|
788
|
+
handleError(err);
|
|
789
|
+
}
|
|
790
|
+
});
|
|
791
|
+
function getRpcUrl() {
|
|
792
|
+
return process.env.SOLANA_RPC_URL || process.env.RPC_URL || "https://api.devnet.solana.com";
|
|
793
|
+
}
|
|
794
|
+
function isDevnet(rpcUrl) {
|
|
795
|
+
return rpcUrl.includes("devnet");
|
|
796
|
+
}
|
|
797
|
+
walletCommand.command("sol-balance").description("Get SOL balance with USD value").option("-w, --wallet <path>", "Wallet file path").option("--rpc <url>", "Solana RPC URL").option("--json", "Output as JSON").action(async (options) => {
|
|
798
|
+
const spin = spinner("Fetching SOL balance...").start();
|
|
799
|
+
try {
|
|
800
|
+
const { client, signer, walletAddress } = createClientWithWallet(options.wallet);
|
|
801
|
+
requireWallet(signer);
|
|
802
|
+
const rpcUrl = options.rpc || getRpcUrl();
|
|
803
|
+
const connection = new import_web3.Connection(rpcUrl, "confirmed");
|
|
804
|
+
const balanceLamports = await connection.getBalance(signer.publicKey);
|
|
805
|
+
const balanceSol = balanceLamports / import_web3.LAMPORTS_PER_SOL;
|
|
806
|
+
let solPrice = 0;
|
|
807
|
+
let usdValue = 0;
|
|
808
|
+
try {
|
|
809
|
+
const priceData = await client.getSolPrice();
|
|
810
|
+
solPrice = priceData.price;
|
|
811
|
+
usdValue = balanceSol * solPrice;
|
|
812
|
+
} catch {
|
|
813
|
+
}
|
|
814
|
+
spin.stop();
|
|
815
|
+
const result = {
|
|
816
|
+
wallet: walletAddress,
|
|
817
|
+
balance: balanceSol,
|
|
818
|
+
lamports: balanceLamports,
|
|
819
|
+
usdValue,
|
|
820
|
+
solPrice,
|
|
821
|
+
rpc: rpcUrl,
|
|
822
|
+
network: isDevnet(rpcUrl) ? "devnet" : "mainnet"
|
|
823
|
+
};
|
|
824
|
+
if (options.json) {
|
|
825
|
+
console.log(JSON.stringify(result, null, 2));
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
console.log(import_chalk5.default.bold("\n\u{1F4B0} SOL Balance\n"));
|
|
829
|
+
const table = new import_cli_table34.default({
|
|
830
|
+
style: { head: [], border: [] }
|
|
831
|
+
});
|
|
832
|
+
table.push(
|
|
833
|
+
{ [import_chalk5.default.cyan("Wallet")]: walletAddress },
|
|
834
|
+
{ [import_chalk5.default.cyan("Balance")]: `${balanceSol.toFixed(9)} SOL` },
|
|
835
|
+
{ [import_chalk5.default.cyan("USD Value")]: solPrice > 0 ? formatUsd(usdValue) : "N/A" },
|
|
836
|
+
{ [import_chalk5.default.cyan("Network")]: result.network }
|
|
837
|
+
);
|
|
838
|
+
console.log(table.toString());
|
|
839
|
+
console.log();
|
|
840
|
+
} catch (err) {
|
|
841
|
+
spin.stop();
|
|
842
|
+
handleError(err);
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
walletCommand.command("transfer").description("Send SOL or tokens to another wallet").requiredOption("--to <address>", "Recipient address").option("--sol <amount>", "Amount of SOL to send").option("-m, --mint <address>", "Token mint address (for token transfers)").option("-a, --amount <amount>", "Amount of tokens to send").option("-w, --wallet <path>", "Wallet file path").option("--rpc <url>", "Solana RPC URL").action(async (options) => {
|
|
846
|
+
try {
|
|
847
|
+
const { signer, walletAddress } = createClientWithWallet(options.wallet);
|
|
848
|
+
requireWallet(signer);
|
|
849
|
+
if (!options.sol && !options.mint) {
|
|
850
|
+
error("Must specify either --sol or --mint with --amount");
|
|
851
|
+
process.exit(1);
|
|
852
|
+
}
|
|
853
|
+
if (options.mint && !options.amount) {
|
|
854
|
+
error("Must specify --amount when using --mint");
|
|
855
|
+
process.exit(1);
|
|
856
|
+
}
|
|
857
|
+
const rpcUrl = options.rpc || getRpcUrl();
|
|
858
|
+
const connection = new import_web3.Connection(rpcUrl, "confirmed");
|
|
859
|
+
const recipient = new import_web3.PublicKey(options.to);
|
|
860
|
+
if (options.sol) {
|
|
861
|
+
const solAmount = parseFloat(options.sol);
|
|
862
|
+
if (isNaN(solAmount) || solAmount <= 0) {
|
|
863
|
+
error("Invalid SOL amount");
|
|
864
|
+
process.exit(1);
|
|
865
|
+
}
|
|
866
|
+
const spin = spinner(`Sending ${solAmount} SOL to ${options.to.slice(0, 8)}...`).start();
|
|
867
|
+
const lamports = Math.floor(solAmount * import_web3.LAMPORTS_PER_SOL);
|
|
868
|
+
const transaction = new import_web3.Transaction().add(
|
|
869
|
+
import_web3.SystemProgram.transfer({
|
|
870
|
+
fromPubkey: signer.publicKey,
|
|
871
|
+
toPubkey: recipient,
|
|
872
|
+
lamports
|
|
873
|
+
})
|
|
874
|
+
);
|
|
875
|
+
const signature = await (0, import_web3.sendAndConfirmTransaction)(
|
|
876
|
+
connection,
|
|
877
|
+
transaction,
|
|
878
|
+
[signer.keypair],
|
|
879
|
+
{ commitment: "confirmed" }
|
|
880
|
+
);
|
|
881
|
+
spin.stop();
|
|
882
|
+
success(`Sent ${solAmount} SOL to ${options.to}`);
|
|
883
|
+
console.log();
|
|
884
|
+
info(`Transaction: ${signature}`);
|
|
885
|
+
if (isDevnet(rpcUrl)) {
|
|
886
|
+
info(`Explorer: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
|
|
887
|
+
} else {
|
|
888
|
+
info(`Explorer: https://explorer.solana.com/tx/${signature}`);
|
|
889
|
+
}
|
|
890
|
+
console.log();
|
|
891
|
+
} else if (options.mint) {
|
|
892
|
+
const tokenAmount = parseFloat(options.amount);
|
|
893
|
+
if (isNaN(tokenAmount) || tokenAmount <= 0) {
|
|
894
|
+
error("Invalid token amount");
|
|
895
|
+
process.exit(1);
|
|
896
|
+
}
|
|
897
|
+
const spin = spinner(`Sending ${tokenAmount} tokens to ${options.to.slice(0, 8)}...`).start();
|
|
898
|
+
const mint = new import_web3.PublicKey(options.mint);
|
|
899
|
+
const sourceAta = await (0, import_spl_token.getAssociatedTokenAddress)(
|
|
900
|
+
mint,
|
|
901
|
+
signer.publicKey,
|
|
902
|
+
false,
|
|
903
|
+
import_spl_token.TOKEN_PROGRAM_ID,
|
|
904
|
+
import_spl_token.ASSOCIATED_TOKEN_PROGRAM_ID
|
|
905
|
+
);
|
|
906
|
+
const destAta = await (0, import_spl_token.getAssociatedTokenAddress)(
|
|
907
|
+
mint,
|
|
908
|
+
recipient,
|
|
909
|
+
false,
|
|
910
|
+
import_spl_token.TOKEN_PROGRAM_ID,
|
|
911
|
+
import_spl_token.ASSOCIATED_TOKEN_PROGRAM_ID
|
|
912
|
+
);
|
|
913
|
+
const transaction = new import_web3.Transaction();
|
|
914
|
+
try {
|
|
915
|
+
await (0, import_spl_token.getAccount)(connection, destAta);
|
|
916
|
+
} catch {
|
|
917
|
+
transaction.add(
|
|
918
|
+
(0, import_spl_token.createAssociatedTokenAccountInstruction)(
|
|
919
|
+
signer.publicKey,
|
|
920
|
+
// payer
|
|
921
|
+
destAta,
|
|
922
|
+
// ata
|
|
923
|
+
recipient,
|
|
924
|
+
// owner
|
|
925
|
+
mint,
|
|
926
|
+
// mint
|
|
927
|
+
import_spl_token.TOKEN_PROGRAM_ID,
|
|
928
|
+
import_spl_token.ASSOCIATED_TOKEN_PROGRAM_ID
|
|
929
|
+
)
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
const decimals = 6;
|
|
933
|
+
const rawAmount = BigInt(Math.floor(tokenAmount * Math.pow(10, decimals)));
|
|
934
|
+
transaction.add(
|
|
935
|
+
(0, import_spl_token.createTransferInstruction)(
|
|
936
|
+
sourceAta,
|
|
937
|
+
destAta,
|
|
938
|
+
signer.publicKey,
|
|
939
|
+
rawAmount,
|
|
940
|
+
[],
|
|
941
|
+
import_spl_token.TOKEN_PROGRAM_ID
|
|
942
|
+
)
|
|
943
|
+
);
|
|
944
|
+
const signature = await (0, import_web3.sendAndConfirmTransaction)(
|
|
945
|
+
connection,
|
|
946
|
+
transaction,
|
|
947
|
+
[signer.keypair],
|
|
948
|
+
{ commitment: "confirmed" }
|
|
949
|
+
);
|
|
950
|
+
spin.stop();
|
|
951
|
+
success(`Sent ${tokenAmount} tokens to ${options.to}`);
|
|
952
|
+
console.log();
|
|
953
|
+
info(`Transaction: ${signature}`);
|
|
954
|
+
if (isDevnet(rpcUrl)) {
|
|
955
|
+
info(`Explorer: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
|
|
956
|
+
} else {
|
|
957
|
+
info(`Explorer: https://explorer.solana.com/tx/${signature}`);
|
|
958
|
+
}
|
|
959
|
+
console.log();
|
|
960
|
+
}
|
|
961
|
+
} catch (err) {
|
|
962
|
+
handleError(err);
|
|
963
|
+
}
|
|
964
|
+
});
|
|
965
|
+
walletCommand.command("airdrop").description("Request SOL from devnet faucet (devnet only)").option("--sol <amount>", "Amount of SOL to request (max 2)", "1").option("-w, --wallet <path>", "Wallet file path").option("--rpc <url>", "Solana RPC URL").action(async (options) => {
|
|
966
|
+
try {
|
|
967
|
+
const { signer, walletAddress } = createClientWithWallet(options.wallet);
|
|
968
|
+
requireWallet(signer);
|
|
969
|
+
const rpcUrl = options.rpc || getRpcUrl();
|
|
970
|
+
if (!isDevnet(rpcUrl)) {
|
|
971
|
+
error("Airdrop only works on devnet!");
|
|
972
|
+
info(`Current RPC: ${rpcUrl}`);
|
|
973
|
+
info("Use --rpc https://api.devnet.solana.com or set SOLANA_RPC_URL");
|
|
974
|
+
process.exit(1);
|
|
975
|
+
}
|
|
976
|
+
const solAmount = parseFloat(options.sol);
|
|
977
|
+
if (isNaN(solAmount) || solAmount <= 0 || solAmount > 2) {
|
|
978
|
+
error("Invalid SOL amount (must be between 0 and 2)");
|
|
979
|
+
process.exit(1);
|
|
980
|
+
}
|
|
981
|
+
const spin = spinner(`Requesting ${solAmount} SOL airdrop...`).start();
|
|
982
|
+
const connection = new import_web3.Connection(rpcUrl, "confirmed");
|
|
983
|
+
const lamports = Math.floor(solAmount * import_web3.LAMPORTS_PER_SOL);
|
|
984
|
+
try {
|
|
985
|
+
const signature = await connection.requestAirdrop(signer.publicKey, lamports);
|
|
986
|
+
spin.text = "Waiting for confirmation...";
|
|
987
|
+
await connection.confirmTransaction(signature, "confirmed");
|
|
988
|
+
spin.stop();
|
|
989
|
+
success(`Airdrop successful! Received ${solAmount} SOL`);
|
|
990
|
+
console.log();
|
|
991
|
+
info(`Transaction: ${signature}`);
|
|
992
|
+
info(`Explorer: https://explorer.solana.com/tx/${signature}?cluster=devnet`);
|
|
993
|
+
console.log();
|
|
994
|
+
const newBalance = await connection.getBalance(signer.publicKey);
|
|
995
|
+
info(`New balance: ${(newBalance / import_web3.LAMPORTS_PER_SOL).toFixed(9)} SOL`);
|
|
996
|
+
console.log();
|
|
997
|
+
} catch (airdropErr) {
|
|
998
|
+
spin.stop();
|
|
999
|
+
if (airdropErr.message?.includes("429") || airdropErr.message?.includes("rate")) {
|
|
1000
|
+
error("Rate limited. Please wait a minute and try again.");
|
|
1001
|
+
} else {
|
|
1002
|
+
throw airdropErr;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
} catch (err) {
|
|
1006
|
+
handleError(err);
|
|
1007
|
+
}
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
// src/index.ts
|
|
1011
|
+
var program = new import_commander5.Command();
|
|
1012
|
+
program.name("clawdvault").description("CLI for ClawdVault - Solana token launchpad").version("0.1.0");
|
|
1013
|
+
program.addCommand(tokensCommand);
|
|
1014
|
+
program.addCommand(tokenCommand);
|
|
1015
|
+
program.addCommand(tradeCommand);
|
|
1016
|
+
program.addCommand(walletCommand);
|
|
1017
|
+
program.hook("preAction", () => {
|
|
1018
|
+
});
|
|
1019
|
+
program.configureOutput({
|
|
1020
|
+
outputError: (str, write) => {
|
|
1021
|
+
write(import_chalk6.default.red(str));
|
|
1022
|
+
}
|
|
1023
|
+
});
|
|
1024
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@clawdvault/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for ClawdVault - Solana token launchpad",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"clawdvault": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup src/index.ts --format cjs --clean --shims",
|
|
14
|
+
"dev": "tsup src/index.ts --format cjs --watch --shims",
|
|
15
|
+
"clean": "rm -rf dist"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@clawdvault/sdk": "^0.1.0",
|
|
19
|
+
"@solana/spl-token": "^0.4.0",
|
|
20
|
+
"@solana/web3.js": "^1.91.0",
|
|
21
|
+
"bs58": "^5.0.0",
|
|
22
|
+
"chalk": "^4.1.2",
|
|
23
|
+
"commander": "^12.0.0",
|
|
24
|
+
"ora": "^5.4.1",
|
|
25
|
+
"cli-table3": "^0.6.3"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.11.0",
|
|
29
|
+
"tsup": "^8.0.1",
|
|
30
|
+
"typescript": "^5.3.3"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"clawdvault",
|
|
34
|
+
"solana",
|
|
35
|
+
"cli",
|
|
36
|
+
"token",
|
|
37
|
+
"launchpad"
|
|
38
|
+
],
|
|
39
|
+
"author": "ClawdVault",
|
|
40
|
+
"license": "MIT"
|
|
41
|
+
}
|