@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 +2 -0
- package/package.json +20 -0
- package/src/index.ts +931 -0
- package/src/worker-commands.ts +14 -0
package/bin/solard.ts
ADDED
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
|
+
};
|