@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/package.json +4 -3
- package/src/accounting-commands.ts +621 -0
- package/src/backtest-batch-command.ts +1 -1
- package/src/backtest-command.ts +1 -1
- package/src/commands/buy.ts +1 -1
- package/src/commands/jupiter-swap.ts +1 -1
- package/src/commands/sell.ts +1 -1
- package/src/commands/trade-result.ts +5 -1
- package/src/commands/trade-targets.ts +1 -1
- package/src/commands/trade-venue.ts +1 -1
- package/src/commands/transfer.ts +1 -1
- package/src/fairfun-commands.ts +213 -61
- package/src/index.ts +246 -116
- package/src/meteora-commands.ts +1 -1
- package/src/pump/external-deployment-cli.ts +1 -1
- package/src/pump/spam-buy-cli.ts +1 -1
- package/src/pump/token-launch-cli.ts +3 -3
- package/src/pump/vamp-cli.ts +1 -1
- package/src/raydium-commands.ts +1 -1
- package/src/refs.ts +1 -1
- package/src/token-history-command.ts +11 -33
- package/src/vault-onboarding.ts +1 -1
package/src/commands/transfer.ts
CHANGED
package/src/fairfun-commands.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { PublicKey } from "@solana/web3.js";
|
|
3
|
-
import type {
|
|
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) =>
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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(
|
|
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
|
|
145
|
-
|
|
146
|
-
|
|
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
|
|
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
|
-
|
|
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,6 +218,59 @@ 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)
|
|
@@ -283,7 +342,7 @@ export async function handleFairfunCommand(args: {
|
|
|
283
342
|
);
|
|
284
343
|
} else {
|
|
285
344
|
emit(
|
|
286
|
-
`${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()}
|
|
345
|
+
`${new Date(event.blockTimeMs ?? event.observedAtMs).toISOString()} ${event.movement.toUpperCase()} ${event.amountRaw} ` +
|
|
287
346
|
`${event.sourceOwner ?? event.sourceTokenAccount} -> ${event.destinationOwner ?? event.destinationTokenAccount} sig=${event.signature}\n`,
|
|
288
347
|
);
|
|
289
348
|
}
|
|
@@ -393,46 +452,145 @@ export async function handleFairfunCommand(args: {
|
|
|
393
452
|
return true;
|
|
394
453
|
}
|
|
395
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
|
+
|
|
396
524
|
if (command === "rewards" && values[0] === "status") {
|
|
397
|
-
const
|
|
398
|
-
if (!
|
|
399
|
-
const
|
|
400
|
-
emit(json(
|
|
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");
|
|
401
529
|
return true;
|
|
402
530
|
}
|
|
403
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
|
+
);
|
|
553
|
+
return true;
|
|
554
|
+
}
|
|
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
|
+
|
|
404
565
|
if (command === "rewards" && values[0] === "distribute") {
|
|
405
566
|
const ref = values[1];
|
|
406
567
|
const wallet = flag(flags, "wallet");
|
|
407
|
-
const id = flag(flags, "id");
|
|
408
568
|
const snapshotPath = flag(flags, "snapshot");
|
|
409
|
-
if (!ref || !wallet || !
|
|
569
|
+
if (!ref || !wallet || !snapshotPath)
|
|
410
570
|
throw new Error(
|
|
411
|
-
"Usage: slrd rewards distribute <token|ca> --wallet <beneficiary> --
|
|
571
|
+
"Usage: slrd rewards distribute <token|ca> --wallet <beneficiary> --snapshot <snapshot.json> [--reward-mint <mint>] [--reserve-raw N] [--live]",
|
|
412
572
|
);
|
|
413
573
|
const token = await ensureToken(slrd, ref);
|
|
414
574
|
const snapshot = rewardSnapshotFile(snapshotPath);
|
|
415
575
|
const common = {
|
|
416
|
-
id,
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
+
})),
|
|
421
583
|
reserveRaw: raw(flag(flags, "reserve-raw"), "--reserve-raw") ?? 0n,
|
|
422
584
|
maxRecipientsPerTransaction: positiveInteger(flag(flags, "max-per-tx")),
|
|
423
585
|
};
|
|
424
586
|
if (!bool(flags, "live")) {
|
|
425
|
-
const plan = await slrd.
|
|
587
|
+
const plan = await slrd.distributions.plan(common);
|
|
426
588
|
emit(
|
|
427
589
|
json({
|
|
428
590
|
live: false,
|
|
429
|
-
version: plan.version,
|
|
430
591
|
id: plan.id,
|
|
431
|
-
snapshotId: plan.snapshotId,
|
|
432
|
-
tokenMint: plan.tokenMint,
|
|
433
592
|
sourceWallet: plan.sourceWallet,
|
|
434
|
-
|
|
435
|
-
totalClaimedRaw: plan.totalClaimedRaw,
|
|
593
|
+
asset: plan.asset,
|
|
436
594
|
totalEntitledRaw: plan.totalEntitledRaw,
|
|
437
595
|
totalConfirmedPaidRaw: plan.totalConfirmedPaidRaw,
|
|
438
596
|
totalOutstandingRaw: plan.totalOutstandingRaw,
|
|
@@ -441,21 +599,15 @@ export async function handleFairfunCommand(args: {
|
|
|
441
599
|
reserveRaw: plan.reserveRaw,
|
|
442
600
|
outstanding: plan.outstanding,
|
|
443
601
|
pending: plan.pending,
|
|
444
|
-
nextBatch: plan.
|
|
445
|
-
? {
|
|
446
|
-
recipients: plan.transferPlan.batches[0].allocations.length,
|
|
447
|
-
totalRaw: plan.transferPlan.batches[0].totalRaw,
|
|
448
|
-
serializedSize:
|
|
449
|
-
plan.transferPlan.batches[0].estimatedSerializedSize,
|
|
450
|
-
payments: plan.nextPayments,
|
|
451
|
-
}
|
|
602
|
+
nextBatch: plan.nextPayments.length
|
|
603
|
+
? { payments: plan.nextPayments }
|
|
452
604
|
: null,
|
|
453
605
|
}) + "\n",
|
|
454
606
|
);
|
|
455
607
|
return true;
|
|
456
608
|
}
|
|
457
609
|
requireLiveGate();
|
|
458
|
-
const state = await slrd.
|
|
610
|
+
const state = await slrd.distributions.execute({
|
|
459
611
|
...common,
|
|
460
612
|
via: flag(flags, "sender") ?? "rpc",
|
|
461
613
|
skipSimulation: bool(flags, "skip-simulation"),
|