@solard/cli 0.1.1

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/bin/solard.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env bun
2
+ import "../src/index.js";
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@solard/cli",
3
+ "version": "0.1.1",
4
+ "description": "Multi-wallet Solana trading CLI UI and commands.",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "bin": {
8
+ "solard": "./bin/solard.ts",
9
+ "slrd": "./bin/solard.ts"
10
+ },
11
+ "files": [
12
+ "bin",
13
+ "src",
14
+ "README.md"
15
+ ],
16
+ "dependencies": {
17
+ "@solard/core": "0.1.0"
18
+ },
19
+ "license": "MIT"
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,931 @@
1
+ #!/usr/bin/env bun
2
+ import type { TokenRow } from "@solard/core/db/schema.ts";
3
+ import { emit } from "@solard/core/core/ui.ts";
4
+ import { listScripts, runScript } from "@solard/core/runner/run-script.ts";
5
+
6
+ const OWL = "🦉";
7
+ type Flags = Map<string, string>;
8
+ function args(input: string[]): { values: string[]; flags: Flags } {
9
+ const values: string[] = [],
10
+ flags = new Map<string, string>();
11
+ for (let i = 0; i < input.length; i++) {
12
+ const current = input[i]!;
13
+ if (current === "-w") {
14
+ flags.set("wallet", input[++i] ?? "");
15
+ continue;
16
+ }
17
+ if (current.startsWith("--")) {
18
+ const [key, inline] = current.slice(2).split("=", 2);
19
+ if (inline != null) flags.set(key!, inline);
20
+ else if (input[i + 1] && !input[i + 1]!.startsWith("-"))
21
+ flags.set(key!, input[++i]!);
22
+ else flags.set(key!, "true");
23
+ continue;
24
+ }
25
+ values.push(current);
26
+ }
27
+ return { values, flags };
28
+ }
29
+ function need(flags: Flags, key: string): string {
30
+ const value = flags.get(key);
31
+ if (!value || value === "true") throw new Error(`Missing --${key} <value>`);
32
+ return value;
33
+ }
34
+ function int(flags: Flags, key: string, fallback?: number): number | undefined {
35
+ const value = flags.get(key);
36
+ if (!value) return fallback;
37
+ const parsed = Number(value);
38
+ if (!Number.isFinite(parsed)) throw new Error(`Invalid --${key}: ${value}`);
39
+ return parsed;
40
+ }
41
+ function json(value: unknown): string {
42
+ return JSON.stringify(
43
+ value,
44
+ (_, item) => (typeof item === "bigint" ? item.toString() : item),
45
+ 2,
46
+ );
47
+ }
48
+ function formatError(error: unknown): string {
49
+ if (error instanceof Error) {
50
+ const code =
51
+ "code" in error &&
52
+ typeof (error as Error & { code?: unknown }).code === "string"
53
+ ? ` [${String((error as Error & { code?: unknown }).code)}]`
54
+ : "";
55
+ const message = error.message.trim();
56
+ const summary = `${error.name}${code}${message ? `: ${message}` : " (no message)"}`;
57
+ return error.stack && error.stack !== summary
58
+ ? `${summary}\n${error.stack}`
59
+ : summary;
60
+ }
61
+ try {
62
+ const encoded = json(error);
63
+ return encoded && encoded !== "undefined" ? encoded : String(error);
64
+ } catch {
65
+ return String(error);
66
+ }
67
+ }
68
+ function duration(value: string | undefined, fallbackMs: number): number {
69
+ if (!value) return fallbackMs;
70
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)?$/i.exec(value.trim());
71
+ if (!match)
72
+ throw new Error(`Invalid duration: ${value}. Use e.g. 500ms, 5s, 15m, 1h.`);
73
+ const n = Number(match[1]);
74
+ const scale = ({ ms: 1, s: 1_000, m: 60_000, h: 3_600_000 } as const)[
75
+ (match[2]?.toLowerCase() ?? "ms") as "ms" | "s" | "m" | "h"
76
+ ];
77
+ return Math.floor(n * scale);
78
+ }
79
+ function formatPrice(value: number | null | undefined): string {
80
+ if (value == null || !Number.isFinite(value)) return "n/a";
81
+ return value >= 0.001
82
+ ? value.toFixed(9).replace(/0+$/, "").replace(/\.$/, "")
83
+ : value.toExponential(6);
84
+ }
85
+
86
+ function csv(value: string | undefined): string[] {
87
+ return (value ?? "")
88
+ .split(",")
89
+ .map((item) => item.trim())
90
+ .filter(Boolean);
91
+ }
92
+ function targetWallets(
93
+ slrd: { groupWallets(name: string): unknown[] },
94
+ flags: Flags,
95
+ usage: string,
96
+ ): { mode: "wallet" | "wallets" | "group"; refs: string[]; group?: string } {
97
+ const wallet = flags.get("wallet");
98
+ const wallets = csv(flags.get("wallets"));
99
+ const group = flags.get("group");
100
+ const selected = [Boolean(wallet), wallets.length > 0, Boolean(group)].filter(
101
+ Boolean,
102
+ ).length;
103
+ if (selected !== 1)
104
+ throw new Error(
105
+ `${usage} Supply exactly one of --wallet, --wallets, or --group.`,
106
+ );
107
+ if (wallet) return { mode: "wallet", refs: [wallet] };
108
+ if (wallets.length > 0) return { mode: "wallets", refs: wallets };
109
+ return {
110
+ mode: "group",
111
+ refs: slrd.groupWallets(group!).map((ref) => String(ref)),
112
+ group,
113
+ };
114
+ }
115
+ function help(): string {
116
+ return `${OWL} slrd — multi-wallet Solana CLI + SDK for traders and AI agents
117
+
118
+ Wallets and tokens
119
+ slrd import <private_key> [name]
120
+ cat key.json | slrd import --stdin [name]
121
+ slrd wallets [--token <token>] [--addresses-only] Show SOL and registered-token balances
122
+ slrd token <token_ca> [name] [--metadata-json <json>]
123
+ slrd token set <token|ca> [--pool <address>] [--quote-mint <mint>] [--quote-program <program>] [--metadata-json <json>]
124
+ slrd token refresh <token|ca>
125
+ slrd tokens
126
+
127
+ Metadata and launching
128
+ solwal launch pump --creator <wallet> (--uri <metadata_uri> | --metadata <json> | --image <path> --description <text>) [--alias <name>] [--live] [--skip-simulation]
129
+ [--deployment-sender helius-rpc|helius-fast] [--helius-tip-sol 0.01]
130
+ slrd metadata upload --image <local_path> --name <name> --symbol <symbol> --description <text> [--provider pump-frontend|pinata]
131
+ [--twitter <url>] [--telegram <url>] [--website <url>] [--video <url>] [--hide-name]
132
+ slrd deploy pump --wallet <wallet> --name <name> --symbol <symbol> (--uri <metadata_uri> | --image <local_path> --description <text>) [--alias <name>] [--live]
133
+ [--twitter <url>] [--telegram <url>] [--website <url>] [--video <url>] [--hide-name]
134
+ slrd run launch-pump-token --creator <wallet> --metadata <token.json> [--deploy-only] [--live]
135
+
136
+ Prices
137
+ slrd quote buy <token|ca> --sol <amount> Inspect buy quote without submitting
138
+ slrd price <token|ca> Sample current venue price
139
+ slrd price average <token|ca> --period 15m Average stored samples in a period
140
+ slrd price watch <token|ca...> [--interval 1s] [--period 1m]
141
+
142
+ Trading
143
+ slrd buy <token|ca> (--wallet <wallet> | --wallets <w1,w2> | --group <name>) --sol <amount> [--slippage-bps 1500] [--sender rpc|helius|jito] [--simulate-only]
144
+ slrd buy <future-mint> (--wallet <wallet> | --group <name>) (--sol <amount> | --lamports <amount> | --min-bps <n> --max-bps <n>) --spam [--live]
145
+ slrd spam-buy [pump] <future-mint> (--wallet <wallet> | --group <name>) (--sol <amount> | --lamports <amount> | --min-bps <n> --max-bps <n>) [--sender <id>] [--live]
146
+ bun run scripts/spam-pump-buyers.ts --mint <future-mint> (--wallet <wallet> | --group <name>) --amount-mode range-bps --min-bps <n> --max-bps <n> [--live]
147
+ slrd sell <token|ca> (--wallet <wallet> | --wallets <w1,w2> | --group <name>) [--bps 10000] [--slippage-bps 1500] [--sender rpc|helius|jito] [--simulate-only]
148
+ slrd unwrap-wsol (--wallet <wallet> | --wallets <w1,w2> | --group <name>) [--sender rpc|helius|jito] [--ignore-missing] [--simulate-only]
149
+ slrd claim <token|ca> --wallet <wallet> [--sender rpc|helius|jito]
150
+
151
+ Scripts (strategies stay outside the kernel)
152
+ slrd scripts List scripts registered in slrd.config.ts
153
+ slrd run <name-or-path> [script flags...] Execute a script that imports slrd
154
+ slrd run snipe --name <exact_name> --group <group> --sol 0.05 --sender jito
155
+ slrd run claim-trade-send --claim <token> --buy <token> --wallet <wallet> --recipient <address>
156
+
157
+ Groups and agents
158
+ slrd group create <name> [description]
159
+ slrd group add <group> <wallet> [weight_bps]
160
+ slrd group add-many <group> <wallet1,wallet2,...>
161
+ slrd group show <group>
162
+ slrd group list
163
+ slrd buy <token|ca> --group <name> --sol <amount> --sender jito Submit group buys as Jito bundles, five tx per bundle
164
+ slrd sell <token|ca> --group <name> --sender jito Submit group sells as Jito bundles, five tx per bundle
165
+ slrd agent create <name> --wallet <wallet> [--config-json <json>]
166
+ slrd agent list
167
+
168
+ Watching
169
+ slrd watch token <token|ca> [label]
170
+ slrd watch wallet <wallet> [label]
171
+ slrd watch program <address> [label]
172
+ slrd watch list
173
+
174
+ Transactions and ALTs
175
+ slrd history
176
+ slrd jito tip-accounts [--endpoint <block-engine-url>]
177
+ slrd alt add <address> [label]
178
+ slrd alt list
179
+ slrd alt create --wallet <wallet>
180
+ slrd alt extend <address> --wallet <wallet> <account...>
181
+
182
+ Environment
183
+ SLRD_DB_PATH local shared database; default ./slrd.db
184
+ SLRD_MASTER_KEY required to import/decrypt stored wallets
185
+ RPC_ENDPOINT required only for chain operations
186
+ HELIUS_SENDER_URL regional/global Helius Sender endpoint used by launch scripts
187
+ HELIUS_RPC_URL RPC endpoint used for ordinary Helius-RPC buyer lane
188
+ HELIUS_TIP_ACCOUNT, HELIUS_TIP_LAMPORTS, HELIUS_PRIORITY_MICRO_LAMPORTS launch transport policy
189
+ SLRD_LAUNCH_* launch resend/timeout policy; shared environment, not per-token metadata
190
+ JITO_BLOCK_ENGINE_URL only when explicitly selecting the separate Jito sender
191
+ SLRD_METADATA_UPLOADER metadata provider; default pump-frontend; optional pinata
192
+ PUMP_IPFS_ENDPOINT browser-facing Pump metadata upload endpoint; default https://pump.fun/api/ipfs
193
+ PINATA_JWT required only with --provider pinata or explicit pinata fallback
194
+ IPFS_PUBLIC_GATEWAY optional Pinata returned metadata gateway; default https://ipfs.io/ipfs
195
+ `;
196
+ }
197
+
198
+ async function main() {
199
+ const [command, ...rest] = process.argv.slice(2);
200
+ const { values, flags } = args(rest);
201
+ if (!command || command === "help" || command === "--help") {
202
+ emit(help());
203
+ return;
204
+ }
205
+ if (command === "version" || command === "--version" || command === "-v") {
206
+ const { readFileSync } = await import("node:fs");
207
+ const { join } = await import("node:path");
208
+ const pkg = JSON.parse(
209
+ readFileSync(join(import.meta.dir, "..", "package.json"), "utf8"),
210
+ ) as { name: string; version: string };
211
+ emit(`${OWL} ${pkg.name} ${pkg.version} (bun ${Bun.version})\n`);
212
+ return;
213
+ }
214
+ if (command === "vanity") {
215
+ const { generateMintKeypairWithSuffix, saveMintKeypairFile, cleanVanitySuffix } =
216
+ await import("@solard/core/launches/pump/vanity-mint.ts");
217
+ const suffix = cleanVanitySuffix(need(flags, "suffix"));
218
+ const out = need(flags, "out");
219
+ const count = int(flags, "count", 1)!;
220
+ const results = [];
221
+ for (let i = 0; i < count; i++) {
222
+ const found = await generateMintKeypairWithSuffix({
223
+ suffix,
224
+ workers: int(flags, "workers"),
225
+ maxAttempts: int(flags, "max-attempts", 25_000_000)!,
226
+ timeoutMs: int(flags, "timeout-ms", 0)!,
227
+ reportEvery: int(flags, "report-every", 1_000_000)!,
228
+ onProgress: (p) =>
229
+ emit(`${OWL} grinding ${p.suffix}: ${p.attempts} attempts, ${p.ratePerSecond}/s\n`),
230
+ });
231
+ const path = count === 1 ? out : out.replace(/(\.json)?$/, `-${i + 1}$1`);
232
+ const saved = saveMintKeypairFile(path, found.mint, { force: flags.has("force") });
233
+ results.push({
234
+ address: found.mint.publicKey.toBase58(),
235
+ keypairPath: saved,
236
+ attempts: found.attempts,
237
+ elapsedMs: found.elapsedMs,
238
+ launchArguments: ["--mint-keypair", saved, "--mint-suffix", suffix],
239
+ });
240
+ }
241
+ emit(json(count === 1 ? results[0] : results) + "\n");
242
+ return;
243
+ }
244
+ if (command === "spam-buy" || command === "buy-spam") {
245
+ const { runPumpSpamBuyFromArgs } =
246
+ await import("@solard/core/launches/pump/spam-buy-cli.ts");
247
+ await runPumpSpamBuyFromArgs(rest);
248
+ return;
249
+ }
250
+ if (command === "buy" && flags.has("spam")) {
251
+ const { runPumpSpamBuyFromArgs } =
252
+ await import("@solard/core/launches/pump/spam-buy-cli.ts");
253
+ await runPumpSpamBuyFromArgs(rest);
254
+ return;
255
+ }
256
+ // const { maybeRunWorkerCliCommand } =
257
+ // await import("./worker-commands.js");
258
+ // if (await maybeRunWorkerCliCommand({ values: [command, ...values], flags }))
259
+ // return;
260
+ if (command === "scripts") {
261
+ emit(json(await listScripts()) + "\n");
262
+ return;
263
+ }
264
+ if (command === "run") {
265
+ const [script, ...scriptArgs] = rest;
266
+ if (!script)
267
+ throw new Error("Usage: slrd run <name-or-path> [script flags...]");
268
+ process.exitCode = await runScript(script, scriptArgs);
269
+ return;
270
+ }
271
+ if (
272
+ command === "launch" &&
273
+ (values[0] === "pump" || values[0] === "pump-token")
274
+ ) {
275
+ const { runPumpTokenLaunchFromArgs } =
276
+ await import("@solard/core/launches/pump/token-launch-cli.ts");
277
+ await runPumpTokenLaunchFromArgs(rest.slice(1), {
278
+ defaultSubmitMode: "after-deploy-processed",
279
+ defaultDeploymentPriorityMicroLamports: 500_000,
280
+ defaultBuyerPriorityMicroLamports: 1_500_000,
281
+ defaultSlippageBps: 1_500,
282
+ persistOnLive: true,
283
+ report: (label, value) => emit(`${label}: ${json(value)}\n`),
284
+ });
285
+ return;
286
+ }
287
+ if (command === "jito" && values[0] === "tip-accounts") {
288
+ const endpoint = (
289
+ flags.get("endpoint") ??
290
+ process.env.JITO_BLOCK_ENGINE_URL ??
291
+ "https://mainnet.block-engine.jito.wtf"
292
+ ).replace(/\/$/, "");
293
+ const response = await fetch(`${endpoint}/api/v1/bundles`, {
294
+ method: "POST",
295
+ headers: { "content-type": "application/json" },
296
+ body: JSON.stringify({
297
+ jsonrpc: "2.0",
298
+ id: Date.now(),
299
+ method: "getTipAccounts",
300
+ params: [],
301
+ }),
302
+ });
303
+ const payload = (await response.json()) as {
304
+ result?: string[];
305
+ error?: unknown;
306
+ };
307
+ if (!response.ok || payload.error || !Array.isArray(payload.result))
308
+ throw new Error(
309
+ `Jito getTipAccounts failed: ${JSON.stringify(payload.error ?? response.status)}`,
310
+ );
311
+ emit(json({ endpoint, tipAccounts: payload.result }) + "\n");
312
+ return;
313
+ }
314
+ if (command === "metadata" && values[0] === "upload") {
315
+ const { uploadPumpMetadata } = await import("@solard/core/metadata/pump-metadata.ts");
316
+ const uploaded = await uploadPumpMetadata(
317
+ {
318
+ imagePath: need(flags, "image"),
319
+ name: need(flags, "name"),
320
+ symbol: need(flags, "symbol"),
321
+ description: need(flags, "description"),
322
+ twitter: flags.get("twitter"),
323
+ telegram: flags.get("telegram"),
324
+ website: flags.get("website"),
325
+ video: flags.get("video"),
326
+ showName: !flags.has("hide-name"),
327
+ },
328
+ {
329
+ provider: (flags.get("provider") ??
330
+ process.env.SLRD_METADATA_UPLOADER ??
331
+ "pump-frontend") as "pump-frontend" | "pinata",
332
+ endpoint: flags.get("endpoint"),
333
+ fallback: flags.get("fallback") === "pinata" ? "pinata" : null,
334
+ },
335
+ );
336
+ emit(json(uploaded) + "\n");
337
+ return;
338
+ }
339
+ const [{ sol, formatRaw }, { shortKey }, { createTraderSolard }] =
340
+ await Promise.all([
341
+ import("@solard/core/core/amounts.ts"),
342
+ import("@solard/core/core/log.ts"),
343
+ import("@solard/core/presets/trader.ts"),
344
+ ]);
345
+ const slrd = createTraderSolard();
346
+ try {
347
+ if (command === "export") {
348
+ const ref = values[0];
349
+ if (!ref)
350
+ throw new Error(
351
+ "Usage: slrd export <wallet|address> [--json] [--out <path>]",
352
+ );
353
+ const bs58 = (await import("bs58")).default;
354
+ const { signer, row } = slrd.wallets.signer(ref);
355
+ const secret = flags.has("json")
356
+ ? JSON.stringify(Array.from(signer.secretKey))
357
+ : bs58.encode(signer.secretKey);
358
+
359
+ const out = flags.get("out");
360
+ if (out && out !== "true") {
361
+ const { writeFileSync, mkdirSync } = await import("node:fs");
362
+ const { dirname, resolve } = await import("node:path");
363
+ const path = resolve(out);
364
+ mkdirSync(dirname(path), { recursive: true });
365
+ writeFileSync(path, `${secret}\n`, { mode: 0o600, flag: "wx" });
366
+ emit(
367
+ `${OWL} exported @${row?.name ?? "?"} ${signer.publicKey.toBase58()} -> ${path}\n`,
368
+ );
369
+ return;
370
+ }
371
+
372
+ // Secret to stdout only; metadata to stderr so piping stays clean.
373
+ process.stderr.write(
374
+ `${OWL} @${row?.name ?? "?"} ${signer.publicKey.toBase58()} (${flags.has("json") ? "keypair-json" : "base58"})\n`,
375
+ );
376
+ process.stdout.write(`${secret}\n`);
377
+ return;
378
+ }
379
+ if (command === "import") {
380
+ const input = flags.has("stdin")
381
+ ? (await new Response(Bun.stdin.stream()).text()).trim()
382
+ : values[0];
383
+ if (!input)
384
+ throw new Error(
385
+ "Usage: slrd import <private_key> [name] or cat key.json | slrd import --stdin [name]",
386
+ );
387
+ const name = flags.has("stdin") ? values[0] : values[1];
388
+ const wallet = slrd.importWallet(input, name);
389
+ emit(`${OWL} imported @${wallet.name} ${wallet.address}\n`);
390
+ return;
391
+ }
392
+ if (command === "wallets") {
393
+ const wallets = slrd.wallets.list();
394
+ if (flags.has("addresses-only")) {
395
+ for (const wallet of wallets)
396
+ emit(`@${wallet.name}\t${wallet.address}\n`);
397
+ return;
398
+ }
399
+ const selectedTokens = flags.get("token")
400
+ ? [slrd.resolveToken(flags.get("token")!)]
401
+ : slrd.tokens.list();
402
+ for (const wallet of wallets) {
403
+ const balances = await slrd.walletBalances(wallet, selectedTokens);
404
+ const holdings = balances.tokenBalances
405
+ .filter(
406
+ (balance) =>
407
+ balance.amountRaw > 0n ||
408
+ flags.has("show-zero") ||
409
+ Boolean(flags.get("token")),
410
+ )
411
+ .map(
412
+ (balance) =>
413
+ `${balance.token.symbol ? "$" + balance.token.symbol : (balance.token.name ?? shortKey(balance.token.mint))}=${formatRaw(balance.amountRaw, balance.decimals)}`,
414
+ )
415
+ .join(" ");
416
+ emit(
417
+ `@${wallet.name}\t${wallet.address}\tSOL=${formatRaw(balances.solLamports, 9)}${holdings ? ` ${holdings}` : ""}\n`,
418
+ );
419
+ }
420
+ return;
421
+ }
422
+ if (command === "deploy") {
423
+ const launchpad = values[0] ?? "pump";
424
+ const wallet = need(flags, "wallet");
425
+ const name = need(flags, "name");
426
+ const symbol = need(flags, "symbol");
427
+ let uri = flags.get("uri");
428
+ let uploadedMetadata: unknown = null;
429
+ if (!uri || uri === "true") {
430
+ if (!flags.get("image"))
431
+ throw new Error(
432
+ "Provide --uri <metadata_uri> or --image <local_path> --description <text>",
433
+ );
434
+ const { uploadPumpMetadata } =
435
+ await import("@solard/core/metadata/pump-metadata.ts");
436
+ uploadedMetadata = await uploadPumpMetadata(
437
+ {
438
+ imagePath: need(flags, "image"),
439
+ name,
440
+ symbol,
441
+ description: need(flags, "description"),
442
+ twitter: flags.get("twitter"),
443
+ telegram: flags.get("telegram"),
444
+ website: flags.get("website"),
445
+ video: flags.get("video"),
446
+ showName: !flags.has("hide-name"),
447
+ },
448
+ {
449
+ provider: (flags.get("provider") ??
450
+ process.env.SLRD_METADATA_UPLOADER ??
451
+ "pump-frontend") as "pump-frontend" | "pinata",
452
+ endpoint: flags.get("endpoint"),
453
+ fallback: flags.get("fallback") === "pinata" ? "pinata" : null,
454
+ },
455
+ );
456
+ uri = (uploadedMetadata as { metadataUri: string }).metadataUri;
457
+ }
458
+ const creator = flags.get("creator")
459
+ ? slrd.resolveWallet(flags.get("creator")!).address
460
+ : slrd.signer(wallet).publicKey;
461
+ const deployment = await slrd.prepareTokenDeployment(launchpad, wallet, {
462
+ name,
463
+ symbol,
464
+ uri,
465
+ creator,
466
+ mayhemMode: flags.has("mayhem"),
467
+ cashback: flags.has("cashback"),
468
+ });
469
+ const plan = await slrd
470
+ .transaction(wallet)
471
+ .addMany(deployment.instructions, {
472
+ kind: "deploy-token",
473
+ mint: deployment.mint.publicKey,
474
+ meta: { launchpad, name, symbol },
475
+ })
476
+ .withSigner(deployment.mint)
477
+ .build();
478
+ const header = {
479
+ launchpad,
480
+ mint: deployment.mint.publicKey.toBase58(),
481
+ name,
482
+ symbol,
483
+ uri,
484
+ uploadedMetadata,
485
+ live: flags.has("live"),
486
+ };
487
+ if (!flags.has("live")) {
488
+ const simulation = await slrd.simulatePlan(plan);
489
+ emit(json({ ...header, simulation }) + "\n");
490
+ return;
491
+ }
492
+ const receipt = await slrd.sendPlan(
493
+ plan,
494
+ flags.get("sender") ?? "rpc",
495
+ `deploy:${launchpad}`,
496
+ {
497
+ skipSimulation: flags.has("skip-simulation"),
498
+ skipPreflight: flags.has("skip-simulation"),
499
+ },
500
+ );
501
+ const token = slrd.persistPreparedDeployment(
502
+ deployment,
503
+ flags.get("alias"),
504
+ );
505
+ emit(json({ ...header, receipt, token }) + "\n");
506
+ return;
507
+ }
508
+ if (command === "token" && values[0] !== "set" && values[0] !== "refresh") {
509
+ const [mint, name] = values;
510
+ if (!mint) throw new Error("Usage: slrd token <token_ca> [name]");
511
+ const metadata = flags.get("metadata-json");
512
+ const token = await slrd.addToken(
513
+ mint,
514
+ name,
515
+ metadata ? { metadataJson: metadata } : {},
516
+ );
517
+ emit(
518
+ `${OWL} token ${token.name ?? token.symbol ?? "-"} ${token.mint} venue=${token.venueHint}\n`,
519
+ );
520
+ return;
521
+ }
522
+ if (command === "token" && values[0] === "refresh") {
523
+ const ref = values[1];
524
+ if (!ref) throw new Error("Usage: slrd token refresh <token|ca>");
525
+ emit(json(await slrd.refreshToken(ref)) + "\n");
526
+ return;
527
+ }
528
+ if (command === "token" && values[0] === "set") {
529
+ const ref = values[1];
530
+ if (!ref) throw new Error("Usage: slrd token set <token|ca> [flags]");
531
+ const patch: Partial<TokenRow> = {};
532
+ if (flags.get("pool")) patch.pool = flags.get("pool");
533
+ if (flags.get("quote-mint")) patch.quoteMint = flags.get("quote-mint");
534
+ if (flags.get("quote-program"))
535
+ patch.quoteTokenProgram = flags.get("quote-program");
536
+ if (flags.get("metadata-json"))
537
+ patch.metadataJson = flags.get("metadata-json");
538
+ const token = slrd.configureToken(ref, patch);
539
+ emit(`${OWL} updated ${token.name ?? shortKey(token.mint)}\n`);
540
+ return;
541
+ }
542
+ if (command === "tokens") {
543
+ for (const token of slrd.tokens.list())
544
+ emit(
545
+ `${token.name ?? "-"}\t${token.symbol ? "$" + token.symbol : "-"}\t${token.mint}\t${token.venueHint}\n`,
546
+ );
547
+ return;
548
+ }
549
+ if (command === "quote" && values[0] === "buy") {
550
+ const tokenRef = values[1];
551
+ if (!tokenRef)
552
+ throw new Error(
553
+ "Usage: slrd quote buy <token|ca> --sol <amount> [--slippage-bps 1500]",
554
+ );
555
+ const spend = need(flags, "sol");
556
+ const result = await slrd.quoteBuy(
557
+ tokenRef,
558
+ sol(spend),
559
+ int(flags, "slippage-bps", 1500),
560
+ );
561
+ emit(
562
+ json({
563
+ token: {
564
+ mint: result.token.mint,
565
+ name: result.token.name,
566
+ symbol: result.token.symbol,
567
+ },
568
+ venue: result.venue,
569
+ quoteAsset:
570
+ result.quoteAsset.kind === "native-sol"
571
+ ? "SOL"
572
+ : result.quoteAsset.mint.toBase58(),
573
+ spendRaw: result.quote.inputRaw,
574
+ expectedOutputRaw: result.quote.expectedOutputRaw,
575
+ minimumOutputRaw: result.quote.minimumOutputRaw,
576
+ meta: result.quote.meta ?? {},
577
+ }) + "\n",
578
+ );
579
+ return;
580
+ }
581
+ if (command === "price" && values[0] === "average") {
582
+ const tokenRef = values[1];
583
+ if (!tokenRef)
584
+ throw new Error("Usage: slrd price average <token|ca> --period 15m");
585
+ const token = slrd.resolveToken(tokenRef);
586
+ const window = slrd.averagePrice(
587
+ token,
588
+ duration(flags.get("period"), 60_000),
589
+ );
590
+ emit(
591
+ `${token.symbol ? "$" + token.symbol : (token.name ?? shortKey(token.mint))} samples=${window.samples} avg=${formatPrice(window.averagePriceQuotePerToken)} min=${formatPrice(window.minimumPriceQuotePerToken)} max=${formatPrice(window.maximumPriceQuotePerToken)} last=${formatPrice(window.lastPriceQuotePerToken)}\n`,
592
+ );
593
+ return;
594
+ }
595
+ if (command === "price" && values[0] === "watch") {
596
+ const tokenRefs = values.slice(1);
597
+ if (tokenRefs.length === 0)
598
+ throw new Error(
599
+ "Usage: slrd price watch <token|ca...> [--interval 1s] [--period 1m]",
600
+ );
601
+ const controller = new AbortController();
602
+ process.once("SIGINT", () => controller.abort());
603
+ for await (const event of slrd.watchPrices(tokenRefs, {
604
+ intervalMs: duration(flags.get("interval"), 1_000),
605
+ averagePeriodMs: duration(flags.get("period"), 60_000),
606
+ signal: controller.signal,
607
+ })) {
608
+ const label = event.token.symbol
609
+ ? "$" + event.token.symbol
610
+ : (event.token.name ?? shortKey(event.token.mint));
611
+ emit(
612
+ `${new Date(event.sample.capturedAtMs).toISOString()} ${label} venue=${event.sample.venue} price=${formatPrice(event.sample.priceQuotePerToken)} avg=${formatPrice(event.average.averagePriceQuotePerToken)} samples=${event.average.samples}\n`,
613
+ );
614
+ }
615
+ return;
616
+ }
617
+ if (command === "price") {
618
+ const tokenRef = values[0];
619
+ if (!tokenRef) throw new Error("Usage: slrd price <token|ca>");
620
+ const token = slrd.resolveToken(tokenRef);
621
+ const sample = await slrd.samplePrice(token);
622
+ const label = token.symbol
623
+ ? "$" + token.symbol
624
+ : (token.name ?? shortKey(token.mint));
625
+ emit(
626
+ `${label} venue=${sample.venue} price=${formatPrice(sample.priceQuotePerToken)} quote=${sample.quoteAsset.kind === "native-sol" ? "SOL" : shortKey(sample.quoteAsset.mint.toBase58())} captured=${new Date(sample.capturedAtMs).toISOString()}\n`,
627
+ );
628
+ return;
629
+ }
630
+ if (command === "buy") {
631
+ const token = values[0];
632
+ if (!token)
633
+ throw new Error(
634
+ "Usage: slrd buy <token|ca> (--wallet <wallet> | --wallets <w1,w2> | --group <group>) --sol <amount>",
635
+ );
636
+ const amount = flags.get("sol");
637
+ if (!amount) throw new Error("Buy requires explicit --sol <amount>");
638
+ const targets = targetWallets(
639
+ slrd,
640
+ flags,
641
+ "Usage: slrd buy <token|ca> (--wallet <wallet> | --wallets <w1,w2> | --group <group>) --sol <amount>.",
642
+ );
643
+ const options = {
644
+ slippageBps: int(flags, "slippage-bps", 1500),
645
+ via: flags.get("sender") ?? "rpc",
646
+ skipSimulation: flags.has("skip-simulation"),
647
+ skipPreflight:
648
+ flags.has("skip-preflight") || flags.has("skip-simulation"),
649
+ };
650
+ if (flags.has("simulate-only")) {
651
+ const plans =
652
+ targets.refs.length === 1
653
+ ? [
654
+ await slrd
655
+ .tx(targets.refs[0]!)
656
+ .buy(token, sol(amount), options)
657
+ .build(),
658
+ ]
659
+ : await slrd
660
+ .composeMany(targets.refs)
661
+ .buy(token, sol(amount), options)
662
+ .build();
663
+ const results = await Promise.all(
664
+ plans.map((plan) => slrd.simulatePlan(plan)),
665
+ );
666
+ emit(json({ mode: "simulation", target: targets, results }) + "\n");
667
+ return;
668
+ }
669
+ const receipts =
670
+ targets.refs.length === 1
671
+ ? await slrd.buy(token, targets.refs[0]!, sol(amount), options)
672
+ : await slrd.buyMany(token, targets.refs, sol(amount), options);
673
+ emit(json(receipts) + "\n");
674
+ return;
675
+ }
676
+ if (command === "sell") {
677
+ const token = values[0];
678
+ if (!token)
679
+ throw new Error(
680
+ "Usage: slrd sell <token|ca> (--wallet <wallet> | --wallets <w1,w2> | --group <group>)",
681
+ );
682
+ const targets = targetWallets(
683
+ slrd,
684
+ flags,
685
+ "Usage: slrd sell <token|ca> (--wallet <wallet> | --wallets <w1,w2> | --group <group>).",
686
+ );
687
+ const sellOptions = {
688
+ bps: int(flags, "bps", 10000),
689
+ slippageBps: int(flags, "slippage-bps", 1500),
690
+ via: flags.get("sender") ?? "rpc",
691
+ skipSimulation: flags.has("skip-simulation"),
692
+ skipPreflight:
693
+ flags.has("skip-preflight") || flags.has("skip-simulation"),
694
+ };
695
+ if (flags.has("simulate-only")) {
696
+ const plans =
697
+ targets.refs.length === 1
698
+ ? [await slrd.tx(targets.refs[0]!).sell(token, sellOptions).build()]
699
+ : await slrd
700
+ .composeMany(targets.refs)
701
+ .sell(token, sellOptions)
702
+ .build();
703
+ const results = await Promise.all(
704
+ plans.map((plan) => slrd.simulatePlan(plan)),
705
+ );
706
+ emit(json({ mode: "simulation", target: targets, results }) + "\n");
707
+ return;
708
+ }
709
+ const receipts =
710
+ targets.refs.length === 1
711
+ ? await slrd.sell(token, targets.refs[0]!, sellOptions)
712
+ : await slrd.sellMany(token, targets.refs, sellOptions);
713
+ emit(json(receipts) + "\n");
714
+ return;
715
+ }
716
+ if (
717
+ command === "unwrap-wsol" ||
718
+ (command === "unwrap" && values[0] === "wsol")
719
+ ) {
720
+ const targets = targetWallets(
721
+ slrd,
722
+ flags,
723
+ "Usage: slrd unwrap-wsol (--wallet <wallet> | --wallets <w1,w2> | --group <group>).",
724
+ );
725
+ const destination = flags.get("destination");
726
+ if (destination && targets.refs.length !== 1)
727
+ throw new Error(
728
+ "--destination is only supported with a single --wallet unwrap.",
729
+ );
730
+ const unwrapOptions = {
731
+ via: flags.get("sender") ?? "rpc",
732
+ destination,
733
+ skipMissing: flags.has("ignore-missing") || flags.has("skip-missing"),
734
+ skipSimulation: flags.has("skip-simulation"),
735
+ skipPreflight:
736
+ flags.has("skip-preflight") || flags.has("skip-simulation"),
737
+ };
738
+ if (flags.has("simulate-only")) {
739
+ const plans =
740
+ targets.refs.length === 1
741
+ ? [
742
+ await slrd
743
+ .tx(targets.refs[0]!)
744
+ .unwrapWsol({
745
+ destination,
746
+ skipMissing: unwrapOptions.skipMissing,
747
+ })
748
+ .build(),
749
+ ]
750
+ : await slrd
751
+ .composeMany(targets.refs)
752
+ .unwrapWsol({ skipMissing: unwrapOptions.skipMissing })
753
+ .build();
754
+ const results = await Promise.all(
755
+ plans.map((plan) => slrd.simulatePlan(plan)),
756
+ );
757
+ emit(json({ mode: "simulation", target: targets, results }) + "\n");
758
+ return;
759
+ }
760
+ const receipts =
761
+ targets.refs.length === 1
762
+ ? await slrd.unwrapWsol(targets.refs[0]!, unwrapOptions)
763
+ : await slrd.unwrapWsolMany(targets.refs, unwrapOptions);
764
+ emit(json(receipts) + "\n");
765
+ return;
766
+ }
767
+ if (command === "claim") {
768
+ const token = values[0];
769
+ if (!token)
770
+ throw new Error("Usage: slrd claim <token|ca> --wallet <wallet>");
771
+ emit(
772
+ json(
773
+ await slrd.claim(token, need(flags, "wallet"), {
774
+ via: flags.get("sender") ?? "rpc",
775
+ }),
776
+ ) + "\n",
777
+ );
778
+ return;
779
+ }
780
+ if (command === "history") {
781
+ emit(json(slrd.executions.history()) + "\n");
782
+ return;
783
+ }
784
+ if (command === "group" && values[0] === "create") {
785
+ const name = values[1];
786
+ if (!name)
787
+ throw new Error("Usage: slrd group create <name> [description]");
788
+ emit(
789
+ json(slrd.groups.create(name, values.slice(2).join(" ") || undefined)) +
790
+ "\n",
791
+ );
792
+ return;
793
+ }
794
+ if (command === "group" && values[0] === "add") {
795
+ const [_, group, wallet, weight] = values;
796
+ if (!group || !wallet)
797
+ throw new Error("Usage: slrd group add <group> <wallet> [weight_bps]");
798
+ const resolved = slrd.resolveWallet(wallet);
799
+ emit(
800
+ json(
801
+ slrd.groups.addWallet(
802
+ group,
803
+ resolved.address.toBase58(),
804
+ weight ? Number(weight) : 10000,
805
+ ),
806
+ ) + "\n",
807
+ );
808
+ return;
809
+ }
810
+ if (command === "group" && values[0] === "add-many") {
811
+ const group = values[1];
812
+ const raw = values[2];
813
+ if (!group || !raw)
814
+ throw new Error(
815
+ "Usage: slrd group add-many <group> <wallet1,wallet2,...>",
816
+ );
817
+ slrd.groups.create(group);
818
+ const members = raw
819
+ .split(",")
820
+ .map((value) => value.trim())
821
+ .filter(Boolean)
822
+ .map((ref) => {
823
+ const resolved = slrd.resolveWallet(ref);
824
+ return slrd.groups.addWallet(
825
+ group,
826
+ resolved.address.toBase58(),
827
+ 10000,
828
+ );
829
+ });
830
+ emit(json({ group, added: members.length, members }) + "\n");
831
+ return;
832
+ }
833
+ if (command === "group" && values[0] === "show") {
834
+ const group = values[1];
835
+ if (!group) throw new Error("Usage: slrd group show <group>");
836
+ const members = slrd.groups.wallets(group).map((row) => {
837
+ const wallet = slrd.resolveWallet(row.walletAddress);
838
+ return {
839
+ name: wallet.row?.name ?? null,
840
+ address: wallet.address.toBase58(),
841
+ weightBps: row.weightBps,
842
+ };
843
+ });
844
+ emit(json({ group, members }) + "\n");
845
+ return;
846
+ }
847
+ if (command === "group" && values[0] === "list") {
848
+ emit(json(slrd.groups.list()) + "\n");
849
+ return;
850
+ }
851
+ if (command === "agent" && values[0] === "create") {
852
+ const name = values[1];
853
+ if (!name)
854
+ throw new Error(
855
+ "Usage: slrd agent create <name> --wallet <wallet> [--config-json <json>]",
856
+ );
857
+ const supplied = flags.get("config-json")
858
+ ? (JSON.parse(flags.get("config-json")!) as Record<string, unknown>)
859
+ : {};
860
+ const wallet = need(flags, "wallet");
861
+ const agent = slrd.configureAgent(name, { ...supplied, wallet });
862
+ emit(json(agent.row) + "\n");
863
+ return;
864
+ }
865
+ if (command === "agent" && values[0] === "list") {
866
+ emit(json(slrd.listAgents()) + "\n");
867
+ return;
868
+ }
869
+ if (command === "watch" && values[0] === "token") {
870
+ const ref = values[1];
871
+ if (!ref) throw new Error("Usage: slrd watch token <token|ca> [label]");
872
+ emit(json(slrd.watchToken(ref, values[2])) + "\n");
873
+ return;
874
+ }
875
+ if (command === "watch" && values[0] === "wallet") {
876
+ const ref = values[1];
877
+ if (!ref) throw new Error("Usage: slrd watch wallet <wallet> [label]");
878
+ emit(json(slrd.watchWallet(ref, values[2])) + "\n");
879
+ return;
880
+ }
881
+ if (command === "watch" && values[0] === "program") {
882
+ const address = values[1];
883
+ if (!address)
884
+ throw new Error("Usage: slrd watch program <address> [label]");
885
+ emit(json(slrd.watchProgram(address, values[2])) + "\n");
886
+ return;
887
+ }
888
+ if (command === "watch" && values[0] === "list") {
889
+ emit(json(slrd.db.watches.select().where({ isActive: 1 }).all()) + "\n");
890
+ return;
891
+ }
892
+ if (command === "alt" && values[0] === "add") {
893
+ if (!values[1]) throw new Error("Usage: slrd alt add <address> [label]");
894
+ emit(json(slrd.alts.register(values[1], values[2])) + "\n");
895
+ return;
896
+ }
897
+ if (command === "alt" && values[0] === "list") {
898
+ emit(json(slrd.alts.list()) + "\n");
899
+ return;
900
+ }
901
+ if (command === "alt" && values[0] === "create") {
902
+ emit(json(await slrd.createAlt(need(flags, "wallet"))) + "\n");
903
+ return;
904
+ }
905
+ if (command === "alt" && values[0] === "extend") {
906
+ if (!values[1] || values.length < 3)
907
+ throw new Error(
908
+ "Usage: slrd alt extend <address> --wallet <wallet> <account...>",
909
+ );
910
+ emit(
911
+ json(
912
+ await slrd.extendAlt(
913
+ values[1],
914
+ need(flags, "wallet"),
915
+ values.slice(2),
916
+ ),
917
+ ) + "\n",
918
+ );
919
+ return;
920
+ }
921
+ throw new Error(`Unknown command: ${command}\n\n${help()}`);
922
+ } finally {
923
+ slrd.close();
924
+ }
925
+ }
926
+ main().catch((error) => {
927
+ emit(`${OWL} error: ${formatError(error)}\n`);
928
+ process.exitCode = 1;
929
+ });
930
+
931
+ export { formatCliError } from "@solard/core";
@@ -0,0 +1,14 @@
1
+ import { listTelegramSignals, terminalStoreStats } from "@solard/core/db.ts";
2
+ import { formatProcessRow } from "@solard/core/solard/terminal/presenter.js";
3
+
4
+ export const workerCommands: Record<string, (args: string[]) => void> = {
5
+ stats: () => {
6
+ console.log("Terminal Stats:", terminalStoreStats());
7
+ },
8
+ signals: () => {
9
+ console.log("Telegram Signals:", listTelegramSignals());
10
+ },
11
+ help: () => {
12
+ console.log("Available commands: stats, signals, help");
13
+ }
14
+ };