@solard/cli 0.2.17 → 0.2.19

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.
Files changed (2) hide show
  1. package/package.json +2 -2
  2. package/src/index.ts +103 -72
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solard/cli",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Command-line interface for the Solard SDK.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -18,7 +18,7 @@
18
18
  "bun": ">=1.1.0"
19
19
  },
20
20
  "dependencies": {
21
- "@solard/core": "0.2.17",
21
+ "@solard/core": "0.2.19",
22
22
  "bs58": "^6.0.0",
23
23
  "@solana/web3.js": "^1.98.4",
24
24
  "measure-fn": "5.2.0"
package/src/index.ts CHANGED
@@ -122,7 +122,7 @@ function friendlyCliError(error: unknown): string | null {
122
122
  if (code === "MISSING_CONFIG" && error.message.includes("SLRD_MASTER_KEY")) {
123
123
  return `${error.message}\n\nEnter the wallet password interactively, or set SLRD_MASTER_KEY for automation/CI.`;
124
124
  }
125
- return null;
125
+ return error.message.trim() || error.name;
126
126
  }
127
127
  function duration(value: string | undefined, fallbackMs: number): number {
128
128
  if (!value) return fallbackMs;
@@ -323,6 +323,14 @@ function commandNeedsSigningVault(
323
323
  return false;
324
324
  }
325
325
 
326
+ function sweepUsage(): string {
327
+ return (
328
+ "Usage: slrd sweep --to <contact|wallet|address> --below <SOL> " +
329
+ "[--wallets <a,b,...>] [--keep <wallet=SOL,...>] [--simulate | --live] [--json]\n" +
330
+ "Compatibility: slrd sweep sol --to <destination> --max-balance-sol <SOL>"
331
+ );
332
+ }
333
+
326
334
  async function storedWalletCount(): Promise<number> {
327
335
  const { createTraderSolard } = await import("@solard/core");
328
336
  const probe = createTraderSolard();
@@ -401,8 +409,9 @@ Backtesting
401
409
  Transfers and consolidation
402
410
  slrd transfer <contact|wallet|address> --wallet <source-wallet> --sol <amount> [--simulate-only]
403
411
  slrd transfer <contact|wallet|address> --wallet <source-wallet> --token <USDC|mint> --amount <ui> [--simulate-only]
404
- slrd sweep sol --to <contact|wallet|address> [--wallets <a,b,...>] [--max-balance-sol <SOL>] [--exclude-group <group>] [--exclude-prefix <prefix>] [--keep <wallet=SOL,...>] [--keep-if-tokens <SOL> | --keep-if-token <token>=<SOL>] [--simulate | --live] [--json]
405
- Without --wallets, sweep considers all stored signing wallets
412
+ slrd sweep --to <contact|wallet|address> --below <SOL> [--wallets <a,b,...>] [--keep <wallet=SOL,...>] [--simulate | --live] [--json]
413
+ Sweeps stored wallets strictly below the threshold; preview is the default
414
+ slrd sweep sol ... Compatibility alias; --max-balance-sol remains accepted
406
415
 
407
416
  Token liquidation
408
417
  slrd liquidate tokens [--except <token|mint>] [--wallets <a,b,...>] [--except-wallet <wallet>] [--except-wallets <a,b,...>] [--slippage-bps 1500] [--no-jupiter] [--burn-unsellable] [--simulate | --live]
@@ -2073,13 +2082,43 @@ async function main() {
2073
2082
  return;
2074
2083
  }
2075
2084
 
2076
- if (command === "sweep" && values[0] === "sol") {
2077
- const destinationInput = flags.get("to") ?? values[1];
2085
+ if (command === "sweep") {
2086
+ const legacySolSyntax = values[0]?.toLowerCase() === "sol";
2087
+ const positionalDestination = legacySolSyntax ? values[1] : values[0];
2088
+ const extraValues = legacySolSyntax ? values.slice(2) : values.slice(1);
2089
+ if (extraValues.length > 0) {
2090
+ throw new Error(
2091
+ `${sweepUsage()}\nUnexpected argument: ${extraValues[0]}`,
2092
+ );
2093
+ }
2094
+
2095
+ const destinationInput = flags.get("to") ?? positionalDestination;
2078
2096
  if (!destinationInput || destinationInput === "true") {
2097
+ throw new Error(sweepUsage());
2098
+ }
2099
+
2100
+ const below = flags.get("below");
2101
+ const maxBalance = flags.get("max-balance-sol");
2102
+ if (below === "true") {
2103
+ throw new Error(`--below requires a SOL amount.\n${sweepUsage()}`);
2104
+ }
2105
+ if (maxBalance === "true") {
2079
2106
  throw new Error(
2080
- "Usage: slrd sweep sol --to <contact|wallet|address> [--wallets <a,b,...>] [--max-balance-sol <SOL>] [--exclude-group <group>] [--exclude-prefix <prefix>] [--keep <wallet=SOL,...>] [--keep-if-tokens <SOL> | --keep-if-token <token>=<SOL>] [--simulate | --live] [--json]",
2107
+ `--max-balance-sol requires a SOL amount.\n${sweepUsage()}`,
2081
2108
  );
2082
2109
  }
2110
+ if (
2111
+ below != null &&
2112
+ maxBalance != null &&
2113
+ maxBalance !== "true" &&
2114
+ below !== maxBalance
2115
+ ) {
2116
+ throw new Error("Use either --below or --max-balance-sol, not both.");
2117
+ }
2118
+ const maxBalanceSol = below ?? maxBalance;
2119
+ if (!legacySolSyntax && maxBalanceSol == null) {
2120
+ throw new Error(`--below is required for slrd sweep.\n${sweepUsage()}`);
2121
+ }
2083
2122
 
2084
2123
  const destination = resolveDestinationRef(slrd, destinationInput);
2085
2124
 
@@ -2120,17 +2159,8 @@ async function main() {
2120
2159
  "Use either --keep-if-tokens or --keep-if-token, not both",
2121
2160
  );
2122
2161
  }
2123
-
2124
- const sweepMode = flags.has("live")
2125
- ? "LIVE"
2126
- : flags.has("simulate")
2127
- ? "SIMULATE"
2128
- : "PLAN";
2129
- if (!flags.has("json")) {
2130
- emit(`SWEEP ${sweepMode} to=${destination.address}\n`);
2131
- emit(
2132
- `RPC hard limit ${process.env.SLRD_RPC_MAX_RPS ?? "5"} req/s\n`,
2133
- );
2162
+ if (flags.has("simulate") && flags.has("live")) {
2163
+ throw new Error("Use either --simulate or --live, not both");
2134
2164
  }
2135
2165
 
2136
2166
  const options = {
@@ -2138,7 +2168,7 @@ async function main() {
2138
2168
  excludeGroups,
2139
2169
  excludePrefixes,
2140
2170
  includeWallets: includeWallets.length ? includeWallets : undefined,
2141
- maxBalanceSol: flags.get("max-balance-sol"),
2171
+ maxBalanceSol,
2142
2172
  keepSolByWallet,
2143
2173
  defaultKeepSol: flags.get("default-keep-sol") ?? "0",
2144
2174
  keepSolIfTokens: flags.get("keep-if-tokens"),
@@ -2152,34 +2182,34 @@ async function main() {
2152
2182
  Math.trunc(int(flags, "rpc-delay-ms", 75) ?? 75),
2153
2183
  ),
2154
2184
  delayMs: int(flags, "delay-ms", 0) ?? 0,
2155
- onProgress: flags.has("json")
2156
- ? undefined
2157
- : (event: any) => {
2158
- if (event.stage === "plan-start") {
2159
- emit(`PLAN ${event.wallets} candidate wallets\n`);
2160
- } else if (event.stage === "plan-ready") {
2161
- emit(
2162
- `PLAN ${event.sendWallets}/${event.wallets} will send, ` +
2163
- `${formatRaw(event.totalSendLamports, 9)} SOL total\n`,
2164
- );
2165
- } else if (event.stage === "wallet-start") {
2166
- emit(
2167
- `SEND ${event.index}/${event.total} @${event.row.walletName} ` +
2168
- `${formatRaw(event.row.sendLamports, 9)} SOL\n`,
2169
- );
2170
- } else if (event.stage === "wallet-done") {
2171
- emit(
2172
- `OK ${event.index}/${event.total} @${event.row.walletName}\n`,
2173
- );
2174
- } else if (event.stage === "wallet-error") {
2175
- emit(
2176
- `FAIL ${event.index}/${event.total} @${event.row.walletName} ` +
2177
- `${event.error}\n`,
2178
- );
2185
+ onProgress:
2186
+ !flags.has("json") && flags.has("live")
2187
+ ? (event: any) => {
2188
+ if (event.stage === "wallet-start") {
2189
+ emit(
2190
+ `SEND ${event.index}/${event.total} @${event.row.walletName} ` +
2191
+ `${formatRaw(event.row.sendLamports, 9)} SOL\n`,
2192
+ );
2193
+ } else if (event.stage === "wallet-done") {
2194
+ emit(
2195
+ `OK ${event.index}/${event.total} @${event.row.walletName}\n`,
2196
+ );
2197
+ } else if (event.stage === "wallet-error") {
2198
+ emit(
2199
+ `FAIL ${event.index}/${event.total} @${event.row.walletName} ` +
2200
+ `${event.error}\n`,
2201
+ );
2202
+ }
2179
2203
  }
2180
- },
2204
+ : undefined,
2181
2205
  };
2182
2206
  const plan = await planRegistrySolSweep(slrd, options);
2207
+ const sendRows = plan.rows.filter((row) => row.sendLamports > 0n);
2208
+ const destinationLabel = destination.contactName
2209
+ ? `${destination.contactName} (${plan.destination})`
2210
+ : destination.walletName
2211
+ ? `${destination.walletName} (${plan.destination})`
2212
+ : plan.destination;
2183
2213
 
2184
2214
  const printablePlan = {
2185
2215
  mode: flags.has("live")
@@ -2193,6 +2223,7 @@ async function main() {
2193
2223
  contact: destination.contactName ?? null,
2194
2224
  wallet: destination.walletName ?? null,
2195
2225
  },
2226
+ belowSol: maxBalanceSol ?? null,
2196
2227
  totalSendSol: formatRaw(plan.totalSendLamports, 9),
2197
2228
  rows: plan.rows.map((row) => ({
2198
2229
  wallet: row.walletName,
@@ -2216,36 +2247,33 @@ async function main() {
2216
2247
  return;
2217
2248
  }
2218
2249
 
2250
+ emit(`SWEEP SOL -> ${destinationLabel}\n`);
2251
+ if (maxBalanceSol != null) emit(`Threshold: < ${maxBalanceSol} SOL\n`);
2219
2252
  emit(
2220
- `SWEEP PLAN to=${plan.destination} ` +
2221
- `send=${formatRaw(plan.totalSendLamports, 9)} SOL ` +
2222
- `wallets=${plan.rows.filter((row) => row.sendLamports > 0n).length}/${plan.rows.length}\n`,
2253
+ `Scanned: ${plan.rows.length} Send: ${sendRows.length} ` +
2254
+ `Skipped: ${plan.rows.length - sendRows.length} ` +
2255
+ `Total: ${formatRaw(plan.totalSendLamports, 9)} SOL\n`,
2223
2256
  );
2224
2257
 
2225
2258
  for (const row of plan.rows) {
2226
2259
  if (row.sendLamports <= 0n && !flags.has("show-skipped")) continue;
2227
- emit(
2228
- `${row.sendLamports > 0n ? "SEND" : "SKIP"} ` +
2229
- `@${row.walletName.padEnd(18)} ` +
2230
- `balance=${formatRaw(row.balanceLamports, 9).padStart(12)} ` +
2231
- `keep=${formatRaw(row.keepLamports, 9).padStart(5)} ` +
2232
- `send=${formatRaw(row.sendLamports, 9).padStart(12)}` +
2233
- `${row.reserveReason === "specific-token" ? " keep-token=yes" : ""}` +
2234
- `${row.skippedReason ? ` ${row.skippedReason}` : ""}\n`,
2235
- );
2260
+ if (row.sendLamports > 0n) {
2261
+ emit(
2262
+ ` @${row.walletName} ${formatRaw(row.balanceLamports, 9)} -> ` +
2263
+ `${formatRaw(row.sendLamports, 9)} SOL\n`,
2264
+ );
2265
+ } else {
2266
+ emit(
2267
+ ` @${row.walletName} ${formatRaw(row.balanceLamports, 9)} ` +
2268
+ `SKIP ${row.skippedReason ?? "not-sendable"}\n`,
2269
+ );
2270
+ }
2236
2271
  }
2237
2272
 
2238
- emit(
2239
- `\n${OWL} plan only; no signer was decrypted and nothing was submitted. ` +
2240
- `Use --json for full details, then --simulate or --live.\n`,
2241
- );
2273
+ emit(`\nPreview only. Add --simulate to verify or --live to send.\n`);
2242
2274
  return;
2243
2275
  }
2244
2276
 
2245
- if (flags.has("simulate") && flags.has("live")) {
2246
- throw new Error("Use either --simulate or --live, not both");
2247
- }
2248
-
2249
2277
  if (flags.has("simulate")) {
2250
2278
  const results = await simulateRegistrySolSweep(slrd, plan, options);
2251
2279
  if (flags.has("json")) {
@@ -2254,18 +2282,19 @@ async function main() {
2254
2282
  const failed = results.filter(
2255
2283
  (result) => result.error || result.simulation?.success === false,
2256
2284
  ).length;
2285
+ emit(`SWEEP SIMULATION -> ${destinationLabel}\n`);
2257
2286
  emit(
2258
- `SWEEP SIMULATION attempted=${results.length} ` +
2259
- `ok=${results.length - failed} failed=${failed} ` +
2260
- `to=${plan.destination}\n`,
2287
+ `Attempted: ${results.length} OK: ${results.length - failed} ` +
2288
+ `Failed: ${failed} Total: ${formatRaw(plan.totalSendLamports, 9)} SOL\n`,
2261
2289
  );
2262
2290
  for (const result of results) {
2263
2291
  if (!result.error && result.simulation?.success !== false) continue;
2264
2292
  emit(
2265
- `FAIL @${result.row.walletName} ` +
2293
+ ` FAIL @${result.row.walletName} ` +
2266
2294
  `${result.error ?? "simulation failed"}\n`,
2267
2295
  );
2268
2296
  }
2297
+ emit("No transactions submitted.\n");
2269
2298
  }
2270
2299
  return;
2271
2300
  }
@@ -2277,14 +2306,14 @@ async function main() {
2277
2306
  const failed = receipts.filter((result) =>
2278
2307
  Boolean(result.error),
2279
2308
  ).length;
2309
+ emit(`SWEEP COMPLETE -> ${destinationLabel}\n`);
2280
2310
  emit(
2281
- `SWEEP LIVE attempted=${receipts.length} ` +
2282
- `ok=${receipts.length - failed} failed=${failed} ` +
2283
- `to=${plan.destination}\n`,
2311
+ `Attempted: ${receipts.length} OK: ${receipts.length - failed} ` +
2312
+ `Failed: ${failed} Planned total: ${formatRaw(plan.totalSendLamports, 9)} SOL\n`,
2284
2313
  );
2285
2314
  for (const result of receipts) {
2286
2315
  if (!result.error) continue;
2287
- emit(`FAIL @${result.row.walletName} ${result.error}\n`);
2316
+ emit(` FAIL @${result.row.walletName} ${result.error}\n`);
2288
2317
  }
2289
2318
  }
2290
2319
  return;
@@ -2742,7 +2771,9 @@ async function main() {
2742
2771
  );
2743
2772
  return;
2744
2773
  }
2745
- throw new Error(`Unknown command: ${command}\n\n${help()}`);
2774
+ throw new Error(
2775
+ `Unknown command: ${command}. Run "slrd help" for available commands.`,
2776
+ );
2746
2777
  } finally {
2747
2778
  slrd.close();
2748
2779
  }