@solard/cli 0.2.16 → 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.
package/src/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bun
2
+ import { configure } from "measure-fn";
2
3
  import {
3
4
  addExternalContact,
4
- configureSolardMeasure,
5
5
  createSolardMeasureCollector,
6
6
  executeRegistrySolSweep,
7
7
  executeRegistryTokenLiquidation,
@@ -21,10 +21,10 @@ import {
21
21
  simulateRegistryTokenLiquidation,
22
22
  wrappedSolAta,
23
23
  type TokenRow,
24
- } from "@solard/sdk";
24
+ } from "@solard/core";
25
25
 
26
26
  import { handleMeteoraCommand } from "./meteora-commands.ts";
27
- import { handleFairfunCommand } from "./fairfun-commands.ts";
27
+ import { handleAccountingCommand } from "./accounting-commands.ts";
28
28
  import { resolveDestinationRef } from "./refs.ts";
29
29
  import {
30
30
  VaultOnboardingError,
@@ -77,7 +77,12 @@ function int(flags: Flags, key: string, fallback?: number): number | undefined {
77
77
  function json(value: unknown): string {
78
78
  return JSON.stringify(
79
79
  value,
80
- (_, item) => (typeof item === "bigint" ? item.toString() : item),
80
+ (_, item) =>
81
+ typeof item === "bigint"
82
+ ? item.toString()
83
+ : item instanceof Map
84
+ ? Object.fromEntries(item)
85
+ : item,
81
86
  2,
82
87
  );
83
88
  }
@@ -117,7 +122,7 @@ function friendlyCliError(error: unknown): string | null {
117
122
  if (code === "MISSING_CONFIG" && error.message.includes("SLRD_MASTER_KEY")) {
118
123
  return `${error.message}\n\nEnter the wallet password interactively, or set SLRD_MASTER_KEY for automation/CI.`;
119
124
  }
120
- return null;
125
+ return error.message.trim() || error.name;
121
126
  }
122
127
  function duration(value: string | undefined, fallbackMs: number): number {
123
128
  if (!value) return fallbackMs;
@@ -318,8 +323,16 @@ function commandNeedsSigningVault(
318
323
  return false;
319
324
  }
320
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
+
321
334
  async function storedWalletCount(): Promise<number> {
322
- const { createTraderSolard } = await import("@solard/sdk");
335
+ const { createTraderSolard } = await import("@solard/core");
323
336
  const probe = createTraderSolard();
324
337
  try {
325
338
  return (probe.db.wallets.select().all() as unknown[]).length;
@@ -357,6 +370,8 @@ External contacts (public addresses only; never signing wallets or group members
357
370
  slrd tokens
358
371
  slrd holders <token|ca> [--exclude <a,b>] [--min-raw N] [--json] Complete on-chain holder snapshot
359
372
  slrd events <token|ca> [--all|--swaps|--transfers|--creates] [--jsonl] Live typed Pump/PumpSwap + transfer/create stream
373
+ slrd events history <token|ca> [--provider auto|solscan|rpc] [--from-slot N] [--to-slot N] [--slot-order] [--json]
374
+ Historical holder-balance movements with explicit completeness verification
360
375
 
361
376
  Vanity mints
362
377
  slrd vanity --suffix pump --out .\\mint.json [--count <n>]
@@ -394,12 +409,13 @@ Backtesting
394
409
  Transfers and consolidation
395
410
  slrd transfer <contact|wallet|address> --wallet <source-wallet> --sol <amount> [--simulate-only]
396
411
  slrd transfer <contact|wallet|address> --wallet <source-wallet> --token <USDC|mint> --amount <ui> [--simulate-only]
397
- 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]
398
- 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
399
415
 
400
416
  Token liquidation
401
- slrd liquidate tokens --except <token|mint> [--wallets <a,b,...>] [--slippage-bps 1500] [--no-jupiter] [--burn-unsellable] [--simulate | --live]
402
- Sell supported tokens except protected mint(s); unwrap WSOL; close unprotected zero-balance token accounts and reclaim rent
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]
418
+ Sell all supported tokens unless protected with --except; excluded wallets are not scanned or touched
403
419
 
404
420
  RPC
405
421
  All Solard JSON-RPC traffic is globally rate-limited to 5 req/s by default.
@@ -424,11 +440,15 @@ Trading
424
440
  slrd transfer-many --wallet <wallet> (--token <mint>|--sol) --file <allocations.json> [--id <stable-id> --live]
425
441
  slrd transfer-many status <stable-id>
426
442
  slrd transfer-many resume <stable-id> [--sender rpc|helius]
427
- slrd rewards distribute <token|ca> --wallet <beneficiary> --id <ledger-id> --snapshot <snapshot.json> [--reward-mint <mint>] [--live]
428
- Snapshot contains cumulative totalClaimedRaw + recipients[].entitledRaw; Solard tracks confirmed payouts
429
- slrd rewards status <distribution-id>
443
+ slrd rewards history <token|ca> [--wallet <reward-recipient>] [--provider auto|solscan|rpc] [--json]
444
+ slrd rewards distribute <token|ca> --wallet <beneficiary> --snapshot <snapshot.json> [--reward-mint <mint>] [--live]
445
+ Pay every outstanding cumulative holder entitlement and exit when complete
446
+ slrd rewards stop <token|ca> Ask the currently running distribution for this token to stop before its next transfer
447
+ slrd rewards status <token|ca> Show remembered cumulative entitlement/payment state
448
+ slrd rewards audit <token|ca> [--holder <wallet>] Show entitlement and confirmed-payment history
430
449
  slrd rewards inspect <token|ca> Show claim source, quote asset, estimated accrued creator reward, and payout address
431
- slrd rewards claim <token|ca> --wallet <fee-payer> Claim creator rewards; on-chain beneficiary receives the reward
450
+ slrd rewards claim <token|ca> --wallet <fee-payer> [--id <stable-claim-id> --basis <reward-basis.json>]
451
+ slrd rewards claim-status <stable-claim-id>
432
452
 
433
453
  Scripts (strategies stay outside the kernel)
434
454
  slrd scripts List scripts registered in slrd.config.ts
@@ -483,6 +503,7 @@ Environment
483
503
  RPC_ENDPOINT required only for chain operations
484
504
  HELIUS_SENDER_URL regional/global Helius Sender endpoint used by launch scripts
485
505
  HELIUS_RPC_URL RPC endpoint used for ordinary Helius-RPC buyer lane
506
+ SOLSCAN_API_KEY optional indexed token-history provider; enables authoritative historical transfer reconstruction
486
507
  HELIUS_TIP_ACCOUNT, HELIUS_TIP_LAMPORTS, HELIUS_PRIORITY_MICRO_LAMPORTS launch transport policy
487
508
  SLRD_LAUNCH_* launch resend/timeout policy; shared environment, not per-token metadata
488
509
  JITO_BLOCK_ENGINE_URL only when explicitly selecting the separate Jito sender
@@ -508,7 +529,7 @@ async function main() {
508
529
  flags.has("measure-stream") ||
509
530
  process.env.SLRD_MEASURE_STREAM === "1" ||
510
531
  process.env.SLRD_MEASURE_STREAM === "true";
511
- configureSolardMeasure(
532
+ configure(
512
533
  streamMeasures
513
534
  ? {
514
535
  silent: false,
@@ -638,7 +659,7 @@ async function main() {
638
659
  listVanityMintPool,
639
660
  releaseVanityMintReservation,
640
661
  saveMintKeypairFile,
641
- } = await import("@solard/sdk");
662
+ } = await import("@solard/core");
642
663
 
643
664
  if (values[0] === "pool") {
644
665
  const action = values[1] ?? "list";
@@ -816,13 +837,13 @@ async function main() {
816
837
  process.env.JITO_BLOCK_ENGINE_URL ??
817
838
  "https://mainnet.block-engine.jito.wtf"
818
839
  ).replace(/\/$/, "");
819
- const { getJitoTipAccounts } = await import("@solard/sdk");
840
+ const { getJitoTipAccounts } = await import("@solard/core");
820
841
  const tipAccounts = await getJitoTipAccounts(endpoint);
821
842
  emit(json({ endpoint, tipAccounts }) + "\n");
822
843
  return;
823
844
  }
824
845
  if (command === "metadata" && values[0] === "upload") {
825
- const { uploadPumpMetadata } = await import("@solard/sdk");
846
+ const { uploadPumpMetadata } = await import("@solard/core");
826
847
  const uploaded = await uploadPumpMetadata(
827
848
  {
828
849
  imagePath: need(flags, "image"),
@@ -848,13 +869,13 @@ async function main() {
848
869
  }
849
870
  const [{ sol, formatRaw }, { shortKey }, { createTraderSolard }] =
850
871
  await Promise.all([
851
- import("@solard/sdk"),
852
- import("@solard/sdk"),
853
- import("@solard/sdk"),
872
+ import("@solard/core"),
873
+ import("@solard/core"),
874
+ import("@solard/core"),
854
875
  ]);
855
876
  const slrd = createTraderSolard();
856
877
  try {
857
- if (await handleFairfunCommand({ command, values, flags, slrd, emit })) {
878
+ if (await handleAccountingCommand({ command, values, flags, slrd, emit })) {
858
879
  return;
859
880
  }
860
881
 
@@ -986,11 +1007,8 @@ async function main() {
986
1007
  (vanityResult
987
1008
  ? ` vanity suffix=${vanityResult.suffix} attempts=${vanityResult.attempts}\n`
988
1009
  : "") +
989
- ` storage encrypted in Solard's local wallet database\n` +
990
- ` password NOT saved; you will enter it again next invocation\n` +
991
1010
  `╰──────────────────────────────────────────────────────╯\n` +
992
- `\nNext: slrd balances --wallet ${wallet.name}\n` +
993
- `Backup: slrd export ${wallet.name} --out .\\${wallet.name}-key.txt\n`,
1011
+ `\nNext: slrd balances --wallet ${wallet.name}\n`,
994
1012
  );
995
1013
  } else {
996
1014
  emit(`${OWL} created @${wallet.name} ${wallet.address}\n`);
@@ -1612,7 +1630,7 @@ async function main() {
1612
1630
  throw new Error(
1613
1631
  "Provide --uri <metadata_uri> or --image <local_path> --description <text>",
1614
1632
  );
1615
- const { uploadPumpMetadata } = await import("@solard/sdk");
1633
+ const { uploadPumpMetadata } = await import("@solard/core");
1616
1634
  uploadedMetadata = await uploadPumpMetadata(
1617
1635
  {
1618
1636
  imagePath: need(flags, "image"),
@@ -1864,11 +1882,18 @@ async function main() {
1864
1882
  }
1865
1883
  if (command === "liquidate" && (values[0] ?? "tokens") === "tokens") {
1866
1884
  const except = csv(flags.get("except"));
1867
- if (except.length === 0) {
1868
- throw new Error(
1869
- "Usage: slrd liquidate tokens --except <token|mint>[,<token|mint>...] [--wallets <a,b,...>] [--burn-unsellable] [--simulate | --live]",
1870
- );
1871
- }
1885
+ const exceptWalletValue =
1886
+ flags.get("except-wallet") ?? flags.get("exceptWallet");
1887
+ const exceptWalletsValue =
1888
+ flags.get("except-wallets") ?? flags.get("exceptWallets");
1889
+ if (exceptWalletValue === "true")
1890
+ throw new Error("Missing --except-wallet <wallet>");
1891
+ if (exceptWalletsValue === "true")
1892
+ throw new Error("Missing --except-wallets <a,b,...>");
1893
+ const exceptWalletRefs = [
1894
+ ...(exceptWalletValue ? [exceptWalletValue] : []),
1895
+ ...csv(exceptWalletsValue),
1896
+ ].filter((ref, index, refs) => refs.indexOf(ref) === index);
1872
1897
  if (flags.has("simulate") && flags.has("live")) {
1873
1898
  throw new Error("Use either --simulate or --live, not both");
1874
1899
  }
@@ -1884,6 +1909,10 @@ async function main() {
1884
1909
  const mint = resolveTokenMintForPolicy(slrd, ref);
1885
1910
  emit(`PROTECT ${ref} -> ${mint}\n`);
1886
1911
  }
1912
+ for (const ref of exceptWalletRefs) {
1913
+ const wallet = slrd.resolveWallet(ref);
1914
+ emit(`EXCLUDE ${ref} -> ${wallet.address.toBase58()}\n`);
1915
+ }
1887
1916
  emit(
1888
1917
  `RPC hard limit ${process.env.SLRD_RPC_MAX_RPS ?? "5"} req/s\n`,
1889
1918
  );
@@ -1899,19 +1928,27 @@ async function main() {
1899
1928
  name: string | null;
1900
1929
  amountUi: string;
1901
1930
  kind: string;
1931
+ tokenAccount?: string;
1902
1932
  },
1903
1933
  ) => {
1904
1934
  const label = action.symbol ?? action.name ?? shortKey(action.mint);
1935
+ const account =
1936
+ action.kind === "close-empty" && action.tokenAccount
1937
+ ? flags.has("verbose")
1938
+ ? action.tokenAccount
1939
+ : shortKey(action.tokenAccount)
1940
+ : null;
1905
1941
  emit(
1906
1942
  `${prefix} ${index}/${total} @${action.walletName} ` +
1907
1943
  `${action.kind === "unwrap-wsol" ? "WSOL" : label} ${action.amountUi}` +
1908
- `${action.kind === "close-empty" ? " [close empty account]" : ""}\n`,
1944
+ `${action.kind === "close-empty" ? ` [close empty account${account ? ` ${account}` : ""}]` : ""}\n`,
1909
1945
  );
1910
1946
  };
1911
1947
 
1912
1948
  const options = {
1913
1949
  except,
1914
1950
  walletRefs: csv(flags.get("wallets")),
1951
+ exceptWalletRefs,
1915
1952
  slippageBps: int(flags, "slippage-bps", 1500) ?? 1500,
1916
1953
  via: flags.get("sender") ?? "rpc",
1917
1954
  delayMs: int(flags, "delay-ms", 0) ?? 0,
@@ -1941,6 +1978,13 @@ async function main() {
1941
1978
  );
1942
1979
  } else if (event.stage === "action-done") {
1943
1980
  showAction("OK ", event.index, event.total, event.action);
1981
+ } else if (event.stage === "action-pending") {
1982
+ showAction("WAIT", event.index, event.total, event.action);
1983
+ if (flags.has("verbose")) {
1984
+ emit(
1985
+ ` attempt=${event.attempt}/${event.maxAttempts} signature=${event.signature ?? "-"} ${event.reason}\n`,
1986
+ );
1987
+ }
1944
1988
  } else if (event.stage === "action-error") {
1945
1989
  showAction("FAIL", event.index, event.total, event.action);
1946
1990
  emit(` ${event.error}\n`);
@@ -1981,7 +2025,7 @@ async function main() {
1981
2025
  }
1982
2026
 
1983
2027
  emit(
1984
- `KEEP ${plan.totals.keepProtected} protected SLRD holding(s)\n` +
2028
+ `KEEP ${plan.totals.keepProtected} protected token holding(s)\n` +
1985
2029
  `SKIP ${plan.totals.skipUnsupported} unsupported/no-liquidity holding(s)`,
1986
2030
  );
1987
2031
  if (plan.totals.skipUnsupported > 0) {
@@ -2038,13 +2082,43 @@ async function main() {
2038
2082
  return;
2039
2083
  }
2040
2084
 
2041
- if (command === "sweep" && values[0] === "sol") {
2042
- 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;
2043
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") {
2044
2106
  throw new Error(
2045
- "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()}`,
2046
2108
  );
2047
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
+ }
2048
2122
 
2049
2123
  const destination = resolveDestinationRef(slrd, destinationInput);
2050
2124
 
@@ -2085,17 +2159,8 @@ async function main() {
2085
2159
  "Use either --keep-if-tokens or --keep-if-token, not both",
2086
2160
  );
2087
2161
  }
2088
-
2089
- const sweepMode = flags.has("live")
2090
- ? "LIVE"
2091
- : flags.has("simulate")
2092
- ? "SIMULATE"
2093
- : "PLAN";
2094
- if (!flags.has("json")) {
2095
- emit(`SWEEP ${sweepMode} to=${destination.address}\n`);
2096
- emit(
2097
- `RPC hard limit ${process.env.SLRD_RPC_MAX_RPS ?? "5"} req/s\n`,
2098
- );
2162
+ if (flags.has("simulate") && flags.has("live")) {
2163
+ throw new Error("Use either --simulate or --live, not both");
2099
2164
  }
2100
2165
 
2101
2166
  const options = {
@@ -2103,7 +2168,7 @@ async function main() {
2103
2168
  excludeGroups,
2104
2169
  excludePrefixes,
2105
2170
  includeWallets: includeWallets.length ? includeWallets : undefined,
2106
- maxBalanceSol: flags.get("max-balance-sol"),
2171
+ maxBalanceSol,
2107
2172
  keepSolByWallet,
2108
2173
  defaultKeepSol: flags.get("default-keep-sol") ?? "0",
2109
2174
  keepSolIfTokens: flags.get("keep-if-tokens"),
@@ -2117,34 +2182,34 @@ async function main() {
2117
2182
  Math.trunc(int(flags, "rpc-delay-ms", 75) ?? 75),
2118
2183
  ),
2119
2184
  delayMs: int(flags, "delay-ms", 0) ?? 0,
2120
- onProgress: flags.has("json")
2121
- ? undefined
2122
- : (event: any) => {
2123
- if (event.stage === "plan-start") {
2124
- emit(`PLAN ${event.wallets} candidate wallets\n`);
2125
- } else if (event.stage === "plan-ready") {
2126
- emit(
2127
- `PLAN ${event.sendWallets}/${event.wallets} will send, ` +
2128
- `${formatRaw(event.totalSendLamports, 9)} SOL total\n`,
2129
- );
2130
- } else if (event.stage === "wallet-start") {
2131
- emit(
2132
- `SEND ${event.index}/${event.total} @${event.row.walletName} ` +
2133
- `${formatRaw(event.row.sendLamports, 9)} SOL\n`,
2134
- );
2135
- } else if (event.stage === "wallet-done") {
2136
- emit(
2137
- `OK ${event.index}/${event.total} @${event.row.walletName}\n`,
2138
- );
2139
- } else if (event.stage === "wallet-error") {
2140
- emit(
2141
- `FAIL ${event.index}/${event.total} @${event.row.walletName} ` +
2142
- `${event.error}\n`,
2143
- );
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
+ }
2144
2203
  }
2145
- },
2204
+ : undefined,
2146
2205
  };
2147
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;
2148
2213
 
2149
2214
  const printablePlan = {
2150
2215
  mode: flags.has("live")
@@ -2158,6 +2223,7 @@ async function main() {
2158
2223
  contact: destination.contactName ?? null,
2159
2224
  wallet: destination.walletName ?? null,
2160
2225
  },
2226
+ belowSol: maxBalanceSol ?? null,
2161
2227
  totalSendSol: formatRaw(plan.totalSendLamports, 9),
2162
2228
  rows: plan.rows.map((row) => ({
2163
2229
  wallet: row.walletName,
@@ -2181,36 +2247,33 @@ async function main() {
2181
2247
  return;
2182
2248
  }
2183
2249
 
2250
+ emit(`SWEEP SOL -> ${destinationLabel}\n`);
2251
+ if (maxBalanceSol != null) emit(`Threshold: < ${maxBalanceSol} SOL\n`);
2184
2252
  emit(
2185
- `SWEEP PLAN to=${plan.destination} ` +
2186
- `send=${formatRaw(plan.totalSendLamports, 9)} SOL ` +
2187
- `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`,
2188
2256
  );
2189
2257
 
2190
2258
  for (const row of plan.rows) {
2191
2259
  if (row.sendLamports <= 0n && !flags.has("show-skipped")) continue;
2192
- emit(
2193
- `${row.sendLamports > 0n ? "SEND" : "SKIP"} ` +
2194
- `@${row.walletName.padEnd(18)} ` +
2195
- `balance=${formatRaw(row.balanceLamports, 9).padStart(12)} ` +
2196
- `keep=${formatRaw(row.keepLamports, 9).padStart(5)} ` +
2197
- `send=${formatRaw(row.sendLamports, 9).padStart(12)}` +
2198
- `${row.reserveReason === "specific-token" ? " keep-token=yes" : ""}` +
2199
- `${row.skippedReason ? ` ${row.skippedReason}` : ""}\n`,
2200
- );
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
+ }
2201
2271
  }
2202
2272
 
2203
- emit(
2204
- `\n${OWL} plan only; no signer was decrypted and nothing was submitted. ` +
2205
- `Use --json for full details, then --simulate or --live.\n`,
2206
- );
2273
+ emit(`\nPreview only. Add --simulate to verify or --live to send.\n`);
2207
2274
  return;
2208
2275
  }
2209
2276
 
2210
- if (flags.has("simulate") && flags.has("live")) {
2211
- throw new Error("Use either --simulate or --live, not both");
2212
- }
2213
-
2214
2277
  if (flags.has("simulate")) {
2215
2278
  const results = await simulateRegistrySolSweep(slrd, plan, options);
2216
2279
  if (flags.has("json")) {
@@ -2219,18 +2282,19 @@ async function main() {
2219
2282
  const failed = results.filter(
2220
2283
  (result) => result.error || result.simulation?.success === false,
2221
2284
  ).length;
2285
+ emit(`SWEEP SIMULATION -> ${destinationLabel}\n`);
2222
2286
  emit(
2223
- `SWEEP SIMULATION attempted=${results.length} ` +
2224
- `ok=${results.length - failed} failed=${failed} ` +
2225
- `to=${plan.destination}\n`,
2287
+ `Attempted: ${results.length} OK: ${results.length - failed} ` +
2288
+ `Failed: ${failed} Total: ${formatRaw(plan.totalSendLamports, 9)} SOL\n`,
2226
2289
  );
2227
2290
  for (const result of results) {
2228
2291
  if (!result.error && result.simulation?.success !== false) continue;
2229
2292
  emit(
2230
- `FAIL @${result.row.walletName} ` +
2293
+ ` FAIL @${result.row.walletName} ` +
2231
2294
  `${result.error ?? "simulation failed"}\n`,
2232
2295
  );
2233
2296
  }
2297
+ emit("No transactions submitted.\n");
2234
2298
  }
2235
2299
  return;
2236
2300
  }
@@ -2242,14 +2306,14 @@ async function main() {
2242
2306
  const failed = receipts.filter((result) =>
2243
2307
  Boolean(result.error),
2244
2308
  ).length;
2309
+ emit(`SWEEP COMPLETE -> ${destinationLabel}\n`);
2245
2310
  emit(
2246
- `SWEEP LIVE attempted=${receipts.length} ` +
2247
- `ok=${receipts.length - failed} failed=${failed} ` +
2248
- `to=${plan.destination}\n`,
2311
+ `Attempted: ${receipts.length} OK: ${receipts.length - failed} ` +
2312
+ `Failed: ${failed} Planned total: ${formatRaw(plan.totalSendLamports, 9)} SOL\n`,
2249
2313
  );
2250
2314
  for (const result of receipts) {
2251
2315
  if (!result.error) continue;
2252
- emit(`FAIL @${result.row.walletName} ${result.error}\n`);
2316
+ emit(` FAIL @${result.row.walletName} ${result.error}\n`);
2253
2317
  }
2254
2318
  }
2255
2319
  return;
@@ -2423,23 +2487,84 @@ async function main() {
2423
2487
  }
2424
2488
  if (command === "rewards") {
2425
2489
  const action = values[0] ?? "inspect";
2490
+
2491
+ if (action === "claim-status") {
2492
+ const id = values[1];
2493
+ if (!id)
2494
+ throw new Error("Usage: slrd rewards claim-status <stable-claim-id>");
2495
+ emit(json(slrd.claims.creatorFees.status(id)) + "\n");
2496
+ return;
2497
+ }
2498
+
2426
2499
  const tokenRef = values[1];
2427
2500
  if (!tokenRef)
2428
2501
  throw new Error(
2429
- "Usage: slrd rewards <inspect|claim> <token|ca> [--wallet <fee-payer>]",
2502
+ "Usage: slrd rewards <inspect|claim|claim-status> <token|ca> [--wallet <fee-payer>]",
2430
2503
  );
2431
2504
 
2432
2505
  if (action === "claim") {
2433
2506
  const wallet = need(flags, "wallet");
2434
- const receipt = await slrd.claim(tokenRef, wallet, {
2507
+ const basisPath = flags.get("basis");
2508
+ let basis:
2509
+ | {
2510
+ id: string;
2511
+ slot: number;
2512
+ hash: string;
2513
+ observedAtMs?: number | null;
2514
+ }
2515
+ | undefined;
2516
+ if (basisPath && basisPath !== "true") {
2517
+ const { readFileSync } = await import("node:fs");
2518
+ const { resolve } = await import("node:path");
2519
+ const parsed = JSON.parse(
2520
+ readFileSync(resolve(basisPath), "utf8"),
2521
+ ) as Record<string, unknown>;
2522
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
2523
+ throw new Error("Reward basis file must contain a JSON object");
2524
+ basis = {
2525
+ id: String(parsed.id ?? "").trim(),
2526
+ slot: Number(parsed.slot),
2527
+ hash: String(parsed.hash ?? "").trim(),
2528
+ observedAtMs:
2529
+ parsed.observedAtMs == null ? null : Number(parsed.observedAtMs),
2530
+ };
2531
+ }
2532
+ const result = await slrd.claims.creatorFees.claim(tokenRef, wallet, {
2533
+ id: flags.get("id"),
2534
+ basis,
2435
2535
  via: flags.get("sender") ?? "rpc",
2536
+ skipSimulation: flags.has("skip-simulation"),
2537
+ skipPreflight: flags.has("skip-preflight"),
2436
2538
  });
2437
2539
  emit(
2438
2540
  json({
2439
2541
  action: "claim",
2440
- token: tokenRef,
2441
- feePayer: wallet,
2442
- receipt,
2542
+ claimId: result.claimId,
2543
+ token: result.tokenMint,
2544
+ feePayer: result.feePayer,
2545
+ source: result.source,
2546
+ quote: result.quoteAsset,
2547
+ estimatedClaimRaw: result.estimatedClaimRaw,
2548
+ claimedRaw: result.claimedRaw,
2549
+ claimedUi:
2550
+ result.claimedRaw == null
2551
+ ? null
2552
+ : formatRaw(result.claimedRaw, result.quoteAsset.decimals),
2553
+ payouts: result.payouts,
2554
+ receipt: result.receipt,
2555
+ basis: result.basis,
2556
+ checkpoint:
2557
+ result.claimedRaw == null
2558
+ ? null
2559
+ : {
2560
+ id: result.claimId ?? result.claimSignature,
2561
+ claimSignature: result.claimSignature,
2562
+ slot: result.claimSlot,
2563
+ claimedRaw: result.claimedRaw,
2564
+ quoteMint: result.quoteAsset.mint,
2565
+ observedAtMs: result.observedAtMs,
2566
+ basis: result.basis,
2567
+ },
2443
2568
  }) + "\n",
2444
2569
  );
2445
2570
  return;
@@ -2449,24 +2574,27 @@ async function main() {
2449
2574
  let token = slrd.resolveToken(tokenRef);
2450
2575
  try {
2451
2576
  token = await slrd.refreshToken(token);
2452
- } catch {
2453
- // A registered token can still expose a useful claim plan when one
2454
- // enrichment source is temporarily unavailable.
2455
- }
2577
+ } catch {}
2456
2578
  const { PublicKey } = await import("@solana/web3.js");
2457
2579
  const probeUser = token.creator
2458
2580
  ? new PublicKey(token.creator)
2459
2581
  : new PublicKey(token.mint);
2460
2582
  const plan = await slrd.resolveClaim(token, probeUser);
2583
+ const payoutAddresses = plan.payouts?.length
2584
+ ? plan.payouts.map((row) => row.address.toBase58())
2585
+ : typeof plan.meta?.payoutAddress === "string"
2586
+ ? [plan.meta.payoutAddress]
2587
+ : token.creator
2588
+ ? [token.creator]
2589
+ : [];
2461
2590
  const payoutAddress =
2462
- typeof plan.meta?.payoutAddress === "string"
2463
- ? plan.meta.payoutAddress
2464
- : token.creator;
2591
+ payoutAddresses.length === 1 ? payoutAddresses[0] : null;
2465
2592
  const output = {
2466
2593
  action: "inspect",
2467
2594
  token: token.mint,
2468
2595
  source: plan.source,
2469
2596
  payoutAddress: payoutAddress ?? null,
2597
+ payoutAddresses,
2470
2598
  quote: {
2471
2599
  kind: plan.quoteAsset.kind,
2472
2600
  mint: plan.quoteAsset.mint.toBase58(),
@@ -2486,7 +2614,7 @@ async function main() {
2486
2614
  return;
2487
2615
  }
2488
2616
 
2489
- throw new Error("Usage: slrd rewards <inspect|claim> <token|ca>");
2617
+ throw new Error("Usage: slrd rewards <inspect|claim|claim-status> ...");
2490
2618
  }
2491
2619
 
2492
2620
  if (command === "claim") {
@@ -2643,7 +2771,9 @@ async function main() {
2643
2771
  );
2644
2772
  return;
2645
2773
  }
2646
- throw new Error(`Unknown command: ${command}\n\n${help()}`);
2774
+ throw new Error(
2775
+ `Unknown command: ${command}. Run "slrd help" for available commands.`,
2776
+ );
2647
2777
  } finally {
2648
2778
  slrd.close();
2649
2779
  }
@@ -5,7 +5,7 @@ import {
5
5
  type MeteoraStrategy,
6
6
  type MeteoraTimeframe,
7
7
  type Solard,
8
- } from "@solard/sdk";
8
+ } from "@solard/core";
9
9
 
10
10
  const WSOL = "So11111111111111111111111111111111111111112";
11
11