@solard/cli 0.2.15 → 0.2.17

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.
@@ -1,4 +1,8 @@
1
- import type { TradeAsset, TradeRoute, TradeRouteResolution } from "@solard/sdk";
1
+ import type {
2
+ TradeAsset,
3
+ TradeRoute,
4
+ TradeRouteResolution,
5
+ } from "@solard/core";
2
6
 
3
7
  export type TradeRouteSummary = {
4
8
  route: TradeRoute;
@@ -1,4 +1,4 @@
1
- import type { Solard } from "@solard/sdk";
1
+ import type { Solard } from "@solard/core";
2
2
  import type { TradeCommandFlags } from "./trade-venue.ts";
3
3
 
4
4
  export type TradeTargets = {
@@ -1,4 +1,4 @@
1
- import type { VenuePreference } from "@solard/sdk";
1
+ import type { VenuePreference } from "@solard/core";
2
2
 
3
3
  export type TradeCommandFlags = ReadonlyMap<string, string>;
4
4
 
@@ -6,7 +6,7 @@ import {
6
6
  transferTokenIxs,
7
7
  type SenderId,
8
8
  type Solard,
9
- } from "@solard/sdk";
9
+ } from "@solard/core";
10
10
 
11
11
  import { resolveDestinationRef } from "../refs.ts";
12
12
 
@@ -1,6 +1,12 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { PublicKey } from "@solana/web3.js";
3
- import type { HolderRewardEntitlementSnapshotInput, Solard } from "@solard/sdk";
3
+ import type { Solard } from "@solard/core";
4
+
5
+ type FairfunEntitlementSnapshot = {
6
+ recipients: Array<{ wallet: string; entitledRaw: string }>;
7
+ totalEntitledRaw?: string;
8
+ observedAtMs: number | null;
9
+ };
4
10
 
5
11
  export type FairfunCliFlags = Map<string, string>;
6
12
  type Emit = (value: string) => void;
@@ -8,7 +14,12 @@ type Emit = (value: string) => void;
8
14
  function json(value: unknown): string {
9
15
  return JSON.stringify(
10
16
  value,
11
- (_, item) => (typeof item === "bigint" ? item.toString() : item),
17
+ (_, item) =>
18
+ typeof item === "bigint"
19
+ ? item.toString()
20
+ : item instanceof Map
21
+ ? Object.fromEntries(item)
22
+ : item,
12
23
  2,
13
24
  );
14
25
  }
@@ -110,26 +121,26 @@ function allocationsFile(
110
121
  });
111
122
  }
112
123
 
113
- function rewardSnapshotFile(
114
- path: string,
115
- ): HolderRewardEntitlementSnapshotInput {
124
+ function rewardSnapshotFile(path: string): FairfunEntitlementSnapshot {
116
125
  const parsed = JSON.parse(readFileSync(path, "utf8")) as any;
117
- if (!parsed || typeof parsed !== "object")
118
- throw new Error("Reward snapshot must be a JSON object");
119
- const id = String(parsed.id ?? parsed.snapshotId ?? "").trim();
120
- if (!id) throw new Error("Reward snapshot requires id");
121
- const totalClaimedRaw = String(parsed.totalClaimedRaw ?? "").trim();
122
- if (!/^\d+$/.test(totalClaimedRaw))
123
- throw new Error("Reward snapshot totalClaimedRaw must be a raw integer");
124
- if (!Array.isArray(parsed.recipients))
125
- throw new Error("Reward snapshot requires recipients: [...]");
126
- const recipients = parsed.recipients.map((row: any, index: number) => {
126
+ const rows = Array.isArray(parsed) ? parsed : parsed?.recipients;
127
+ if (!Array.isArray(rows))
128
+ throw new Error(
129
+ "Reward snapshot must be a JSON array or { recipients: [...] }",
130
+ );
131
+ const recipients = rows.map((row: any, index: number) => {
127
132
  if (!row || typeof row !== "object")
128
133
  throw new Error(
129
134
  `Reward snapshot recipient ${index + 1} must be an object`,
130
135
  );
131
- const wallet = String(row.wallet ?? "").trim();
132
- const entitledRaw = String(row.entitledRaw ?? "").trim();
136
+ const wallet = String(row.wallet ?? row.address ?? "").trim();
137
+ const entitledRaw = String(
138
+ row.entitledRaw ??
139
+ row.accumulatedRewardRaw ??
140
+ row.accumulatedRaw ??
141
+ row.rewardRaw ??
142
+ "",
143
+ ).trim();
133
144
  if (!wallet)
134
145
  throw new Error(`Reward snapshot recipient ${index + 1} requires wallet`);
135
146
  if (!/^\d+$/.test(entitledRaw))
@@ -141,11 +152,17 @@ function rewardSnapshotFile(
141
152
  entitledRaw,
142
153
  };
143
154
  });
144
- const slot = parsed.slot == null ? null : Number(parsed.slot);
145
- if (slot != null && (!Number.isInteger(slot) || slot < 0))
146
- throw new Error("Reward snapshot slot must be a non-negative integer");
155
+ const totalInput = Array.isArray(parsed)
156
+ ? undefined
157
+ : (parsed.totalEntitledRaw ?? parsed.totalRewardRaw ?? undefined);
158
+ const totalEntitledRaw =
159
+ totalInput == null ? undefined : String(totalInput).trim();
160
+ if (totalEntitledRaw != null && !/^\d+$/.test(totalEntitledRaw))
161
+ throw new Error("Reward snapshot totalEntitledRaw must be a raw integer");
147
162
  const observedAtMs =
148
- parsed.observedAtMs == null ? null : Number(parsed.observedAtMs);
163
+ Array.isArray(parsed) || parsed.observedAtMs == null
164
+ ? null
165
+ : Number(parsed.observedAtMs);
149
166
  if (
150
167
  observedAtMs != null &&
151
168
  (!Number.isFinite(observedAtMs) || observedAtMs < 0)
@@ -153,18 +170,7 @@ function rewardSnapshotFile(
153
170
  throw new Error(
154
171
  "Reward snapshot observedAtMs must be a non-negative number",
155
172
  );
156
- const claimSignature =
157
- parsed.claimSignature == null
158
- ? null
159
- : String(parsed.claimSignature).trim() || null;
160
- return {
161
- id,
162
- totalClaimedRaw,
163
- recipients,
164
- slot,
165
- observedAtMs,
166
- claimSignature,
167
- };
173
+ return { recipients, totalEntitledRaw, observedAtMs };
168
174
  }
169
175
 
170
176
  export async function handleFairfunCommand(args: {
@@ -212,22 +218,98 @@ export async function handleFairfunCommand(args: {
212
218
  return true;
213
219
  }
214
220
 
221
+ if (command === "events" && values[0] === "history") {
222
+ const ref = values[1];
223
+ if (!ref)
224
+ throw new Error(
225
+ "Usage: slrd events history <token|ca> [--provider auto|solscan|rpc] [--from-slot N] [--to-slot N] [--slot-order] [--json|--jsonl]",
226
+ );
227
+ const token = await ensureToken(slrd, ref);
228
+ const provider = flag(flags, "provider") ?? "auto";
229
+ if (!new Set(["auto", "solscan", "rpc"]).has(provider))
230
+ throw new Error("--provider must be auto, solscan, or rpc");
231
+ const history = await slrd.events.history(token.mint, {
232
+ provider: provider as "auto" | "solscan" | "rpc",
233
+ fromSlot: nonNegativeInteger(flag(flags, "from-slot")),
234
+ toSlot: nonNegativeInteger(flag(flags, "to-slot")),
235
+ maxPages: positiveInteger(flag(flags, "max-pages")),
236
+ exactOrdering: !bool(flags, "slot-order"),
237
+ verifyCurrentBalances: !bool(flags, "no-verify"),
238
+ commitment: bool(flags, "confirmed") ? "confirmed" : "finalized",
239
+ });
240
+ if (bool(flags, "json")) {
241
+ emit(json({ coverage: history.coverage, events: history.events }) + "\n");
242
+ return true;
243
+ }
244
+ if (bool(flags, "jsonl")) {
245
+ for (const event of history.events) {
246
+ emit(
247
+ JSON.stringify(event, (_, item) =>
248
+ typeof item === "bigint" ? item.toString() : item,
249
+ ) + "\n",
250
+ );
251
+ }
252
+ emit(JSON.stringify({ type: "coverage", ...history.coverage }) + "\n");
253
+ return true;
254
+ }
255
+ emit(
256
+ `EVENT HISTORY ${history.mint}\n` +
257
+ `provider=${history.coverage.provider} coverage=${history.coverage.status} transfer-coverage=${history.coverage.transferCoverage} from-creation=${history.coverage.fromCreation} authoritative=${history.coverage.authoritativeForBalanceReplay}\n` +
258
+ `events=${history.events.length} slots=${history.coverage.firstEventSlot ?? "?"}..${history.coverage.lastEventSlot ?? "?"} ordering=${history.coverage.ordering}\n` +
259
+ `balance-check=${history.coverage.balanceVerification.matches == null ? "not-checked" : history.coverage.balanceVerification.matches ? "match" : "MISMATCH"}\n\n`,
260
+ );
261
+ for (const event of history.events) {
262
+ emit(
263
+ `${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()} ${event.movement.toUpperCase()} ${event.amountRaw} ` +
264
+ `${event.sourceOwner ?? "MINT"} -> ${event.destinationOwner ?? "BURN"} slot=${event.slot} tx=${event.transactionIndex ?? "?"} sig=${event.signature}\n`,
265
+ );
266
+ }
267
+ if (history.coverage.warnings.length) {
268
+ emit("\nWARNINGS\n");
269
+ for (const warning of history.coverage.warnings) emit(`- ${warning}\n`);
270
+ }
271
+ return true;
272
+ }
273
+
215
274
  if (command === "events") {
216
275
  const ref = values[0];
217
276
  if (!ref)
218
277
  throw new Error(
219
- "Usage: slrd events <token|ca> [--swaps-only|--transfers-only] [--jsonl]",
278
+ "Usage: slrd events <token|ca> [--all|--swaps|--transfers|--creates|--swaps-only|--transfers-only] [--jsonl]",
220
279
  );
221
280
  const token = await ensureToken(slrd, ref);
222
281
  const controller = new AbortController();
223
282
  const stop = () => controller.abort();
224
283
  process.once("SIGINT", stop);
225
284
  process.once("SIGTERM", stop);
226
- const swaps = !bool(flags, "transfers-only");
227
- const transfers = !bool(flags, "swaps-only");
285
+
286
+ const explicit =
287
+ bool(flags, "all") ||
288
+ bool(flags, "swaps") ||
289
+ bool(flags, "transfers") ||
290
+ bool(flags, "creates");
291
+ let swaps = true;
292
+ let transfers = true;
293
+ let creates = false;
294
+ if (bool(flags, "swaps-only")) {
295
+ swaps = true;
296
+ transfers = false;
297
+ creates = false;
298
+ } else if (bool(flags, "transfers-only")) {
299
+ swaps = false;
300
+ transfers = true;
301
+ creates = false;
302
+ } else if (explicit) {
303
+ const all = bool(flags, "all");
304
+ swaps = all || bool(flags, "swaps");
305
+ transfers = all || bool(flags, "transfers");
306
+ creates = all || bool(flags, "creates");
307
+ }
308
+
228
309
  const subscription = await slrd.subscribeTokenEvents(token.mint, {
229
310
  swaps,
230
311
  transfers,
312
+ creates,
231
313
  commitment: bool(flags, "finalized") ? "finalized" : "confirmed",
232
314
  signal: controller.signal,
233
315
  });
@@ -251,11 +333,16 @@ export async function handleFairfunCommand(args: {
251
333
  if (event.type === "swap") {
252
334
  emit(
253
335
  `${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()} SWAP ${event.venue} ${event.side.toUpperCase()} ` +
254
- `${event.tokenAmountRaw} tokenRaw trader=${event.trader ?? "?"} sig=${event.signature}\n`,
336
+ `${event.tokenAmountRaw} tokenRaw quote=${event.quoteAmountRaw ?? "?"} ${event.quoteMint} trader=${event.trader ?? "?"} sig=${event.signature}\n`,
337
+ );
338
+ } else if (event.type === "create") {
339
+ emit(
340
+ `${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()} CREATE ` +
341
+ `${event.symbol ?? "?"} ${event.name ?? "?"} creator=${event.creator ?? "?"} quote=${event.quoteMint} sig=${event.signature}\n`,
255
342
  );
256
343
  } else {
257
344
  emit(
258
- `${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()} TRANSFER ${event.amountRaw} ` +
345
+ `${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()} ${event.movement.toUpperCase()} ${event.amountRaw} ` +
259
346
  `${event.sourceOwner ?? event.sourceTokenAccount} -> ${event.destinationOwner ?? event.destinationTokenAccount} sig=${event.signature}\n`,
260
347
  );
261
348
  }
@@ -365,46 +452,145 @@ export async function handleFairfunCommand(args: {
365
452
  return true;
366
453
  }
367
454
 
455
+ if (command === "rewards" && values[0] === "history") {
456
+ const ref = values[1];
457
+ if (!ref)
458
+ throw new Error(
459
+ "Usage: slrd rewards history <token|ca> [--wallet <reward-recipient>] [--provider auto|solscan|rpc] [--json|--jsonl]",
460
+ );
461
+ for (const key of [
462
+ "from-slot",
463
+ "to-slot",
464
+ "slot-order",
465
+ "no-verify",
466
+ "confirmed",
467
+ ]) {
468
+ if (flags.has(key)) {
469
+ throw new Error(
470
+ `--${key} is not supported by persistent replay; use slrd events history for bounded diagnostic history`,
471
+ );
472
+ }
473
+ }
474
+ const token = await ensureToken(slrd, ref);
475
+ const provider = flag(flags, "provider") ?? "auto";
476
+ if (!new Set(["auto", "solscan", "rpc"]).has(provider))
477
+ throw new Error("--provider must be auto, solscan, or rpc");
478
+ const history = await slrd.history.replay(token.mint, {
479
+ recipient: flag(flags, "wallet"),
480
+ provider: provider as "auto" | "solscan" | "rpc",
481
+ maxPages: positiveInteger(flag(flags, "max-pages")),
482
+ claimMaxPages: positiveInteger(flag(flags, "claim-max-pages")),
483
+ });
484
+ if (bool(flags, "json")) {
485
+ emit(
486
+ json({
487
+ mint: history.mint,
488
+ coverage: history.coverage,
489
+ items: history.items,
490
+ }) + "\n",
491
+ );
492
+ return true;
493
+ }
494
+ if (bool(flags, "jsonl")) {
495
+ for (const item of history.items) emit(json(item) + "\n");
496
+ emit(json({ type: "coverage", replay: history.coverage }) + "\n");
497
+ return true;
498
+ }
499
+ const transfers = history.items.filter((item) => item.trx !== "claim");
500
+ const claims = history.items.filter((item) => item.trx === "claim");
501
+ emit(
502
+ `REPLAY HISTORY ${history.mint}\n` +
503
+ `recipient=${"-"} authoritative=${history.coverage.complete} transfers=${transfers.length} claims=${claims.length} finalizedThroughSlot=${history.coverage.throughSlot}\n\n`,
504
+ );
505
+ for (const item of history.items) {
506
+ const event = item.raw;
507
+ if (event.type === "claim") {
508
+ emit(
509
+ `${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()} CLAIM ${event.payout.amountRaw} ${event.payout.assetMint} attribution=${event.attribution} slot=${item.slot} tx=${item.transactionIndex ?? "?"} sig=${item.signature}\n`,
510
+ );
511
+ } else {
512
+ emit(
513
+ `${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()} ${event.movement.toUpperCase()} ${event.amountRaw} ${event.sourceOwner ?? "MINT"} -> ${event.destinationOwner ?? "BURN"} slot=${item.slot} tx=${item.transactionIndex ?? "?"} sig=${item.signature}\n`,
514
+ );
515
+ }
516
+ }
517
+ if (history.coverage.warnings.length) {
518
+ emit("\nWARNINGS\n");
519
+ for (const warning of history.coverage.warnings) emit(`- ${warning}\n`);
520
+ }
521
+ return true;
522
+ }
523
+
368
524
  if (command === "rewards" && values[0] === "status") {
369
- const id = values[1];
370
- if (!id) throw new Error("Usage: slrd rewards status <distribution-id>");
371
- const { getHolderRewardDistributionState } = await import("@solard/sdk");
372
- emit(json(getHolderRewardDistributionState(slrd, id)) + "\n");
525
+ const ref = values[1];
526
+ if (!ref) throw new Error("Usage: slrd rewards status <token|ca>");
527
+ const token = await ensureToken(slrd, ref);
528
+ emit(json(slrd.distributions.status(`fairfun:${token.mint}`)) + "\n");
529
+ return true;
530
+ }
531
+
532
+ if (command === "rewards" && values[0] === "audit") {
533
+ const ref = values[1];
534
+ if (!ref)
535
+ throw new Error(
536
+ "Usage: slrd rewards audit <token|ca> [--holder <wallet>]",
537
+ );
538
+ const token = await ensureToken(slrd, ref);
539
+ const state = slrd.distributions.status(`fairfun:${token.mint}`);
540
+ const holder = flag(flags, "holder");
541
+ emit(
542
+ json(
543
+ holder && state
544
+ ? {
545
+ ...state,
546
+ recipients: state.recipients.filter(
547
+ (row) => row.recipient === holder,
548
+ ),
549
+ }
550
+ : state,
551
+ ) + "\n",
552
+ );
373
553
  return true;
374
554
  }
375
555
 
556
+ if (command === "rewards" && values[0] === "stop") {
557
+ const ref = values[1];
558
+ if (!ref) throw new Error("Usage: slrd rewards stop <token|ca>");
559
+ const token = await ensureToken(slrd, ref);
560
+ throw new Error(
561
+ `Distribution ${`fairfun:${token.mint}`} has no process-level stop state; stop the supervising process instead.`,
562
+ );
563
+ }
564
+
376
565
  if (command === "rewards" && values[0] === "distribute") {
377
566
  const ref = values[1];
378
567
  const wallet = flag(flags, "wallet");
379
- const id = flag(flags, "id");
380
568
  const snapshotPath = flag(flags, "snapshot");
381
- if (!ref || !wallet || !id || !snapshotPath)
569
+ if (!ref || !wallet || !snapshotPath)
382
570
  throw new Error(
383
- "Usage: slrd rewards distribute <token|ca> --wallet <beneficiary> --id <ledger-id> --snapshot <snapshot.json> [--reward-mint <mint>] [--reserve-raw N] [--live]",
571
+ "Usage: slrd rewards distribute <token|ca> --wallet <beneficiary> --snapshot <snapshot.json> [--reward-mint <mint>] [--reserve-raw N] [--live]",
384
572
  );
385
573
  const token = await ensureToken(slrd, ref);
386
574
  const snapshot = rewardSnapshotFile(snapshotPath);
387
575
  const common = {
388
- id,
389
- token: token.mint,
390
- wallet,
391
- rewardMint: flag(flags, "reward-mint"),
392
- snapshot,
576
+ id: flag(flags, "id") ?? `fairfun:${token.mint}`,
577
+ from: wallet,
578
+ asset: flag(flags, "reward-mint") ?? token.quoteMint ?? "SOL",
579
+ entitlements: snapshot.recipients.map((row) => ({
580
+ recipient: row.wallet,
581
+ entitledRaw: row.entitledRaw,
582
+ })),
393
583
  reserveRaw: raw(flag(flags, "reserve-raw"), "--reserve-raw") ?? 0n,
394
584
  maxRecipientsPerTransaction: positiveInteger(flag(flags, "max-per-tx")),
395
585
  };
396
586
  if (!bool(flags, "live")) {
397
- const plan = await slrd.planHolderRewards(common);
587
+ const plan = await slrd.distributions.plan(common);
398
588
  emit(
399
589
  json({
400
590
  live: false,
401
- version: plan.version,
402
591
  id: plan.id,
403
- snapshotId: plan.snapshotId,
404
- tokenMint: plan.tokenMint,
405
592
  sourceWallet: plan.sourceWallet,
406
- rewardAsset: plan.rewardAsset,
407
- totalClaimedRaw: plan.totalClaimedRaw,
593
+ asset: plan.asset,
408
594
  totalEntitledRaw: plan.totalEntitledRaw,
409
595
  totalConfirmedPaidRaw: plan.totalConfirmedPaidRaw,
410
596
  totalOutstandingRaw: plan.totalOutstandingRaw,
@@ -413,21 +599,15 @@ export async function handleFairfunCommand(args: {
413
599
  reserveRaw: plan.reserveRaw,
414
600
  outstanding: plan.outstanding,
415
601
  pending: plan.pending,
416
- nextBatch: plan.transferPlan?.batches[0]
417
- ? {
418
- recipients: plan.transferPlan.batches[0].allocations.length,
419
- totalRaw: plan.transferPlan.batches[0].totalRaw,
420
- serializedSize:
421
- plan.transferPlan.batches[0].estimatedSerializedSize,
422
- payments: plan.nextPayments,
423
- }
602
+ nextBatch: plan.nextPayments.length
603
+ ? { payments: plan.nextPayments }
424
604
  : null,
425
605
  }) + "\n",
426
606
  );
427
607
  return true;
428
608
  }
429
609
  requireLiveGate();
430
- const state = await slrd.distributeHolderRewards({
610
+ const state = await slrd.distributions.execute({
431
611
  ...common,
432
612
  via: flag(flags, "sender") ?? "rpc",
433
613
  skipSimulation: bool(flags, "skip-simulation"),